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