Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgsauxiliarystorage.cpp - description
3 : : -------------------
4 : : begin : Aug 28, 2017
5 : : copyright : (C) 2017 by Paul Blottiere
6 : : email : paul.blottiere@oslandia.com
7 : : ***************************************************************************/
8 : :
9 : : /***************************************************************************
10 : : * *
11 : : * This program is free software; you can redistribute it and/or modify *
12 : : * it under the terms of the GNU General Public License as published by *
13 : : * the Free Software Foundation; either version 2 of the License, or *
14 : : * (at your option) any later version. *
15 : : * *
16 : : ***************************************************************************/
17 : :
18 : : #include "qgsauxiliarystorage.h"
19 : : #include "qgslogger.h"
20 : : #include "qgsspatialiteutils.h"
21 : : #include "qgsproject.h"
22 : : #include "qgsvectorlayerlabeling.h"
23 : : #include "qgsdiagramrenderer.h"
24 : : #include "qgsmemoryproviderutils.h"
25 : : #include "qgssymbollayer.h"
26 : :
27 : : #include <sqlite3.h>
28 : : #include <QFile>
29 : :
30 : : #define AS_JOINFIELD QStringLiteral( "ASPK" )
31 : : #define AS_EXTENSION QStringLiteral( "qgd" )
32 : : #define AS_JOINPREFIX QStringLiteral( "auxiliary_storage_" )
33 : : typedef QVector<QgsPalLayerSettings::Property> PalPropertyList;
34 : 0 : Q_GLOBAL_STATIC_WITH_ARGS( PalPropertyList, palHiddenProperties, (
35 : : {
36 : : QgsPalLayerSettings::PositionX,
37 : : QgsPalLayerSettings::PositionY,
38 : : QgsPalLayerSettings::Show,
39 : : QgsPalLayerSettings::LabelRotation,
40 : : QgsPalLayerSettings::Family,
41 : : QgsPalLayerSettings::FontStyle,
42 : : QgsPalLayerSettings::Size,
43 : : QgsPalLayerSettings::Bold,
44 : : QgsPalLayerSettings::Italic,
45 : : QgsPalLayerSettings::Underline,
46 : : QgsPalLayerSettings::Color,
47 : : QgsPalLayerSettings::Strikeout,
48 : : QgsPalLayerSettings::MultiLineAlignment,
49 : : QgsPalLayerSettings::BufferSize,
50 : : QgsPalLayerSettings::BufferDraw,
51 : : QgsPalLayerSettings::BufferColor,
52 : : QgsPalLayerSettings::LabelDistance,
53 : : QgsPalLayerSettings::Hali,
54 : : QgsPalLayerSettings::Vali,
55 : : QgsPalLayerSettings::ScaleVisibility,
56 : : QgsPalLayerSettings::MinScale,
57 : : QgsPalLayerSettings::MaxScale,
58 : : QgsPalLayerSettings::AlwaysShow,
59 : : QgsPalLayerSettings::CalloutDraw,
60 : : QgsPalLayerSettings::LabelAllParts
61 : : } ) )
62 : :
63 : : //
64 : : // QgsAuxiliaryLayer
65 : : //
66 : :
67 : 0 : QgsAuxiliaryLayer::QgsAuxiliaryLayer( const QString &pkField, const QString &filename, const QString &table, QgsVectorLayer *vlayer )
68 : 0 : : QgsVectorLayer( QStringLiteral( "%1|layername=%2" ).arg( filename, table ),
69 : 0 : QStringLiteral( "%1_auxiliarystorage" ).arg( table ), QStringLiteral( "ogr" ) )
70 : 0 : , mFileName( filename )
71 : 0 : , mTable( table )
72 : 0 : , mLayer( vlayer )
73 : 0 : {
74 : : // init join info
75 : 0 : mJoinInfo.setPrefix( AS_JOINPREFIX );
76 : 0 : mJoinInfo.setJoinLayer( this );
77 : 0 : mJoinInfo.setJoinFieldName( AS_JOINFIELD );
78 : 0 : mJoinInfo.setTargetFieldName( pkField );
79 : 0 : mJoinInfo.setEditable( true );
80 : 0 : mJoinInfo.setUpsertOnEdit( true );
81 : 0 : mJoinInfo.setCascadedDelete( true );
82 : 0 : mJoinInfo.setJoinFieldNamesBlockList( QStringList() << QStringLiteral( "rowid" ) ); // introduced by ogr provider
83 : 0 : }
84 : :
85 : 0 : QgsAuxiliaryLayer *QgsAuxiliaryLayer::clone( QgsVectorLayer *target ) const
86 : : {
87 : 0 : QgsAuxiliaryStorage::duplicateTable( source(), target->id() );
88 : 0 : return new QgsAuxiliaryLayer( mJoinInfo.targetFieldName(), mFileName, target->id(), target );
89 : 0 : }
90 : :
91 : 0 : bool QgsAuxiliaryLayer::clear()
92 : : {
93 : 0 : bool rc = deleteFeatures( allFeatureIds() );
94 : 0 : commitChanges();
95 : 0 : startEditing();
96 : 0 : return rc;
97 : 0 : }
98 : :
99 : 0 : QgsVectorLayer *QgsAuxiliaryLayer::toSpatialLayer() const
100 : : {
101 : 0 : QgsVectorLayer *layer = QgsMemoryProviderUtils::createMemoryLayer( QStringLiteral( "auxiliary_layer" ), fields(), mLayer->wkbType(), mLayer->crs() );
102 : :
103 : 0 : QString pkField = mJoinInfo.targetFieldName();
104 : 0 : QgsFeature joinFeature;
105 : 0 : QgsFeature targetFeature;
106 : 0 : QgsFeatureIterator it = getFeatures();
107 : :
108 : 0 : layer->startEditing();
109 : 0 : while ( it.nextFeature( joinFeature ) )
110 : : {
111 : 0 : QString filter = QgsExpression::createFieldEqualityExpression( pkField, joinFeature.attribute( AS_JOINFIELD ) );
112 : :
113 : 0 : QgsFeatureRequest request;
114 : 0 : request.setFilterExpression( filter );
115 : :
116 : 0 : mLayer->getFeatures( request ).nextFeature( targetFeature );
117 : :
118 : 0 : if ( targetFeature.isValid() )
119 : : {
120 : 0 : QgsFeature newFeature( joinFeature );
121 : 0 : newFeature.setGeometry( targetFeature.geometry() );
122 : 0 : layer->addFeature( newFeature );
123 : 0 : }
124 : 0 : }
125 : 0 : layer->commitChanges();
126 : :
127 : 0 : return layer;
128 : 0 : }
129 : :
130 : 0 : QgsVectorLayerJoinInfo QgsAuxiliaryLayer::joinInfo() const
131 : : {
132 : 0 : return mJoinInfo;
133 : : }
134 : :
135 : 0 : bool QgsAuxiliaryLayer::exists( const QgsPropertyDefinition &definition ) const
136 : : {
137 : 0 : return ( indexOfPropertyDefinition( definition ) >= 0 );
138 : : }
139 : :
140 : 0 : bool QgsAuxiliaryLayer::addAuxiliaryField( const QgsPropertyDefinition &definition )
141 : : {
142 : 0 : if ( ( definition.name().isEmpty() && definition.comment().isEmpty() ) || exists( definition ) )
143 : 0 : return false;
144 : :
145 : 0 : const QgsField af = createAuxiliaryField( definition );
146 : 0 : const bool rc = addAttribute( af );
147 : 0 : updateFields();
148 : 0 : mLayer->updateFields();
149 : :
150 : 0 : if ( rc )
151 : : {
152 : 0 : int auxIndex = indexOfPropertyDefinition( definition );
153 : 0 : int index = mLayer->fields().indexOf( nameFromProperty( definition, true ) );
154 : :
155 : 0 : if ( index >= 0 && auxIndex >= 0 )
156 : : {
157 : 0 : if ( isHiddenProperty( auxIndex ) )
158 : : {
159 : : // update editor widget
160 : 0 : QgsEditorWidgetSetup setup = QgsEditorWidgetSetup( QStringLiteral( "Hidden" ), QVariantMap() );
161 : 0 : setEditorWidgetSetup( auxIndex, setup );
162 : :
163 : : // column is hidden
164 : 0 : QgsAttributeTableConfig attrCfg = mLayer->attributeTableConfig();
165 : 0 : attrCfg.update( mLayer->fields() );
166 : 0 : QVector<QgsAttributeTableConfig::ColumnConfig> columns = attrCfg.columns();
167 : : QVector<QgsAttributeTableConfig::ColumnConfig>::iterator it;
168 : :
169 : 0 : for ( it = columns.begin(); it != columns.end(); ++it )
170 : : {
171 : 0 : if ( it->name.compare( mLayer->fields().field( index ).name() ) == 0 )
172 : 0 : it->hidden = true;
173 : 0 : }
174 : :
175 : 0 : attrCfg.setColumns( columns );
176 : 0 : mLayer->setAttributeTableConfig( attrCfg );
177 : 0 : }
178 : 0 : else if ( definition.standardTemplate() == QgsPropertyDefinition::ColorNoAlpha
179 : 0 : || definition.standardTemplate() == QgsPropertyDefinition::ColorWithAlpha )
180 : : {
181 : 0 : QgsEditorWidgetSetup setup = QgsEditorWidgetSetup( QStringLiteral( "Color" ), QVariantMap() );
182 : 0 : setEditorWidgetSetup( auxIndex, setup );
183 : 0 : }
184 : :
185 : 0 : mLayer->setEditorWidgetSetup( index, editorWidgetSetup( auxIndex ) );
186 : 0 : }
187 : 0 : }
188 : :
189 : 0 : return rc;
190 : 0 : }
191 : :
192 : 0 : QgsFields QgsAuxiliaryLayer::auxiliaryFields() const
193 : : {
194 : 0 : QgsFields afields;
195 : :
196 : 0 : for ( int i = 2; i < fields().count(); i++ ) // ignore rowid and PK field
197 : 0 : afields.append( createAuxiliaryField( fields().field( i ) ) );
198 : :
199 : 0 : return afields;
200 : 0 : }
201 : :
202 : 0 : bool QgsAuxiliaryLayer::deleteAttribute( int attr )
203 : : {
204 : 0 : QgsVectorLayer::deleteAttribute( attr );
205 : 0 : bool rc = commitChanges();
206 : 0 : startEditing();
207 : 0 : return rc;
208 : : }
209 : :
210 : 0 : bool QgsAuxiliaryLayer::save()
211 : : {
212 : 0 : bool rc = false;
213 : :
214 : 0 : if ( isEditable() )
215 : : {
216 : 0 : rc = commitChanges();
217 : 0 : }
218 : :
219 : 0 : startEditing();
220 : :
221 : 0 : return rc;
222 : : }
223 : :
224 : 0 : int QgsAuxiliaryLayer::createProperty( QgsPalLayerSettings::Property property, QgsVectorLayer *layer )
225 : : {
226 : 0 : int index = -1;
227 : :
228 : 0 : if ( layer && layer->labeling() && layer->auxiliaryLayer() )
229 : : {
230 : : // property definition are identical whatever the provider id
231 : 0 : const QgsPropertyDefinition def = layer->labeling()->settings().propertyDefinitions()[property];
232 : 0 : const QString fieldName = nameFromProperty( def, true );
233 : :
234 : 0 : layer->auxiliaryLayer()->addAuxiliaryField( def );
235 : :
236 : 0 : if ( layer->auxiliaryLayer()->indexOfPropertyDefinition( def ) >= 0 )
237 : : {
238 : 0 : const QgsProperty prop = QgsProperty::fromField( fieldName );
239 : :
240 : 0 : const QStringList subProviderIds = layer->labeling()->subProviders();
241 : 0 : for ( const QString &providerId : subProviderIds )
242 : : {
243 : 0 : QgsPalLayerSettings *settings = new QgsPalLayerSettings( layer->labeling()->settings( providerId ) );
244 : :
245 : 0 : QgsPropertyCollection c = settings->dataDefinedProperties();
246 : 0 : c.setProperty( property, prop );
247 : 0 : settings->setDataDefinedProperties( c );
248 : :
249 : 0 : layer->labeling()->setSettings( settings, providerId );
250 : 0 : }
251 : 0 : }
252 : :
253 : 0 : index = layer->fields().lookupField( fieldName );
254 : 0 : }
255 : :
256 : 0 : return index;
257 : 0 : }
258 : :
259 : 0 : int QgsAuxiliaryLayer::createProperty( QgsDiagramLayerSettings::Property property, QgsVectorLayer *layer )
260 : : {
261 : 0 : int index = -1;
262 : :
263 : 0 : if ( layer && layer->diagramLayerSettings() && layer->auxiliaryLayer() )
264 : : {
265 : 0 : const QgsPropertyDefinition def = layer->diagramLayerSettings()->propertyDefinitions()[property];
266 : :
267 : 0 : if ( layer->auxiliaryLayer()->addAuxiliaryField( def ) )
268 : : {
269 : 0 : const QString fieldName = nameFromProperty( def, true );
270 : 0 : const QgsProperty prop = QgsProperty::fromField( fieldName );
271 : :
272 : 0 : QgsDiagramLayerSettings settings( *layer->diagramLayerSettings() );
273 : :
274 : 0 : QgsPropertyCollection c = settings.dataDefinedProperties();
275 : 0 : c.setProperty( property, prop );
276 : 0 : settings.setDataDefinedProperties( c );
277 : :
278 : 0 : layer->setDiagramLayerSettings( settings );
279 : 0 : index = layer->fields().lookupField( fieldName );
280 : 0 : }
281 : 0 : }
282 : :
283 : 0 : return index;
284 : 0 : }
285 : :
286 : 0 : int QgsAuxiliaryLayer::createProperty( QgsCallout::Property property, QgsVectorLayer *layer )
287 : : {
288 : 0 : int index = -1;
289 : :
290 : 0 : if ( layer && layer->labeling() && layer->labeling()->settings().callout() && layer->auxiliaryLayer() )
291 : : {
292 : : // property definition are identical whatever the provider id
293 : 0 : const QgsPropertyDefinition def = layer->labeling()->settings().callout()->propertyDefinitions()[property];
294 : 0 : const QString fieldName = nameFromProperty( def, true );
295 : :
296 : 0 : layer->auxiliaryLayer()->addAuxiliaryField( def );
297 : :
298 : 0 : if ( layer->auxiliaryLayer()->indexOfPropertyDefinition( def ) >= 0 )
299 : : {
300 : 0 : const QgsProperty prop = QgsProperty::fromField( fieldName );
301 : :
302 : 0 : const QStringList subProviderIds = layer->labeling()->subProviders();
303 : 0 : for ( const QString &providerId : subProviderIds )
304 : : {
305 : 0 : QgsPalLayerSettings *settings = new QgsPalLayerSettings( layer->labeling()->settings( providerId ) );
306 : 0 : if ( settings->callout() )
307 : : {
308 : 0 : QgsPropertyCollection c = settings->callout()->dataDefinedProperties();
309 : 0 : c.setProperty( property, prop );
310 : 0 : settings->callout()->setDataDefinedProperties( c );
311 : 0 : }
312 : 0 : layer->labeling()->setSettings( settings, providerId );
313 : : }
314 : 0 : }
315 : :
316 : 0 : index = layer->fields().lookupField( fieldName );
317 : 0 : }
318 : :
319 : 0 : return index;
320 : 0 : }
321 : :
322 : 0 : bool QgsAuxiliaryLayer::isHiddenProperty( int index ) const
323 : : {
324 : 0 : bool hidden = false;
325 : 0 : QgsPropertyDefinition def = propertyDefinitionFromIndex( index );
326 : :
327 : 0 : if ( def.origin().compare( QLatin1String( "labeling" ) ) == 0 )
328 : : {
329 : 0 : const PalPropertyList &palProps = *palHiddenProperties();
330 : 0 : for ( const QgsPalLayerSettings::Property &p : palProps )
331 : : {
332 : 0 : const QString propName = QgsPalLayerSettings::propertyDefinitions()[ p ].name();
333 : 0 : if ( propName.compare( def.name() ) == 0 )
334 : : {
335 : 0 : hidden = true;
336 : 0 : break;
337 : : }
338 : 0 : }
339 : 0 : }
340 : :
341 : 0 : return hidden;
342 : 0 : }
343 : :
344 : 0 : int QgsAuxiliaryLayer::propertyFromIndex( int index ) const
345 : : {
346 : 0 : int p = -1;
347 : 0 : QgsPropertyDefinition aDef = propertyDefinitionFromIndex( index );
348 : :
349 : 0 : if ( aDef.origin().compare( QLatin1String( "labeling" ) ) == 0 )
350 : : {
351 : 0 : const QgsPropertiesDefinition defs = QgsPalLayerSettings::propertyDefinitions();
352 : 0 : QgsPropertiesDefinition::const_iterator it = defs.constBegin();
353 : 0 : for ( ; it != defs.constEnd(); ++it )
354 : : {
355 : 0 : if ( it->name().compare( aDef.name(), Qt::CaseInsensitive ) == 0 )
356 : : {
357 : 0 : p = it.key();
358 : 0 : break;
359 : : }
360 : 0 : }
361 : 0 : }
362 : 0 : else if ( aDef.origin().compare( QLatin1String( "symbol" ) ) == 0 )
363 : : {
364 : 0 : const QgsPropertiesDefinition defs = QgsSymbolLayer::propertyDefinitions();
365 : 0 : QgsPropertiesDefinition::const_iterator it = defs.constBegin();
366 : 0 : for ( ; it != defs.constEnd(); ++it )
367 : : {
368 : 0 : if ( it->name().compare( aDef.name(), Qt::CaseInsensitive ) == 0 )
369 : : {
370 : 0 : p = it.key();
371 : 0 : break;
372 : : }
373 : 0 : }
374 : 0 : }
375 : 0 : else if ( aDef.origin().compare( QLatin1String( "diagram" ) ) == 0 )
376 : : {
377 : 0 : const QgsPropertiesDefinition defs = QgsDiagramLayerSettings::propertyDefinitions();
378 : 0 : QgsPropertiesDefinition::const_iterator it = defs.constBegin();
379 : 0 : for ( ; it != defs.constEnd(); ++it )
380 : : {
381 : 0 : if ( it->name().compare( aDef.name(), Qt::CaseInsensitive ) == 0 )
382 : : {
383 : 0 : p = it.key();
384 : 0 : break;
385 : : }
386 : 0 : }
387 : 0 : }
388 : :
389 : 0 : return p;
390 : 0 : }
391 : :
392 : 0 : QgsPropertyDefinition QgsAuxiliaryLayer::propertyDefinitionFromIndex( int index ) const
393 : : {
394 : 0 : return propertyDefinitionFromField( fields().field( index ) );
395 : 0 : }
396 : :
397 : 0 : int QgsAuxiliaryLayer::indexOfPropertyDefinition( const QgsPropertyDefinition &def ) const
398 : : {
399 : 0 : return fields().indexOf( nameFromProperty( def ) );
400 : 0 : }
401 : :
402 : 0 : QString QgsAuxiliaryLayer::nameFromProperty( const QgsPropertyDefinition &def, bool joined )
403 : : {
404 : 0 : QString fieldName = def.origin();
405 : :
406 : 0 : if ( !def.name().isEmpty() )
407 : 0 : fieldName = QStringLiteral( "%1_%2" ).arg( fieldName, def.name().toLower() );
408 : :
409 : 0 : if ( !def.comment().isEmpty() )
410 : 0 : fieldName = QStringLiteral( "%1_%2" ).arg( fieldName, def.comment() );
411 : :
412 : 0 : if ( joined )
413 : 0 : fieldName = QStringLiteral( "%1%2" ).arg( AS_JOINPREFIX, fieldName );
414 : :
415 : 0 : return fieldName;
416 : 0 : }
417 : :
418 : 0 : QgsField QgsAuxiliaryLayer::createAuxiliaryField( const QgsPropertyDefinition &def )
419 : : {
420 : 0 : QgsField afield;
421 : :
422 : 0 : if ( !def.name().isEmpty() || !def.comment().isEmpty() )
423 : : {
424 : 0 : QVariant::Type type = QVariant::Invalid;
425 : 0 : QString typeName;
426 : 0 : int len( 0 ), precision( 0 );
427 : 0 : switch ( def.dataType() )
428 : : {
429 : : case QgsPropertyDefinition::DataTypeString:
430 : 0 : type = QVariant::String;
431 : 0 : len = 50;
432 : 0 : typeName = QStringLiteral( "String" );
433 : 0 : break;
434 : : case QgsPropertyDefinition::DataTypeNumeric:
435 : 0 : type = QVariant::Double;
436 : 13 : len = 0;
437 : 0 : precision = 0;
438 : 0 : typeName = QStringLiteral( "Real" );
439 : 0 : break;
440 : : case QgsPropertyDefinition::DataTypeBoolean:
441 : 0 : type = QVariant::Int; // sqlite does not have a bool type
442 : 0 : typeName = QStringLiteral( "Integer" );
443 : 0 : break;
444 : : }
445 : :
446 : 0 : afield.setType( type );
447 : 0 : afield.setName( nameFromProperty( def ) );
448 : 0 : afield.setTypeName( typeName );
449 : 0 : afield.setLength( len );
450 : 0 : afield.setPrecision( precision );
451 : 0 : }
452 : :
453 : 0 : return afield;
454 : 0 : }
455 : :
456 : 0 : QgsPropertyDefinition QgsAuxiliaryLayer::propertyDefinitionFromField( const QgsField &f )
457 : : {
458 : 0 : QgsPropertyDefinition def;
459 : 0 : const QStringList parts = f.name().split( '_' );
460 : :
461 : 0 : if ( parts.size() <= 1 )
462 : 0 : return def;
463 : :
464 : 0 : const QString origin = parts[0];
465 : 0 : const QString propertyName = parts[1];
466 : :
467 : 0 : if ( origin.compare( QLatin1String( "labeling" ), Qt::CaseInsensitive ) == 0 )
468 : : {
469 : 0 : const QgsPropertiesDefinition props = QgsPalLayerSettings::propertyDefinitions();
470 : 0 : for ( auto it = props.constBegin(); it != props.constEnd(); ++it )
471 : : {
472 : 0 : if ( it.value().name().compare( propertyName, Qt::CaseInsensitive ) == 0 )
473 : : {
474 : 0 : def = it.value();
475 : 0 : if ( parts.size() >= 3 )
476 : 0 : def.setComment( parts.mid( 2 ).join( '_' ) );
477 : 0 : break;
478 : : }
479 : 0 : }
480 : 0 : }
481 : 0 : else if ( origin.compare( QLatin1String( "symbol" ), Qt::CaseInsensitive ) == 0 )
482 : : {
483 : 0 : const QgsPropertiesDefinition props = QgsSymbolLayer::propertyDefinitions();
484 : 0 : for ( auto it = props.constBegin(); it != props.constEnd(); ++it )
485 : : {
486 : 0 : if ( it.value().name().compare( propertyName, Qt::CaseInsensitive ) == 0 )
487 : : {
488 : 0 : def = it.value();
489 : 0 : if ( parts.size() >= 3 )
490 : 0 : def.setComment( parts.mid( 2 ).join( '_' ) );
491 : 0 : break;
492 : : }
493 : 0 : }
494 : 0 : }
495 : 0 : else if ( origin.compare( QLatin1String( "diagram" ), Qt::CaseInsensitive ) == 0 )
496 : : {
497 : 0 : const QgsPropertiesDefinition props = QgsDiagramLayerSettings::propertyDefinitions();
498 : 0 : for ( auto it = props.constBegin(); it != props.constEnd(); ++it )
499 : : {
500 : 0 : if ( it.value().name().compare( propertyName, Qt::CaseInsensitive ) == 0 )
501 : : {
502 : 0 : def = it.value();
503 : 0 : if ( parts.size() >= 3 )
504 : 0 : def.setComment( parts.mid( 2 ).join( '_' ) );
505 : 0 : break;
506 : : }
507 : 0 : }
508 : 0 : }
509 : : else
510 : : {
511 : 0 : def.setOrigin( origin );
512 : 0 : def.setName( propertyName );
513 : 0 : switch ( f.type() )
514 : : {
515 : : case QVariant::Double:
516 : 0 : def.setDataType( QgsPropertyDefinition::DataTypeNumeric );
517 : 0 : break;
518 : :
519 : : case QVariant::Bool:
520 : 0 : def.setDataType( QgsPropertyDefinition::DataTypeBoolean );
521 : 0 : break;
522 : :
523 : : case QVariant::String:
524 : : default:
525 : 0 : def.setDataType( QgsPropertyDefinition::DataTypeString );
526 : 0 : break;
527 : : }
528 : :
529 : 0 : if ( parts.size() >= 3 )
530 : 0 : def.setComment( parts.mid( 2 ).join( '_' ) );
531 : : }
532 : :
533 : 0 : return def;
534 : 0 : }
535 : :
536 : 0 : QgsField QgsAuxiliaryLayer::createAuxiliaryField( const QgsField &field )
537 : : {
538 : 0 : QgsPropertyDefinition def = propertyDefinitionFromField( field );
539 : 0 : QgsField afield;
540 : :
541 : 0 : if ( !def.name().isEmpty() || !def.comment().isEmpty() )
542 : : {
543 : 0 : afield = createAuxiliaryField( def );
544 : 0 : afield.setTypeName( field.typeName() );
545 : 0 : }
546 : :
547 : 0 : return afield;
548 : 0 : }
549 : :
550 : : //
551 : : // QgsAuxiliaryStorage
552 : : //
553 : :
554 : 0 : QgsAuxiliaryStorage::QgsAuxiliaryStorage( const QgsProject &project, bool copy )
555 : 0 : : mCopy( copy )
556 : 0 : {
557 : 0 : initTmpFileName();
558 : :
559 : 0 : if ( !project.absoluteFilePath().isEmpty() )
560 : : {
561 : 0 : mFileName = filenameForProject( project );
562 : 0 : }
563 : :
564 : 0 : open( mFileName );
565 : 0 : }
566 : :
567 : 13 : QgsAuxiliaryStorage::QgsAuxiliaryStorage( const QString &filename, bool copy )
568 : 13 : : mFileName( filename )
569 : 13 : , mCopy( copy )
570 : 13 : {
571 : 13 : initTmpFileName();
572 : :
573 : 13 : open( filename );
574 : 13 : }
575 : :
576 : 22 : QgsAuxiliaryStorage::~QgsAuxiliaryStorage()
577 : 22 : {
578 : 11 : QFile::remove( mTmpFileName );
579 : 22 : }
580 : :
581 : 1 : bool QgsAuxiliaryStorage::isValid() const
582 : : {
583 : 1 : return mValid;
584 : : }
585 : :
586 : 0 : QString QgsAuxiliaryStorage::fileName() const
587 : : {
588 : 0 : return mFileName;
589 : : }
590 : :
591 : 0 : bool QgsAuxiliaryStorage::save() const
592 : : {
593 : 0 : if ( mFileName.isEmpty() )
594 : : {
595 : : // only a saveAs is available on a new database
596 : 0 : return false;
597 : : }
598 : 0 : else if ( mCopy )
599 : : {
600 : 0 : if ( QFile::exists( mFileName ) )
601 : 0 : QFile::remove( mFileName );
602 : :
603 : 0 : return QFile::copy( mTmpFileName, mFileName );
604 : : }
605 : : else
606 : : {
607 : : // if the file is not empty the copy mode is not activated, then we're
608 : : // directly working on the database since the beginning (no savepoints
609 : : // /rollback for now)
610 : 0 : return true;
611 : : }
612 : 0 : }
613 : :
614 : 0 : QgsAuxiliaryLayer *QgsAuxiliaryStorage::createAuxiliaryLayer( const QgsField &field, QgsVectorLayer *layer ) const
615 : : {
616 : 0 : QgsAuxiliaryLayer *alayer = nullptr;
617 : :
618 : 0 : if ( mValid && layer )
619 : : {
620 : 0 : const QString table( layer->id() );
621 : 0 : spatialite_database_unique_ptr database;
622 : 0 : database = openDB( currentFileName() );
623 : :
624 : 0 : if ( !tableExists( table, database.get() ) )
625 : : {
626 : 0 : if ( !createTable( field.typeName(), table, database.get() ) )
627 : : {
628 : 0 : return alayer;
629 : : }
630 : 0 : }
631 : :
632 : 0 : alayer = new QgsAuxiliaryLayer( field.name(), currentFileName(), table, layer );
633 : 0 : alayer->startEditing();
634 : 0 : }
635 : :
636 : 0 : return alayer;
637 : 0 : }
638 : :
639 : 0 : bool QgsAuxiliaryStorage::deleteTable( const QgsDataSourceUri &ogrUri )
640 : : {
641 : 0 : bool rc = false;
642 : 0 : QgsDataSourceUri uri = parseOgrUri( ogrUri );
643 : :
644 : 0 : if ( !uri.database().isEmpty() && !uri.table().isEmpty() )
645 : : {
646 : 0 : spatialite_database_unique_ptr database;
647 : 0 : database = openDB( uri.database() );
648 : :
649 : 0 : if ( database )
650 : : {
651 : 0 : QString sql = QStringLiteral( "DROP TABLE %1" ).arg( uri.table() );
652 : 0 : rc = exec( sql, database.get() );
653 : :
654 : 0 : sql = QStringLiteral( "VACUUM" );
655 : 0 : rc = exec( sql, database.get() );
656 : 0 : }
657 : 0 : }
658 : :
659 : 0 : return rc;
660 : 0 : }
661 : :
662 : 0 : bool QgsAuxiliaryStorage::duplicateTable( const QgsDataSourceUri &ogrUri, const QString &newTable )
663 : : {
664 : 0 : QgsDataSourceUri uri = parseOgrUri( ogrUri );
665 : 0 : bool rc = false;
666 : :
667 : 0 : if ( !uri.table().isEmpty() && !uri.database().isEmpty() )
668 : : {
669 : 0 : spatialite_database_unique_ptr database;
670 : 0 : database = openDB( uri.database() );
671 : :
672 : 0 : if ( database )
673 : : {
674 : 0 : QString sql = QStringLiteral( "CREATE TABLE %1 AS SELECT * FROM %2" ).arg( newTable, uri.table() );
675 : 0 : rc = exec( sql, database.get() );
676 : 0 : }
677 : 0 : }
678 : :
679 : 0 : return rc;
680 : 0 : }
681 : :
682 : 0 : QString QgsAuxiliaryStorage::errorString() const
683 : : {
684 : 0 : return mErrorString;
685 : : }
686 : :
687 : 0 : bool QgsAuxiliaryStorage::saveAs( const QString &filename )
688 : : {
689 : 0 : mErrorString.clear();
690 : :
691 : 0 : QFile dest( filename );
692 : 0 : if ( dest.exists() && !dest.remove() )
693 : : {
694 : 0 : mErrorString = dest.errorString();
695 : 0 : return false;
696 : : }
697 : :
698 : 0 : QFile origin( currentFileName() );
699 : 0 : if ( !origin.copy( filename ) )
700 : : {
701 : 0 : mErrorString = origin.errorString();
702 : 0 : return false;
703 : : }
704 : :
705 : 0 : return true;
706 : 0 : }
707 : :
708 : 0 : bool QgsAuxiliaryStorage::saveAs( const QgsProject &project )
709 : : {
710 : 0 : return saveAs( filenameForProject( project ) );
711 : 0 : }
712 : :
713 : 0 : QString QgsAuxiliaryStorage::extension()
714 : : {
715 : 0 : return AS_EXTENSION;
716 : : }
717 : :
718 : 0 : bool QgsAuxiliaryStorage::exists( const QgsProject &project )
719 : : {
720 : 0 : const QFileInfo fileinfo( filenameForProject( project ) );
721 : 0 : return fileinfo.exists() && fileinfo.isFile();
722 : 0 : }
723 : :
724 : 13 : bool QgsAuxiliaryStorage::exec( const QString &sql, sqlite3 *handler )
725 : : {
726 : 13 : bool rc = false;
727 : :
728 : 13 : if ( handler )
729 : : {
730 : 13 : const int err = sqlite3_exec( handler, sql.toStdString().c_str(), nullptr, nullptr, nullptr );
731 : :
732 : 13 : if ( err == SQLITE_OK )
733 : 13 : rc = true;
734 : : else
735 : 0 : debugMsg( sql, handler );
736 : 13 : }
737 : :
738 : 13 : return rc;
739 : 0 : }
740 : :
741 : 0 : void QgsAuxiliaryStorage::debugMsg( const QString &sql, sqlite3 *handler )
742 : : {
743 : : #ifdef QGISDEBUG
744 : : const QString err = QString::fromUtf8( sqlite3_errmsg( handler ) );
745 : : const QString msg = QObject::tr( "Unable to execute" );
746 : : const QString errMsg = QObject::tr( "%1 '%2': %3" ).arg( msg, sql, err );
747 : : QgsDebugMsg( errMsg );
748 : : #else
749 : 0 : Q_UNUSED( sql )
750 : : Q_UNUSED( handler )
751 : : #endif
752 : 0 : }
753 : :
754 : 0 : bool QgsAuxiliaryStorage::createTable( const QString &type, const QString &table, sqlite3 *handler )
755 : : {
756 : 0 : const QString sql = QStringLiteral( "CREATE TABLE IF NOT EXISTS '%1' ( '%2' %3 )" ).arg( table, AS_JOINFIELD, type );
757 : :
758 : 0 : if ( !exec( sql, handler ) )
759 : 0 : return false;
760 : :
761 : 0 : return true;
762 : 0 : }
763 : :
764 : 13 : spatialite_database_unique_ptr QgsAuxiliaryStorage::createDB( const QString &filename )
765 : : {
766 : 13 : spatialite_database_unique_ptr database;
767 : :
768 : : int rc;
769 : 13 : rc = database.open_v2( filename, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr );
770 : 13 : if ( rc )
771 : : {
772 : 0 : debugMsg( QStringLiteral( "sqlite3_open_v2" ), database.get() );
773 : 0 : }
774 : : else
775 : : // activating Foreign Key constraints
776 : 26 : exec( QStringLiteral( "PRAGMA foreign_keys = 1" ), database.get() );
777 : :
778 : 13 : return database;
779 : 13 : }
780 : :
781 : 0 : spatialite_database_unique_ptr QgsAuxiliaryStorage::openDB( const QString &filename )
782 : : {
783 : 0 : spatialite_database_unique_ptr database;
784 : 0 : int rc = database.open_v2( filename, SQLITE_OPEN_READWRITE, nullptr );
785 : :
786 : 0 : if ( rc )
787 : : {
788 : 0 : debugMsg( QStringLiteral( "sqlite3_open_v2" ), database.get() );
789 : 0 : }
790 : :
791 : 0 : return database;
792 : 0 : }
793 : :
794 : 0 : bool QgsAuxiliaryStorage::tableExists( const QString &table, sqlite3 *handler )
795 : : {
796 : 0 : const QString sql = QStringLiteral( "SELECT 1 FROM sqlite_master WHERE type='table' AND name='%1'" ).arg( table );
797 : 0 : int rows = 0;
798 : 0 : int columns = 0;
799 : 0 : char **results = nullptr;
800 : 0 : const int rc = sqlite3_get_table( handler, sql.toStdString().c_str(), &results, &rows, &columns, nullptr );
801 : 0 : if ( rc != SQLITE_OK )
802 : : {
803 : 0 : debugMsg( sql, handler );
804 : 0 : return false;
805 : : }
806 : :
807 : 0 : sqlite3_free_table( results );
808 : 0 : if ( rows >= 1 )
809 : 0 : return true;
810 : :
811 : 0 : return false;
812 : 0 : }
813 : :
814 : 13 : spatialite_database_unique_ptr QgsAuxiliaryStorage::open( const QString &filename )
815 : : {
816 : 13 : spatialite_database_unique_ptr database;
817 : :
818 : 13 : if ( filename.isEmpty() )
819 : : {
820 : 13 : if ( ( database = createDB( currentFileName() ) ) )
821 : 13 : mValid = true;
822 : 13 : }
823 : 0 : else if ( QFile::exists( filename ) )
824 : : {
825 : 0 : if ( mCopy )
826 : 0 : QFile::copy( filename, mTmpFileName );
827 : :
828 : 0 : if ( ( database = openDB( currentFileName() ) ) )
829 : 0 : mValid = true;
830 : 0 : }
831 : : else
832 : : {
833 : 0 : if ( ( database = createDB( currentFileName() ) ) )
834 : 0 : mValid = true;
835 : : }
836 : :
837 : 13 : return database;
838 : 13 : }
839 : :
840 : 0 : spatialite_database_unique_ptr QgsAuxiliaryStorage::open( const QgsProject &project )
841 : : {
842 : 0 : return open( filenameForProject( project ) );
843 : 0 : }
844 : :
845 : 0 : QString QgsAuxiliaryStorage::filenameForProject( const QgsProject &project )
846 : : {
847 : 0 : const QFileInfo info( project.absoluteFilePath() );
848 : 0 : const QString path = info.path() + QDir::separator() + info.baseName();
849 : 0 : return path + '.' + QgsAuxiliaryStorage::extension();
850 : 0 : }
851 : :
852 : 13 : void QgsAuxiliaryStorage::initTmpFileName()
853 : : {
854 : 13 : QTemporaryFile tmpFile;
855 : 13 : tmpFile.open();
856 : 13 : tmpFile.close();
857 : 13 : mTmpFileName = tmpFile.fileName();
858 : 13 : }
859 : :
860 : 13 : QString QgsAuxiliaryStorage::currentFileName() const
861 : : {
862 : 13 : if ( mCopy || mFileName.isEmpty() )
863 : 13 : return mTmpFileName;
864 : : else
865 : 0 : return mFileName;
866 : 13 : }
867 : :
868 : 0 : QgsDataSourceUri QgsAuxiliaryStorage::parseOgrUri( const QgsDataSourceUri &uri )
869 : : {
870 : 0 : QgsDataSourceUri newUri;
871 : :
872 : : // parsing for ogr style uri :
873 : : // " filePath|layername='tableName' table="" sql="
874 : 0 : QStringList uriParts = uri.uri().split( '|' );
875 : 0 : if ( uriParts.count() < 2 )
876 : 0 : return newUri;
877 : :
878 : 0 : const QString databasePath = uriParts[0].replace( ' ', QString() );
879 : :
880 : 0 : const QString table = uriParts[1];
881 : 0 : QStringList tableParts = table.split( ' ' );
882 : :
883 : 0 : if ( tableParts.count() < 1 )
884 : 0 : return newUri;
885 : :
886 : 0 : const QString tableName = tableParts[0].replace( QLatin1String( "layername=" ), QString() );
887 : :
888 : 0 : newUri.setDataSource( QString(), tableName, QString() );
889 : 0 : newUri.setDatabase( databasePath );
890 : :
891 : 0 : return newUri;
892 : 0 : }
|