Skip to content

Commit bd8d212

Browse files
committed
[FEATURE] Create attribute index support for spatialite provider
Allows creation of attribute indexes for spatialite layers
1 parent e927447 commit bd8d212

File tree

3 files changed

+113
-1
lines changed

3 files changed

+113
-1
lines changed

src/providers/spatialite/qgsspatialiteprovider.cpp

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,7 @@ QgsSpatiaLiteProvider::QgsSpatiaLiteProvider( QString const &uri )
538538
mEnabledCapabilities |= QgsVectorDataProvider::ChangeAttributeValues;
539539
mEnabledCapabilities |= QgsVectorDataProvider::AddFeatures;
540540
mEnabledCapabilities |= QgsVectorDataProvider::AddAttributes;
541+
mEnabledCapabilities |= QgsVectorDataProvider::CreateAttributeIndex;
541542
}
542543

543544
alreadyDone = false;
@@ -4007,6 +4008,71 @@ bool QgsSpatiaLiteProvider::addFeatures( QgsFeatureList & flist )
40074008
return ret == SQLITE_OK;
40084009
}
40094010

4011+
QString createIndexName( QString tableName, QString field )
4012+
{
4013+
QRegularExpression safeExp( QStringLiteral( "[^a-zA-Z0-9]" ) );
4014+
tableName.replace( safeExp, QStringLiteral( "_" ) );
4015+
field.replace( safeExp, QStringLiteral( "_" ) );
4016+
return QStringLiteral( "%1_%2_idx" ).arg( tableName, field );
4017+
}
4018+
4019+
bool QgsSpatiaLiteProvider::createAttributeIndex( int field )
4020+
{
4021+
char *errMsg = nullptr;
4022+
bool toCommit = false;
4023+
4024+
if ( field < 0 || field >= mAttributeFields.count() )
4025+
return false;
4026+
4027+
QString sql;
4028+
QString fieldName;
4029+
4030+
int ret = sqlite3_exec( mSqliteHandle, "BEGIN", nullptr, nullptr, &errMsg );
4031+
if ( ret != SQLITE_OK )
4032+
{
4033+
// some error occurred
4034+
goto abort;
4035+
}
4036+
toCommit = true;
4037+
4038+
fieldName = mAttributeFields.at( field ).name();
4039+
4040+
sql = QStringLiteral( "CREATE INDEX IF NOT EXISTS %1 ON \"%2\" (%3)" )
4041+
.arg( createIndexName( mTableName, fieldName ),
4042+
mTableName,
4043+
quotedIdentifier( fieldName ) );
4044+
ret = sqlite3_exec( mSqliteHandle, sql.toUtf8().constData(), nullptr, nullptr, &errMsg );
4045+
if ( ret != SQLITE_OK )
4046+
{
4047+
// some error occurred
4048+
goto abort;
4049+
}
4050+
4051+
ret = sqlite3_exec( mSqliteHandle, "COMMIT", nullptr, nullptr, &errMsg );
4052+
if ( ret != SQLITE_OK )
4053+
{
4054+
// some error occurred
4055+
goto abort;
4056+
}
4057+
4058+
return true;
4059+
4060+
abort:
4061+
pushError( tr( "SQLite error: %2\nSQL: %1" ).arg( sql, errMsg ? errMsg : tr( "unknown cause" ) ) );
4062+
if ( errMsg )
4063+
{
4064+
sqlite3_free( errMsg );
4065+
}
4066+
4067+
if ( toCommit )
4068+
{
4069+
// ROLLBACK after some previous error
4070+
( void )sqlite3_exec( mSqliteHandle, "ROLLBACK", nullptr, nullptr, nullptr );
4071+
}
4072+
4073+
return false;
4074+
}
4075+
40104076
bool QgsSpatiaLiteProvider::deleteFeatures( const QgsFeatureIds &id )
40114077
{
40124078
sqlite3_stmt *stmt = nullptr;

src/providers/spatialite/qgsspatialiteprovider.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ class QgsSpatiaLiteProvider: public QgsVectorDataProvider
170170

171171
QVariant defaultValue( int fieldId ) const override;
172172

173+
bool createAttributeIndex( int field ) override;
174+
173175
/** The SpatiaLite provider does its own transforms so we return
174176
* true for the following three functions to indicate that transforms
175177
* should not be handled by the QgsCoordinateTransform object. See the

tests/src/python/test_provider_spatialite.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,16 @@
1919
import shutil
2020
import tempfile
2121

22-
from qgis.core import QgsVectorLayer, QgsPoint, QgsFeature, QgsGeometry, QgsProject, QgsMapLayerRegistry, QgsField, QgsFieldConstraints, QgsVectorLayerUtils
22+
from qgis.core import (QgsVectorLayer,
23+
QgsVectorDataProvider,
24+
QgsPoint,
25+
QgsFeature,
26+
QgsGeometry,
27+
QgsProject,
28+
QgsMapLayerRegistry,
29+
QgsField,
30+
QgsFieldConstraints,
31+
QgsVectorLayerUtils)
2332

2433
from qgis.testing import start_app, unittest
2534
from utilities import unitTestDataPath
@@ -483,5 +492,40 @@ def testVectorLayerUtilsCreateFeatureWithProviderDefaultLiteral(self):
483492
f = QgsVectorLayerUtils.createFeature(vl, attributes={1: 'qgis is great', 0: 3})
484493
self.assertEqual(f.attributes(), [3, "qgis 'is good", 5, 12, None])
485494

495+
def testCreateAttributeIndex(self):
496+
vl = QgsVectorLayer("dbname=%s table='test_defaults' key='id'" % self.dbname, "test_defaults", "spatialite")
497+
self.assertTrue(vl.dataProvider().capabilities() & QgsVectorDataProvider.CreateAttributeIndex)
498+
self.assertFalse(vl.dataProvider().createAttributeIndex(-1))
499+
self.assertFalse(vl.dataProvider().createAttributeIndex(100))
500+
self.assertTrue(vl.dataProvider().createAttributeIndex(1))
501+
502+
con = spatialite_connect(self.dbname, isolation_level=None)
503+
cur = con.cursor()
504+
rs = cur.execute("SELECT * FROM sqlite_master WHERE type='index' AND tbl_name='test_defaults'")
505+
res = [row for row in rs]
506+
self.assertEqual(len(res), 1)
507+
index_name = res[0][1]
508+
rs = cur.execute("PRAGMA index_info({})".format(index_name))
509+
res = [row for row in rs]
510+
self.assertEqual(len(res), 1)
511+
self.assertEqual(res[0][2], 'name')
512+
513+
# second index
514+
self.assertTrue(vl.dataProvider().createAttributeIndex(2))
515+
rs = cur.execute("SELECT * FROM sqlite_master WHERE type='index' AND tbl_name='test_defaults'")
516+
res = [row for row in rs]
517+
self.assertEqual(len(res), 2)
518+
indexed_columns = []
519+
for row in res:
520+
index_name = row[1]
521+
rs = cur.execute("PRAGMA index_info({})".format(index_name))
522+
res = [row for row in rs]
523+
self.assertEqual(len(res), 1)
524+
indexed_columns.append(res[0][2])
525+
526+
self.assertEqual(set(indexed_columns), set(['name', 'number']))
527+
con.close()
528+
529+
486530
if __name__ == '__main__':
487531
unittest.main()

0 commit comments

Comments
 (0)