@@ -1123,6 +1123,34 @@ void QgsSpatiaLiteProvider::determineViewPrimaryKey()
1123
1123
}
1124
1124
}
1125
1125
1126
+ QList<QString> QgsSpatiaLiteProvider::tablePrimaryKeys ( const QString tableName ) const
1127
+ {
1128
+ QList<QString> result;
1129
+ const QString sql = QStringLiteral ( " PRAGMA table_info(%1)" ).arg ( QgsSpatiaLiteProvider::quotedIdentifier ( tableName ) );
1130
+ char **results = nullptr ;
1131
+ int rows;
1132
+ int columns;
1133
+ char *errMsg = nullptr ;
1134
+ int ret = sqlite3_get_table ( mSqliteHandle , sql.toUtf8 ().constData (), &results, &rows, &columns, &errMsg );
1135
+ if ( ret == SQLITE_OK )
1136
+ {
1137
+ for ( int row = 1 ; row <= rows; ++row )
1138
+ {
1139
+ if ( QString::fromUtf8 ( results[row * columns + 5 ] ) == QChar ( ' 1' ) )
1140
+ {
1141
+ result << QString::fromUtf8 ( results[row * columns + 1 ] );
1142
+ }
1143
+ }
1144
+ sqlite3_free_table ( results );
1145
+ }
1146
+ else
1147
+ {
1148
+ QgsLogger::warning ( QStringLiteral ( " SQLite error discovering relations: %1" ).arg ( errMsg ) );
1149
+ sqlite3_free ( errMsg );
1150
+ }
1151
+ return result;
1152
+ }
1153
+
1126
1154
1127
1155
bool QgsSpatiaLiteProvider::hasTriggers ()
1128
1156
{
@@ -4557,8 +4585,6 @@ bool QgsSpatiaLiteProvider::checkLayerType()
4557
4585
}
4558
4586
else if ( mQuery .startsWith ( ' (' ) && mQuery .endsWith ( ' )' ) )
4559
4587
{
4560
- // checking if this one is a select query
4561
-
4562
4588
// get a new alias for the subquery
4563
4589
int index = 0 ;
4564
4590
QString alias;
@@ -4579,61 +4605,117 @@ bool QgsSpatiaLiteProvider::checkLayerType()
4579
4605
4580
4606
sql = QStringLiteral ( " SELECT 0, %1 FROM %2 LIMIT 1" ).arg ( quotedIdentifier ( mGeometryColumn ), mQuery );
4581
4607
ret = sqlite3_get_table ( mSqliteHandle , sql.toUtf8 ().constData (), &results, &rows, &columns, &errMsg );
4608
+
4609
+ // Try to find a PK or try to use ROWID
4582
4610
if ( ret == SQLITE_OK && rows == 1 )
4583
4611
{
4584
- // Check if we can get use the ROWID from the table that provides the geometry
4585
4612
sqlite3_stmt *stmt = nullptr ;
4586
- // ! String containing the name of the table that provides the geometry if the layer data source is based on a query
4587
- QString queryGeomTableName;
4613
+
4588
4614
// 1. find the table that provides geometry
4615
+ // String containing the name of the table that provides the geometry if the layer data source is based on a query
4616
+ QString queryGeomTableName;
4589
4617
if ( sqlite3_prepare_v2 ( mSqliteHandle , sql.toUtf8 ().constData (), -1 , &stmt, nullptr ) == SQLITE_OK )
4590
4618
{
4591
4619
queryGeomTableName = sqlite3_column_table_name ( stmt, 1 );
4592
4620
}
4593
- // 2. check if the table has a usable ROWID
4621
+
4622
+ // 3. Find pks
4623
+ QList<QString> pks;
4594
4624
if ( ! queryGeomTableName.isEmpty () )
4595
4625
{
4596
- sql = QStringLiteral ( " SELECT ROWID FROM %1 WHERE ROWID IS NOT NULL LIMIT 1" ).arg ( quotedIdentifier ( queryGeomTableName ) );
4597
- ret = sqlite3_get_table ( mSqliteHandle , sql.toUtf8 ().constData (), &results, &rows, &columns, &errMsg );
4598
- if ( ret != SQLITE_OK || rows != 1 )
4599
- {
4600
- queryGeomTableName = QString ();
4601
- }
4626
+ pks = tablePrimaryKeys ( queryGeomTableName );
4602
4627
}
4603
- // 3. check if ROWID injection works
4628
+
4629
+ // find table alias if any
4630
+ QString tableAlias;
4604
4631
if ( ! queryGeomTableName.isEmpty () )
4605
4632
{
4606
- // Check if the whole sql is aliased (I couldn't find a sqlite API call to get this information)
4607
- QRegularExpression re { R"re( \s+AS\s+(\w+)\n?\)?$)re" };
4633
+ // Try first with single table alias
4634
+ // (I couldn't find a sqlite API call to get this information)
4635
+ QRegularExpression re { QStringLiteral ( R"re( "?%1"?\s+AS\s+(\w+))re" ).arg ( queryGeomTableName ) };
4608
4636
re.setPatternOptions ( QRegularExpression::PatternOption::MultilineOption |
4609
4637
QRegularExpression::PatternOption::CaseInsensitiveOption );
4610
4638
QRegularExpressionMatch match { re.match ( mTableName ) };
4611
- regex.setPattern ( QStringLiteral ( R"re( \s+AS\s+(\w+)\n?\)?$)re" ) );
4612
- QString tableAlias;
4613
4639
if ( match.hasMatch () )
4614
4640
{
4615
4641
tableAlias = match.captured ( 1 );
4616
4642
}
4617
- QString newSql ( mQuery .replace ( QStringLiteral ( " SELECT " ),
4618
- QStringLiteral ( " SELECT %1.%2, " )
4619
- .arg ( quotedIdentifier ( tableAlias.isEmpty () ? queryGeomTableName : tableAlias ),
4620
- QStringLiteral ( " ROWID" ) ),
4621
- Qt::CaseInsensitive ) );
4622
- sql = QStringLiteral ( " SELECT ROWID FROM %1 WHERE ROWID IS NOT NULL LIMIT 1" ).arg ( newSql );
4643
+ // Check if the whole sql is aliased i.e. '(SELECT * FROM \\"somedata\\" as my_alias\n)'
4644
+ if ( tableAlias.isEmpty () )
4645
+ {
4646
+ regex.setPattern ( QStringLiteral ( R"re( \s+AS\s+(\w+)\n?\)?$)re" ) );
4647
+ match = re.match ( mTableName );
4648
+ if ( match.hasMatch () )
4649
+ {
4650
+ tableAlias = match.captured ( 1 );
4651
+ }
4652
+ }
4653
+ }
4654
+
4655
+ const QString tableIdentifier { tableAlias.isEmpty () ? queryGeomTableName : tableAlias };
4656
+ QRegularExpression injectionRe { QStringLiteral ( R"re( SELECT\s([^\(]+?FROM\s+"?%1"?))re" ).arg ( tableIdentifier ) };
4657
+ injectionRe.setPatternOptions ( QRegularExpression::PatternOption::MultilineOption |
4658
+ QRegularExpression::PatternOption::CaseInsensitiveOption );
4659
+
4660
+
4661
+ if ( ! pks.isEmpty () )
4662
+ {
4663
+ if ( pks.length () > 1 )
4664
+ {
4665
+ QgsMessageLog::logMessage ( tr ( " SQLite composite keys are not supported in query layer, using the first component only. %1" )
4666
+ .arg ( sql ), tr ( " SpatiaLite" ), Qgis::MessageLevel::Warning );
4667
+ }
4668
+
4669
+ QString pk { QStringLiteral ( " %1.%2" ).arg ( quotedIdentifier ( alias ) ).arg ( pks.first () ) };
4670
+ QString newSql ( mQuery .replace ( injectionRe,
4671
+ QStringLiteral ( R"re( SELECT %1.%2, \1)re" )
4672
+ .arg ( quotedIdentifier ( tableIdentifier ) )
4673
+ .arg ( pks.first () ) ) );
4674
+ sql = QStringLiteral ( " SELECT %1 FROM %2 LIMIT 1" ).arg ( pk ).arg ( newSql );
4623
4675
ret = sqlite3_get_table ( mSqliteHandle , sql.toUtf8 ().constData (), &results, &rows, &columns, &errMsg );
4624
4676
if ( ret == SQLITE_OK && rows == 1 )
4625
4677
{
4626
4678
mQuery = newSql;
4627
- mPrimaryKey = QStringLiteral ( " ROWID" );
4628
- mRowidInjectedInQuery = true ;
4679
+ mPrimaryKey = pks.first ( );
4629
4680
}
4630
4681
}
4631
- // 4. if it does not work, simply clear the message and fallback to the original behavior
4632
- if ( errMsg )
4682
+
4683
+ // If there is still no primary key, check if we can get use the ROWID from the table that provides the geometry
4684
+ if ( mPrimaryKey .isEmpty () )
4633
4685
{
4634
- QgsMessageLog::logMessage ( tr ( " SQLite error while trying to inject ROWID: %2\n SQL: %1" ).arg ( sql, errMsg ), tr ( " SpatiaLite" ) );
4635
- sqlite3_free ( errMsg );
4636
- errMsg = nullptr ;
4686
+ // 4. check if the table has a usable ROWID
4687
+ if ( ! queryGeomTableName.isEmpty () )
4688
+ {
4689
+ sql = QStringLiteral ( " SELECT ROWID FROM %1 WHERE ROWID IS NOT NULL LIMIT 1" ).arg ( quotedIdentifier ( queryGeomTableName ) );
4690
+ ret = sqlite3_get_table ( mSqliteHandle , sql.toUtf8 ().constData (), &results, &rows, &columns, &errMsg );
4691
+ if ( ret != SQLITE_OK || rows != 1 )
4692
+ {
4693
+ queryGeomTableName = QString ();
4694
+ }
4695
+ }
4696
+ // 5. check if ROWID injection works
4697
+ if ( ! queryGeomTableName.isEmpty () )
4698
+ {
4699
+ const QString newSql ( mQuery .replace ( injectionRe,
4700
+ QStringLiteral ( R"re( SELECT %1.%2, \1)re" )
4701
+ .arg ( quotedIdentifier ( tableIdentifier ),
4702
+ QStringLiteral ( " ROWID" ) ) ) );
4703
+ sql = QStringLiteral ( " SELECT ROWID FROM %1 WHERE ROWID IS NOT NULL LIMIT 1" ).arg ( newSql );
4704
+ ret = sqlite3_get_table ( mSqliteHandle , sql.toUtf8 ().constData (), &results, &rows, &columns, &errMsg );
4705
+ if ( ret == SQLITE_OK && rows == 1 )
4706
+ {
4707
+ mQuery = newSql;
4708
+ mPrimaryKey = QStringLiteral ( " ROWID" );
4709
+ mRowidInjectedInQuery = true ;
4710
+ }
4711
+ }
4712
+ // 6. if it does not work, simply clear the message and fallback to the original behavior
4713
+ if ( errMsg )
4714
+ {
4715
+ QgsMessageLog::logMessage ( tr ( " SQLite error while trying to inject ROWID: %2\n SQL: %1" ).arg ( sql, errMsg ), tr ( " SpatiaLite" ) );
4716
+ sqlite3_free ( errMsg );
4717
+ errMsg = nullptr ;
4718
+ }
4637
4719
}
4638
4720
sqlite3_finalize ( stmt );
4639
4721
mIsQuery = true ;
0 commit comments