1 /***************************************************************************
2 qgsvirtuallayerfeatureiterator.cpp
3 Feature iterator for the virtual layer provider
4 begin : Nov 2015
5 copyright : (C) 2015 Hugo Mercier, Oslandia
6 email : hugo dot mercier at oslandia dot 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 "qgsvirtuallayerfeatureiterator.h"
19 #include "qgsmessagelog.h"
20 #include "qgsgeometry.h"
21 #include "qgsvirtuallayerblob.h"
22 #include "qgsexception.h"
23
24 #include <stdexcept>
25
quotedColumn(QString name)26 static QString quotedColumn( QString name )
27 {
28 return "\"" + name.replace( QLatin1String( "\"" ), QLatin1String( "\"\"" ) ) + "\"";
29 }
30
QgsVirtualLayerFeatureIterator(QgsVirtualLayerFeatureSource * source,bool ownSource,const QgsFeatureRequest & request)31 QgsVirtualLayerFeatureIterator::QgsVirtualLayerFeatureIterator( QgsVirtualLayerFeatureSource *source, bool ownSource, const QgsFeatureRequest &request )
32 : QgsAbstractFeatureIteratorFromSource<QgsVirtualLayerFeatureSource>( source, ownSource, request )
33 {
34
35 // NOTE: this is really bad and should be removed.
36 // it's only here to guard mSource->mSqlite - because if the provider is removed
37 // then mSqlite will be meaningless.
38 // this needs to be totally reworked so that mSqlite no longer depends on the provider
39 // and can be fully encapsulated in the source
40 if ( !mSource->mProvider )
41 {
42 close();
43 return;
44 }
45
46 if ( mRequest.destinationCrs().isValid() && mRequest.destinationCrs() != mSource->mCrs )
47 {
48 mTransform = QgsCoordinateTransform( mSource->mCrs, mRequest.destinationCrs(), mRequest.transformContext() );
49 }
50 try
51 {
52 mFilterRect = filterRectToSourceCrs( mTransform );
53 }
54 catch ( QgsCsException & )
55 {
56 // can't reproject mFilterRect
57 close();
58 return;
59 }
60
61 // prepare spatial filter geometries for optimal speed
62 switch ( mRequest.spatialFilterType() )
63 {
64 case Qgis::SpatialFilterType::NoFilter:
65 case Qgis::SpatialFilterType::BoundingBox:
66 break;
67
68 case Qgis::SpatialFilterType::DistanceWithin:
69 if ( !mRequest.referenceGeometry().isEmpty() )
70 {
71 mDistanceWithinGeom = mRequest.referenceGeometry();
72 mDistanceWithinEngine.reset( QgsGeometry::createGeometryEngine( mDistanceWithinGeom.constGet() ) );
73 mDistanceWithinEngine->prepareGeometry();
74 }
75 break;
76 }
77
78 try
79 {
80 const QString tableName = mSource->mTableName;
81
82 QStringList wheres;
83 QString offset;
84 const QString subset = mSource->mSubset;
85 if ( !subset.isEmpty() )
86 {
87 wheres << subset;
88 }
89
90 QVariantList binded;
91 if ( !mSource->mDefinition.uid().isNull() )
92 {
93 // filters are only available when a column with unique id exists
94 if ( mSource->mDefinition.hasDefinedGeometry() && !mFilterRect.isNull() )
95 {
96 const bool do_exact = request.flags() & QgsFeatureRequest::ExactIntersect;
97 wheres << quotedColumn( mSource->mDefinition.geometryField() ) + " is not null";
98 wheres << QStringLiteral( "%1Intersects(%2,BuildMbr(?,?,?,?))" )
99 .arg( do_exact ? "" : "Mbr",
100 quotedColumn( mSource->mDefinition.geometryField() ) );
101
102 binded << mFilterRect.xMinimum() << mFilterRect.yMinimum()
103 << mFilterRect.xMaximum() << mFilterRect.yMaximum();
104 }
105 else if ( request.filterType() == QgsFeatureRequest::FilterFid )
106 {
107 wheres << QStringLiteral( "%1=%2" )
108 .arg( quotedColumn( mSource->mDefinition.uid() ) )
109 .arg( request.filterFid() );
110 }
111 else if ( request.filterType() == QgsFeatureRequest::FilterFids )
112 {
113 QString values = quotedColumn( mSource->mDefinition.uid() ) + " IN (";
114 bool first = true;
115 const auto constFilterFids = request.filterFids();
116 for ( const QgsFeatureId v : constFilterFids )
117 {
118 if ( !first )
119 {
120 values += QLatin1Char( ',' );
121 }
122 first = false;
123 values += QString::number( v );
124 }
125 values += QLatin1Char( ')' );
126 wheres << values;
127 }
128 }
129 else
130 {
131 if ( request.filterType() == QgsFeatureRequest::FilterFid )
132 {
133 if ( request.filterFid() >= 0 )
134 offset = QStringLiteral( " LIMIT 1 OFFSET %1" ).arg( request.filterFid() );
135 else // never return a feature if the id is negative
136 offset = QStringLiteral( " LIMIT 0" );
137 }
138 if ( !mFilterRect.isNull() && mRequest.spatialFilterType() == Qgis::SpatialFilterType::BoundingBox
139 && mRequest.flags() & QgsFeatureRequest::ExactIntersect )
140 {
141 // if an exact intersection is requested, prepare the geometry to intersect
142 const QgsGeometry rectGeom = QgsGeometry::fromRect( mFilterRect );
143 mRectEngine.reset( QgsGeometry::createGeometryEngine( rectGeom.constGet() ) );
144 mRectEngine->prepareGeometry();
145 }
146 }
147
148 if ( request.flags() & QgsFeatureRequest::SubsetOfAttributes )
149 {
150 // copy only selected fields
151 const auto subsetOfAttributes = request.subsetOfAttributes();
152 for ( const int idx : subsetOfAttributes )
153 {
154 mAttributes << idx;
155 }
156
157 // ensure that all attributes required for expression filter are being fetched
158 if ( request.filterType() == QgsFeatureRequest::FilterExpression )
159 {
160 const QSet<int> attributeIndexes = request.filterExpression()->referencedAttributeIndexes( mSource->mFields );
161 for ( const int attrIdx : attributeIndexes )
162 {
163 if ( !mAttributes.contains( attrIdx ) )
164 mAttributes << attrIdx;
165 }
166 }
167
168 // also need attributes required by order by
169 if ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes && !mRequest.orderBy().isEmpty() )
170 {
171 const auto usedAttributeIndices = mRequest.orderBy().usedAttributeIndices( mSource->mFields );
172 for ( const int attrIdx : usedAttributeIndices )
173 {
174 if ( !mAttributes.contains( attrIdx ) )
175 mAttributes << attrIdx;
176 }
177 }
178 }
179 else
180 {
181 mAttributes = mSource->mFields.allAttributesList();
182 }
183
184 QString columns;
185 {
186 // the first column is always the uid (or 0)
187 if ( !mSource->mDefinition.uid().isNull() )
188 {
189 columns = quotedColumn( mSource->mDefinition.uid() );
190 }
191 else
192 {
193 if ( request.filterType() == QgsFeatureRequest::FilterFid )
194 {
195 columns = QString::number( request.filterFid() );
196 }
197 else
198 {
199 columns = QStringLiteral( "0" );
200 }
201 }
202 const auto constMAttributes = mAttributes;
203 for ( const int i : constMAttributes )
204 {
205 columns += QLatin1Char( ',' );
206 const QString cname = mSource->mFields.at( i ).name().toLower();
207 columns += quotedColumn( cname );
208 }
209 }
210 // the last column is the geometry, if any
211 if ( ( !( request.flags() & QgsFeatureRequest::NoGeometry )
212 || ( request.filterType() == QgsFeatureRequest::FilterExpression && request.filterExpression()->needsGeometry() ) )
213 && !mSource->mDefinition.geometryField().isNull() && mSource->mDefinition.geometryField() != QLatin1String( "*no*" ) )
214 {
215 columns += "," + quotedColumn( mSource->mDefinition.geometryField() );
216 }
217
218 mSqlQuery = "SELECT " + columns + " FROM " + tableName;
219 if ( !wheres.isEmpty() )
220 {
221 mSqlQuery += " WHERE " + wheres.join( QLatin1String( " AND " ) );
222 }
223
224 if ( !offset.isEmpty() )
225 {
226 mSqlQuery += offset;
227 }
228
229 mQuery.reset( new Sqlite::Query( mSource->mSqlite, mSqlQuery ) );
230 for ( const QVariant &toBind : binded )
231 {
232 mQuery->bind( toBind );
233 }
234
235 mFid = 0;
236 }
237 catch ( std::runtime_error &e )
238 {
239 QgsMessageLog::logMessage( e.what(), QObject::tr( "VLayer" ) );
240 close();
241 }
242 }
243
~QgsVirtualLayerFeatureIterator()244 QgsVirtualLayerFeatureIterator::~QgsVirtualLayerFeatureIterator()
245 {
246 close();
247 }
248
rewind()249 bool QgsVirtualLayerFeatureIterator::rewind()
250 {
251 if ( mClosed )
252 {
253 return false;
254 }
255
256 mQuery->reset();
257
258 return true;
259 }
260
close()261 bool QgsVirtualLayerFeatureIterator::close()
262 {
263 if ( mClosed )
264 {
265 return false;
266 }
267
268 // this call is absolutely needed
269 iteratorClosed();
270
271 mClosed = true;
272 return true;
273 }
274
fetchFeature(QgsFeature & feature)275 bool QgsVirtualLayerFeatureIterator::fetchFeature( QgsFeature &feature )
276 {
277 feature.setValid( false );
278
279 if ( mClosed )
280 {
281 return false;
282 }
283
284 bool skipFeature = false;
285 do
286 {
287 if ( mQuery->step() != SQLITE_ROW )
288 {
289 return false;
290 }
291 skipFeature = false;
292
293 feature.setFields( mSource->mFields, /* init */ true );
294
295 if ( mSource->mDefinition.uid().isNull() &&
296 mRequest.filterType() != QgsFeatureRequest::FilterFid )
297 {
298 // no id column => autoincrement
299 feature.setId( mFid++ );
300 }
301 else
302 {
303 // first column: uid
304 feature.setId( mQuery->columnInt64( 0 ) );
305 }
306
307 const int n = mQuery->columnCount();
308 int i = 0;
309 const auto constMAttributes = mAttributes;
310 for ( const int idx : constMAttributes )
311 {
312 const int type = mQuery->columnType( i + 1 );
313 switch ( type )
314 {
315 case SQLITE_INTEGER:
316 feature.setAttribute( idx, mQuery->columnInt64( i + 1 ) );
317 break;
318 case SQLITE_FLOAT:
319 feature.setAttribute( idx, mQuery->columnDouble( i + 1 ) );
320 break;
321 case SQLITE_TEXT:
322 default:
323 feature.setAttribute( idx, mQuery->columnText( i + 1 ) );
324 break;
325 }
326 i++;
327 }
328 if ( n > mAttributes.size() + 1 )
329 {
330 // geometry field
331 const QByteArray blob( mQuery->columnBlob( n - 1 ) );
332 if ( blob.size() > 0 )
333 {
334 feature.setGeometry( spatialiteBlobToQgsGeometry( blob.constData(), blob.size() ) );
335 }
336 else
337 {
338 feature.clearGeometry();
339 }
340 }
341
342 feature.setValid( true );
343 geometryToDestinationCrs( feature, mTransform );
344
345 // if the FilterRect has not been applied on the query
346 // apply it here by skipping features until they intersect
347 if ( mSource->mDefinition.uid().isNull() && feature.hasGeometry() && mSource->mDefinition.hasDefinedGeometry() )
348 {
349 if ( !mFilterRect.isNull() )
350 {
351 if ( mRequest.spatialFilterType() == Qgis::SpatialFilterType::BoundingBox && mRequest.flags() & QgsFeatureRequest::ExactIntersect )
352 {
353 // using exact test when checking for intersection
354 skipFeature = !mRectEngine->intersects( feature.geometry().constGet() );
355 }
356 else
357 {
358 // check just bounding box against rect when not using intersection
359 skipFeature = !feature.geometry().boundingBox().intersects( mFilterRect );
360 }
361 }
362 }
363
364 if ( !skipFeature && mDistanceWithinEngine )
365 {
366 if ( mDistanceWithinEngine->distance( feature.geometry().constGet() ) > mRequest.distanceWithin() )
367 skipFeature = true;
368 }
369 }
370 while ( skipFeature );
371
372 return true;
373 }
374
QgsVirtualLayerFeatureSource(const QgsVirtualLayerProvider * p)375 QgsVirtualLayerFeatureSource::QgsVirtualLayerFeatureSource( const QgsVirtualLayerProvider *p )
376 : mProvider( p )
377 , mDefinition( p->mDefinition )
378 , mFields( p->fields() )
379 , mSqlite( p->mSqlite.get() )
380 , mTableName( p->mTableName )
381 , mSubset( p->mSubset )
382 , mCrs( p->crs() )
383 {
384 }
385
getFeatures(const QgsFeatureRequest & request)386 QgsFeatureIterator QgsVirtualLayerFeatureSource::getFeatures( const QgsFeatureRequest &request )
387 {
388 return QgsFeatureIterator( new QgsVirtualLayerFeatureIterator( this, false, request ) );
389 }
390