1 /***************************************************************************
2     qgsspatialitefeatureiterator.cpp
3     ---------------------
4     begin                : Juli 2012
5     copyright            : (C) 2012 by Martin Dobias
6     email                : wonder dot sk at gmail dot com
7  ***************************************************************************
8  *                                                                         *
9  *   This program is free software; you can redistribute it and/or modify  *
10  *   it under the terms of the GNU General Public License as published by  *
11  *   the Free Software Foundation; either version 2 of the License, or     *
12  *   (at your option) any later version.                                   *
13  *                                                                         *
14  ***************************************************************************/
15 #include "qgsspatialitefeatureiterator.h"
16 
17 #include "qgsspatialiteconnection.h"
18 #include "qgsspatialiteconnpool.h"
19 #include "qgsspatialiteprovider.h"
20 #include "qgssqliteexpressioncompiler.h"
21 
22 #include "qgsgeometry.h"
23 #include "qgslogger.h"
24 #include "qgsmessagelog.h"
25 #include "qgsjsonutils.h"
26 #include "qgssettings.h"
27 #include "qgsexception.h"
28 
QgsSpatiaLiteFeatureIterator(QgsSpatiaLiteFeatureSource * source,bool ownSource,const QgsFeatureRequest & request)29 QgsSpatiaLiteFeatureIterator::QgsSpatiaLiteFeatureIterator( QgsSpatiaLiteFeatureSource *source, bool ownSource, const QgsFeatureRequest &request )
30   : QgsAbstractFeatureIteratorFromSource<QgsSpatiaLiteFeatureSource>( source, ownSource, request )
31 {
32 
33   mSqliteHandle = source->transactionHandle();
34   if ( ! mSqliteHandle )
35   {
36     mHandle = QgsSpatiaLiteConnPool::instance()->acquireConnection( mSource->mSqlitePath, request.timeout(), request.requestMayBeNested() );
37     if ( mHandle )
38     {
39       mSqliteHandle = mHandle->handle();
40     }
41   }
42 
43   mFetchGeometry = !mSource->mGeometryColumn.isNull() && !( mRequest.flags() & QgsFeatureRequest::NoGeometry );
44   mHasPrimaryKey = !mSource->mPrimaryKey.isEmpty();
45   mRowNumber = 0;
46 
47   QStringList whereClauses;
48   bool useFallbackWhereClause = false;
49   QString fallbackWhereClause;
50   QString whereClause;
51 
52   if ( mRequest.destinationCrs().isValid() && mRequest.destinationCrs() != mSource->mCrs )
53   {
54     mTransform = QgsCoordinateTransform( mSource->mCrs, mRequest.destinationCrs(), mRequest.transformContext() );
55   }
56   try
57   {
58     mFilterRect = filterRectToSourceCrs( mTransform );
59   }
60   catch ( QgsCsException & )
61   {
62     // can't reproject mFilterRect
63     close();
64     return;
65   }
66 
67   //beware - limitAtProvider needs to be set to false if the request cannot be completely handled
68   //by the provider (e.g., utilising QGIS expression filters)
69   bool limitAtProvider = ( mRequest.limit() >= 0 );
70 
71   if ( !mFilterRect.isNull() && !mSource->mGeometryColumn.isNull() )
72   {
73     // some kind of MBR spatial filtering is required
74     whereClause = whereClauseRect();
75     if ( ! whereClause.isEmpty() )
76     {
77       whereClauses.append( whereClause );
78     }
79   }
80 
81   if ( !mSource->mSubsetString.isEmpty() )
82   {
83     whereClause = "( " + mSource->mSubsetString + ')';
84     if ( ! whereClause.isEmpty() )
85     {
86       whereClauses.append( whereClause );
87     }
88   }
89 
90   if ( request.filterType() == QgsFeatureRequest::FilterFid )
91   {
92     whereClause = whereClauseFid();
93     if ( ! whereClause.isEmpty() )
94     {
95       whereClauses.append( whereClause );
96     }
97   }
98   else if ( request.filterType() == QgsFeatureRequest::FilterFids )
99   {
100     if ( request.filterFids().isEmpty() )
101     {
102       close();
103     }
104     else
105     {
106       whereClauses.append( whereClauseFids() );
107     }
108   }
109   //IMPORTANT - this MUST be the last clause added!
110   else if ( request.filterType() == QgsFeatureRequest::FilterExpression )
111   {
112     // ensure that all attributes required for expression filter are being fetched
113     if ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes && request.filterType() == QgsFeatureRequest::FilterExpression )
114     {
115       QgsAttributeList attrs = request.subsetOfAttributes();
116       //ensure that all fields required for filter expressions are prepared
117       QSet<int> attributeIndexes = request.filterExpression()->referencedAttributeIndexes( mSource->mFields );
118       attributeIndexes += qgis::listToSet( attrs );
119       mRequest.setSubsetOfAttributes( qgis::setToList( attributeIndexes ) );
120     }
121     if ( request.filterExpression()->needsGeometry() )
122     {
123       mFetchGeometry = true;
124     }
125 
126     if ( QgsSettings().value( QStringLiteral( "qgis/compileExpressions" ), true ).toBool() )
127     {
128       QgsSQLiteExpressionCompiler compiler = QgsSQLiteExpressionCompiler( source->mFields );
129 
130       QgsSqlExpressionCompiler::Result result = compiler.compile( request.filterExpression() );
131 
132       if ( result == QgsSqlExpressionCompiler::Complete || result == QgsSqlExpressionCompiler::Partial )
133       {
134         whereClause = compiler.result();
135         if ( !whereClause.isEmpty() )
136         {
137           useFallbackWhereClause = true;
138           fallbackWhereClause = whereClauses.join( QLatin1String( " AND " ) );
139           whereClauses.append( whereClause );
140           //if only partial success when compiling expression, we need to double-check results using QGIS' expressions
141           mExpressionCompiled = ( result == QgsSqlExpressionCompiler::Complete );
142           mCompileStatus = ( mExpressionCompiled ? Compiled : PartiallyCompiled );
143         }
144       }
145       if ( result != QgsSqlExpressionCompiler::Complete )
146       {
147         //can't apply limit at provider side as we need to check all results using QGIS expressions
148         limitAtProvider = false;
149       }
150     }
151     else
152     {
153       limitAtProvider = false;
154     }
155   }
156 
157   if ( !mClosed )
158   {
159     whereClause = whereClauses.join( QLatin1String( " AND " ) );
160 
161     // Setup the order by
162     QStringList orderByParts;
163 
164     mOrderByCompiled = true;
165 
166     if ( QgsSettings().value( QStringLiteral( "qgis/compileExpressions" ), true ).toBool() )
167     {
168       const auto constOrderBy = request.orderBy();
169       for ( const QgsFeatureRequest::OrderByClause &clause : constOrderBy )
170       {
171         QgsSQLiteExpressionCompiler compiler = QgsSQLiteExpressionCompiler( source->mFields );
172         QgsExpression expression = clause.expression();
173         if ( compiler.compile( &expression ) == QgsSqlExpressionCompiler::Complete )
174         {
175           QString part;
176           part = compiler.result();
177 
178           if ( clause.nullsFirst() )
179             orderByParts << QStringLiteral( "%1 IS NOT NULL" ).arg( part );
180           else
181             orderByParts << QStringLiteral( "%1 IS NULL" ).arg( part );
182 
183           part += clause.ascending() ? " COLLATE NOCASE ASC" : " COLLATE NOCASE DESC";
184           orderByParts << part;
185         }
186         else
187         {
188           // Bail out on first non-complete compilation.
189           // Most important clauses at the beginning of the list
190           // will still be sent and used to pre-sort so the local
191           // CPU can use its cycles for fine-tuning.
192           mOrderByCompiled = false;
193           break;
194         }
195       }
196     }
197     else
198     {
199       mOrderByCompiled = false;
200     }
201 
202     if ( !mOrderByCompiled )
203       limitAtProvider = false;
204 
205     // also need attributes required by order by
206     if ( !mOrderByCompiled && mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes && !mRequest.orderBy().isEmpty() )
207     {
208       QSet<int> attributeIndexes;
209       const auto usedAttributeIndices = mRequest.orderBy().usedAttributeIndices( mSource->mFields );
210       for ( int attrIdx : usedAttributeIndices )
211       {
212         attributeIndexes << attrIdx;
213       }
214       attributeIndexes += qgis::listToSet( mRequest.subsetOfAttributes() );
215       mRequest.setSubsetOfAttributes( qgis::setToList( attributeIndexes ) );
216     }
217 
218     // preparing the SQL statement
219     bool success = prepareStatement( whereClause, limitAtProvider ? mRequest.limit() : -1, orderByParts.join( QLatin1Char( ',' ) ) );
220     if ( !success && useFallbackWhereClause )
221     {
222       //try with the fallback where clause, e.g., for cases when using compiled expression failed to prepare
223       mExpressionCompiled = false;
224       success = prepareStatement( fallbackWhereClause, -1, orderByParts.join( QLatin1Char( ',' ) ) );
225       mCompileFailed = true;
226     }
227 
228     if ( !success )
229     {
230       // some error occurred
231       sqliteStatement = nullptr;
232       close();
233     }
234   }
235 }
236 
~QgsSpatiaLiteFeatureIterator()237 QgsSpatiaLiteFeatureIterator::~QgsSpatiaLiteFeatureIterator()
238 {
239   close();
240 }
241 
242 
fetchFeature(QgsFeature & feature)243 bool QgsSpatiaLiteFeatureIterator::fetchFeature( QgsFeature &feature )
244 {
245   feature.setValid( false );
246 
247   if ( mClosed )
248     return false;
249 
250   if ( !sqliteStatement )
251   {
252     QgsDebugMsg( QStringLiteral( "Invalid current SQLite statement" ) );
253     close();
254     return false;
255   }
256 
257   if ( !getFeature( sqliteStatement, feature ) )
258   {
259     sqlite3_finalize( sqliteStatement );
260     sqliteStatement = nullptr;
261     close();
262     return false;
263   }
264 
265   feature.setValid( true );
266   geometryToDestinationCrs( feature, mTransform );
267   return true;
268 }
269 
nextFeatureFilterExpression(QgsFeature & f)270 bool QgsSpatiaLiteFeatureIterator::nextFeatureFilterExpression( QgsFeature &f )
271 {
272   if ( !mExpressionCompiled )
273     return QgsAbstractFeatureIterator::nextFeatureFilterExpression( f );
274   else
275     return fetchFeature( f );
276 }
277 
278 
rewind()279 bool QgsSpatiaLiteFeatureIterator::rewind()
280 {
281   if ( mClosed )
282     return false;
283 
284   if ( sqlite3_reset( sqliteStatement ) == SQLITE_OK )
285   {
286     mRowNumber = 0;
287     return true;
288   }
289   else
290   {
291     return false;
292   }
293 }
294 
close()295 bool QgsSpatiaLiteFeatureIterator::close()
296 {
297   if ( mClosed )
298     return false;
299 
300   iteratorClosed();
301 
302   mClosed = true;
303 
304   if ( !mSqliteHandle )
305   {
306     return false;
307   }
308 
309   if ( sqliteStatement )
310   {
311     sqlite3_finalize( sqliteStatement );
312     sqliteStatement = nullptr;
313   }
314 
315   if ( mHandle )
316   {
317     QgsSpatiaLiteConnPool::instance()->releaseConnection( mHandle );
318     mHandle = nullptr;
319   }
320 
321   mSqliteHandle = nullptr;
322   mClosed = true;
323   return true;
324 }
325 
326 
327 ////
328 
329 
prepareStatement(const QString & whereClause,long limit,const QString & orderBy)330 bool QgsSpatiaLiteFeatureIterator::prepareStatement( const QString &whereClause, long limit, const QString &orderBy )
331 {
332   if ( !mSqliteHandle )
333     return false;
334 
335   try
336   {
337     QString sql = QStringLiteral( "SELECT %1" ).arg( mHasPrimaryKey ? quotedPrimaryKey() : QStringLiteral( "0" ) );
338     int colIdx = 1; // column 0 is primary key
339 
340     if ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes )
341     {
342       QgsAttributeList fetchAttributes = mRequest.subsetOfAttributes();
343       for ( QgsAttributeList::const_iterator it = fetchAttributes.constBegin(); it != fetchAttributes.constEnd(); ++it )
344       {
345         sql += ',' + fieldName( mSource->mFields.field( *it ) );
346         colIdx++;
347       }
348     }
349     else
350     {
351       // fetch all attributes
352       for ( int idx = 0; idx < mSource->mFields.count(); ++idx )
353       {
354         sql += ',' + fieldName( mSource->mFields.at( idx ) );
355         colIdx++;
356       }
357     }
358 
359     if ( mFetchGeometry )
360     {
361       sql += QStringLiteral( ", AsBinary(%1)" ).arg( QgsSqliteUtils::quotedIdentifier( mSource->mGeometryColumn ) );
362       mGeomColIdx = colIdx;
363     }
364     sql += QStringLiteral( " FROM %1" ).arg( mSource->mQuery );
365 
366     if ( !whereClause.isEmpty() )
367       sql += QStringLiteral( " WHERE %1" ).arg( whereClause );
368 
369     if ( !orderBy.isEmpty() )
370       sql += QStringLiteral( " ORDER BY %1" ).arg( orderBy );
371 
372     if ( limit >= 0 )
373       sql += QStringLiteral( " LIMIT %1" ).arg( limit );
374 
375     QgsDebugMsgLevel( sql, 4 );
376 
377     if ( sqlite3_prepare_v2( mSqliteHandle, sql.toUtf8().constData(), -1, &sqliteStatement, nullptr ) != SQLITE_OK )
378     {
379       // some error occurred
380       QgsMessageLog::logMessage( QObject::tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( mSqliteHandle ) ), QObject::tr( "SpatiaLite" ) );
381       return false;
382     }
383   }
384   catch ( QgsSpatiaLiteProvider::SLFieldNotFound )
385   {
386     rewind();
387     return false;
388   }
389 
390   return true;
391 }
392 
quotedPrimaryKey()393 QString QgsSpatiaLiteFeatureIterator::quotedPrimaryKey()
394 {
395   return mSource->mPrimaryKey.isEmpty() ? QStringLiteral( "ROWID" ) : QgsSqliteUtils::quotedIdentifier( mSource->mPrimaryKey );
396 }
397 
whereClauseFid()398 QString QgsSpatiaLiteFeatureIterator::whereClauseFid()
399 {
400   return QStringLiteral( "%1=%2" ).arg( quotedPrimaryKey() ).arg( mRequest.filterFid() );
401 }
402 
whereClauseFids()403 QString QgsSpatiaLiteFeatureIterator::whereClauseFids()
404 {
405   if ( mRequest.filterFids().isEmpty() )
406     return QString();
407 
408   QString expr = QStringLiteral( "%1 IN (" ).arg( quotedPrimaryKey() ), delim;
409   const auto constFilterFids = mRequest.filterFids();
410   for ( const QgsFeatureId featureId : constFilterFids )
411   {
412     expr += delim + QString::number( featureId );
413     delim = ',';
414   }
415   expr += ')';
416   return expr;
417 }
418 
whereClauseRect()419 QString QgsSpatiaLiteFeatureIterator::whereClauseRect()
420 {
421   QString whereClause;
422 
423   if ( mRequest.flags() & QgsFeatureRequest::ExactIntersect )
424   {
425     // we are requested to evaluate a true INTERSECT relationship
426     whereClause += QStringLiteral( "Intersects(%1, BuildMbr(%2)) AND " ).arg( QgsSqliteUtils::quotedIdentifier( mSource->mGeometryColumn ), mbr( mFilterRect ) );
427   }
428   if ( mSource->mVShapeBased )
429   {
430     // handling a VirtualShape layer
431     whereClause += QStringLiteral( "MbrIntersects(%1, BuildMbr(%2))" ).arg( QgsSqliteUtils::quotedIdentifier( mSource->mGeometryColumn ), mbr( mFilterRect ) );
432   }
433   else if ( mFilterRect.isFinite() )
434   {
435     if ( mSource->mSpatialIndexRTree )
436     {
437       // using the RTree spatial index
438       QString mbrFilter = QStringLiteral( "xmin <= %1 AND " ).arg( qgsDoubleToString( mFilterRect.xMaximum() ) );
439       mbrFilter += QStringLiteral( "xmax >= %1 AND " ).arg( qgsDoubleToString( mFilterRect.xMinimum() ) );
440       mbrFilter += QStringLiteral( "ymin <= %1 AND " ).arg( qgsDoubleToString( mFilterRect.yMaximum() ) );
441       mbrFilter += QStringLiteral( "ymax >= %1" ).arg( qgsDoubleToString( mFilterRect.yMinimum() ) );
442       QString idxName = QStringLiteral( "idx_%1_%2" ).arg( mSource->mIndexTable, mSource->mIndexGeometry );
443       whereClause += QStringLiteral( "%1 IN (SELECT pkid FROM %2 WHERE %3)" )
444                      .arg( mSource->mViewBased ? quotedPrimaryKey() : QStringLiteral( "ROWID" ),
445                            QgsSqliteUtils::quotedIdentifier( idxName ),
446                            mbrFilter );
447     }
448     else if ( mSource->mSpatialIndexMbrCache )
449     {
450       // using the MbrCache spatial index
451       QString idxName = QStringLiteral( "cache_%1_%2" ).arg( mSource->mIndexTable, mSource->mIndexGeometry );
452       whereClause += QStringLiteral( "%1 IN (SELECT rowid FROM %2 WHERE mbr = FilterMbrIntersects(%3))" )
453                      .arg( mSource->mViewBased ? quotedPrimaryKey() : QStringLiteral( "ROWID" ),
454                            QgsSqliteUtils::quotedIdentifier( idxName ),
455                            mbr( mFilterRect ) );
456     }
457     else
458     {
459       // using simple MBR filtering
460       whereClause += QStringLiteral( "MbrIntersects(%1, BuildMbr(%2))" ).arg( QgsSqliteUtils::quotedIdentifier( mSource->mGeometryColumn ), mbr( mFilterRect ) );
461     }
462   }
463   else
464   {
465     whereClause = '1';
466   }
467   return whereClause;
468 }
469 
470 
mbr(const QgsRectangle & rect)471 QString QgsSpatiaLiteFeatureIterator::mbr( const QgsRectangle &rect )
472 {
473   return QStringLiteral( "%1, %2, %3, %4" )
474          .arg( qgsDoubleToString( rect.xMinimum() ),
475                qgsDoubleToString( rect.yMinimum() ),
476                qgsDoubleToString( rect.xMaximum() ),
477                qgsDoubleToString( rect.yMaximum() ) );
478 }
479 
480 
fieldName(const QgsField & fld)481 QString QgsSpatiaLiteFeatureIterator::fieldName( const QgsField &fld )
482 {
483   QString fieldname = QgsSqliteUtils::quotedIdentifier( fld.name() );
484   const QString type = fld.typeName().toLower();
485   if ( type.contains( QLatin1String( "geometry" ) ) || type.contains( QLatin1String( "point" ) ) ||
486        type.contains( QLatin1String( "line" ) ) || type.contains( QLatin1String( "polygon" ) ) )
487   {
488     fieldname = QStringLiteral( "AsText(%1)" ).arg( fieldname );
489   }
490   return fieldname;
491 }
492 
493 
getFeature(sqlite3_stmt * stmt,QgsFeature & feature)494 bool QgsSpatiaLiteFeatureIterator::getFeature( sqlite3_stmt *stmt, QgsFeature &feature )
495 {
496   bool subsetAttributes = mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes;
497 
498   int ret = sqlite3_step( stmt );
499   if ( ret == SQLITE_DONE )
500   {
501     // there are no more rows to fetch
502     return false;
503   }
504   if ( ret != SQLITE_ROW )
505   {
506     // some unexpected error occurred
507     QgsMessageLog::logMessage( QObject::tr( "SQLite error getting feature: %1" ).arg( QString::fromUtf8( sqlite3_errmsg( mSqliteHandle ) ) ), QObject::tr( "SpatiaLite" ) );
508     return false;
509   }
510 
511   // one valid row has been fetched from the result set
512   if ( !mFetchGeometry )
513   {
514     // no geometry was required
515     feature.clearGeometry();
516   }
517 
518   feature.initAttributes( mSource->mFields.count() );
519   feature.setFields( mSource->mFields ); // allow name-based attribute lookups
520 
521   int ic;
522   int n_columns = sqlite3_column_count( stmt );
523   for ( ic = 0; ic < n_columns; ic++ )
524   {
525     if ( ic == 0 )
526     {
527       if ( mHasPrimaryKey && sqlite3_column_type( stmt, ic ) == SQLITE_INTEGER )
528       {
529         // first column always contains the ROWID (or the primary key)
530         QgsFeatureId fid = sqlite3_column_int64( stmt, ic );
531         QgsDebugMsgLevel( QStringLiteral( "fid=%1" ).arg( fid ), 3 );
532         feature.setId( fid );
533       }
534       else
535       {
536         QgsDebugMsgLevel( QStringLiteral( "Primary key is not an integer field: setting autoincrement fid" ), 3 );
537         // autoincrement a row number
538         mRowNumber++;
539         feature.setId( mRowNumber );
540       }
541     }
542     else if ( mFetchGeometry && ic == mGeomColIdx )
543     {
544       getFeatureGeometry( stmt, ic, feature );
545     }
546     else
547     {
548       if ( subsetAttributes )
549       {
550         if ( ic <= mRequest.subsetOfAttributes().size() )
551         {
552           const int attrIndex = mRequest.subsetOfAttributes().at( ic - 1 );
553           const QgsField field = mSource->mFields.at( attrIndex );
554           feature.setAttribute( attrIndex, getFeatureAttribute( stmt, ic, field.type(), field.subType() ) );
555         }
556       }
557       else
558       {
559         const int attrIndex = ic - 1;
560         const QgsField field = mSource->mFields.at( attrIndex );
561         feature.setAttribute( attrIndex, getFeatureAttribute( stmt, ic, field.type(), field.subType() ) );
562       }
563     }
564   }
565 
566   return true;
567 }
568 
getFeatureAttribute(sqlite3_stmt * stmt,int ic,QVariant::Type type,QVariant::Type subType)569 QVariant QgsSpatiaLiteFeatureIterator::getFeatureAttribute( sqlite3_stmt *stmt, int ic, QVariant::Type type, QVariant::Type subType )
570 {
571   if ( sqlite3_column_type( stmt, ic ) == SQLITE_INTEGER )
572   {
573     if ( type == QVariant::Int )
574     {
575       // INTEGER value
576       return sqlite3_column_int( stmt, ic );
577     }
578     else
579     {
580       // INTEGER value
581       return ( qint64 ) sqlite3_column_int64( stmt, ic );
582     }
583   }
584 
585   if ( sqlite3_column_type( stmt, ic ) == SQLITE_FLOAT )
586   {
587     // DOUBLE value
588     return sqlite3_column_double( stmt, ic );
589   }
590 
591   if ( sqlite3_column_type( stmt, ic ) == SQLITE_BLOB )
592   {
593     // BLOB value
594     int blob_size = sqlite3_column_bytes( stmt, ic );
595     const char *blob = static_cast<const char *>( sqlite3_column_blob( stmt, ic ) );
596     return QByteArray( blob, blob_size );
597   }
598 
599   if ( sqlite3_column_type( stmt, ic ) == SQLITE_TEXT )
600   {
601     // TEXT value
602     const QString txt = QString::fromUtf8( ( const char * ) sqlite3_column_text( stmt, ic ) );
603     if ( type == QVariant::List || type == QVariant::StringList )
604     {
605       // assume arrays are stored as JSON
606       QVariant result = QVariant( QgsJsonUtils::parseArray( txt, subType ) );
607       if ( ! result.convert( static_cast<int>( type ) ) )
608       {
609         QgsDebugMsgLevel( QStringLiteral( "Could not convert JSON value to requested QVariant type" ).arg( txt ), 3 );
610       }
611       return result;
612     }
613     else if ( type == QVariant::DateTime )
614     {
615       // first use the GDAL date format
616       QDateTime dt = QDateTime::fromString( txt, Qt::ISODate );
617       if ( !dt.isValid() )
618       {
619         // if that fails, try SQLite's default date format
620         dt = QDateTime::fromString( txt, QStringLiteral( "yyyy-MM-dd hh:mm:ss" ) );
621       }
622 
623       return dt;
624     }
625     else if ( type == QVariant::Date )
626     {
627       return QDate::fromString( txt, QStringLiteral( "yyyy-MM-dd" ) );
628     }
629     return txt;
630   }
631 
632   // assuming NULL
633   return QVariant( type );
634 }
635 
getFeatureGeometry(sqlite3_stmt * stmt,int ic,QgsFeature & feature)636 void QgsSpatiaLiteFeatureIterator::getFeatureGeometry( sqlite3_stmt *stmt, int ic, QgsFeature &feature )
637 {
638   if ( sqlite3_column_type( stmt, ic ) == SQLITE_BLOB )
639   {
640     unsigned char *featureGeom = nullptr;
641     int geom_size = 0;
642     const void *blob = sqlite3_column_blob( stmt, ic );
643     int blob_size = sqlite3_column_bytes( stmt, ic );
644     QgsSpatiaLiteProvider::convertToGeosWKB( ( const unsigned char * )blob, blob_size, &featureGeom, &geom_size );
645     if ( featureGeom )
646     {
647       QgsGeometry g;
648       g.fromWkb( featureGeom, geom_size );
649       feature.setGeometry( g );
650     }
651     else
652       feature.clearGeometry();
653   }
654   else
655   {
656     // NULL geometry
657     feature.clearGeometry();
658   }
659 }
660 
prepareOrderBy(const QList<QgsFeatureRequest::OrderByClause> & orderBys)661 bool QgsSpatiaLiteFeatureIterator::prepareOrderBy( const QList<QgsFeatureRequest::OrderByClause> &orderBys )
662 {
663   Q_UNUSED( orderBys )
664   // Preparation has already been done in the constructor, so we just communicate the result
665   return mOrderByCompiled;
666 }
667 
668 
QgsSpatiaLiteFeatureSource(const QgsSpatiaLiteProvider * p)669 QgsSpatiaLiteFeatureSource::QgsSpatiaLiteFeatureSource( const QgsSpatiaLiteProvider *p )
670   : mGeometryColumn( p->mGeometryColumn )
671   , mSubsetString( p->mSubsetString )
672   , mFields( p->mAttributeFields )
673   , mQuery( p->mQuery )
674   , mIsQuery( p->mIsQuery )
675   , mViewBased( p->mViewBased )
676   , mVShapeBased( p->mVShapeBased )
677   , mIndexTable( p->mIndexTable )
678   , mIndexGeometry( p->mIndexGeometry )
679   , mPrimaryKey( p->mPrimaryKey )
680   , mSpatialIndexRTree( p->mSpatialIndexRTree )
681   , mSpatialIndexMbrCache( p->mSpatialIndexMbrCache )
682   , mSqlitePath( p->mSqlitePath )
683   , mCrs( p->crs() )
684   , mTransactionHandle( p->transaction() ? p->sqliteHandle() : nullptr )
685 {
686 }
687 
getFeatures(const QgsFeatureRequest & request)688 QgsFeatureIterator QgsSpatiaLiteFeatureSource::getFeatures( const QgsFeatureRequest &request )
689 {
690   return QgsFeatureIterator( new QgsSpatiaLiteFeatureIterator( this, false, request ) );
691 }
692 
transactionHandle()693 sqlite3 *QgsSpatiaLiteFeatureSource::transactionHandle()
694 {
695   return mTransactionHandle;
696 }
697