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