diff --git a/src/providers/spatialite/qgsspatialiteprovider.cpp b/src/providers/spatialite/qgsspatialiteprovider.cpp index 4ba2c140bb34..32bcc268806c 100644 --- a/src/providers/spatialite/qgsspatialiteprovider.cpp +++ b/src/providers/spatialite/qgsspatialiteprovider.cpp @@ -983,6 +983,11 @@ void QgsSpatiaLiteProvider::insertDefaultValue( int fieldIndex, QString defaultV } } +QString QgsSpatiaLiteProvider::defaultValueClause( int fieldIndex ) const +{ + return mDefaultValues.value( fieldIndex, QString() ).toString(); +} + void QgsSpatiaLiteProvider::handleError( const QString &sql, char *errorMessage, bool rollback ) { QgsMessageLog::logMessage( tr( "SQLite error: %2\nSQL: %1" ).arg( sql, errorMessage ? errorMessage : tr( "unknown cause" ) ), tr( "SpatiaLite" ) ); @@ -3957,10 +3962,11 @@ bool QgsSpatiaLiteProvider::addFeatures( QgsFeatureList &flist, Flags flags ) sqlite3_stmt *stmt = nullptr; char *errMsg = nullptr; bool toCommit = false; - QString sql; QString values; QString separator; int ia, ret; + // SQL for single row + QString sql; if ( flist.isEmpty() ) return true; @@ -3971,46 +3977,57 @@ bool QgsSpatiaLiteProvider::addFeatures( QgsFeatureList &flist, Flags flags ) { toCommit = true; - sql = QStringLiteral( "INSERT INTO %1(" ).arg( QgsSqliteUtils::quotedIdentifier( mTableName ) ); + QString baseSql { QStringLiteral( "INSERT INTO %1(" ).arg( QgsSqliteUtils::quotedIdentifier( mTableName ) ) }; values = QStringLiteral( ") VALUES (" ); separator.clear(); if ( !mGeometryColumn.isEmpty() ) { - sql += separator + QgsSqliteUtils::quotedIdentifier( mGeometryColumn ); + baseSql += separator + QgsSqliteUtils::quotedIdentifier( mGeometryColumn ); values += separator + geomParam(); separator = ','; } - for ( int i = 0; i < attributevec.count(); ++i ) + for ( QgsFeatureList::iterator feature = flist.begin(); feature != flist.end(); ++feature ) { - if ( i >= mAttributeFields.count() ) - continue; - QString fieldname = mAttributeFields.at( i ).name(); - if ( fieldname.isEmpty() || fieldname == mGeometryColumn ) - continue; + sql = baseSql; - sql += separator + QgsSqliteUtils::quotedIdentifier( fieldname ); - values += separator + '?'; - separator = ','; - } + // looping on each feature to insert + QgsAttributes attributevec = feature->attributes(); - sql += values; - sql += ')'; + // Default indexes (to be skipped) + QList defaultIndexes; - // SQLite prepared statement - ret = sqlite3_prepare_v2( mSqliteHandle, sql.toUtf8().constData(), -1, &stmt, nullptr ); - if ( ret == SQLITE_OK ) - { - for ( QgsFeatureList::iterator feature = flist.begin(); feature != flist.end(); ++feature ) + for ( int i = 0; i < attributevec.count(); ++i ) { - // looping on each feature to insert - QgsAttributes attributevec = feature->attributes(); + if ( mDefaultValues.contains( i ) && defaultValue( i ) == attributevec.at( i ).toString() ) + { + defaultIndexes.push_back( i ); + continue; + } - // resetting Prepared Statement and bindings - sqlite3_reset( stmt ); - sqlite3_clear_bindings( stmt ); + if ( i >= mAttributeFields.count() ) + continue; + + QString fieldname = mAttributeFields.at( i ).name(); + if ( fieldname.isEmpty() || fieldname == mGeometryColumn ) + { + continue; + } + + sql += separator + QgsSqliteUtils::quotedIdentifier( fieldname ); + values += separator + '?'; + separator = ','; + } + + sql += values; + sql += ')'; + + // SQLite prepared statement + ret = sqlite3_prepare_v2( mSqliteHandle, sql.toUtf8().constData(), -1, &stmt, nullptr ); + if ( ret == SQLITE_OK ) + { // initializing the column counter ia = 0; @@ -4039,6 +4056,11 @@ bool QgsSpatiaLiteProvider::addFeatures( QgsFeatureList &flist, Flags flags ) for ( int i = 0; i < attributevec.count(); ++i ) { + if ( defaultIndexes.contains( i ) ) + { + continue; + } + QVariant v = attributevec.at( i ); // binding values for each attribute @@ -4103,6 +4125,8 @@ bool QgsSpatiaLiteProvider::addFeatures( QgsFeatureList &flist, Flags flags ) // performing actual row insert ret = sqlite3_step( stmt ); + qDebug() << sqlite3_expanded_sql( stmt ); + if ( ret == SQLITE_DONE || ret == SQLITE_ROW ) { // update feature id diff --git a/src/providers/spatialite/qgsspatialiteprovider.h b/src/providers/spatialite/qgsspatialiteprovider.h index ede7ae64212a..05f8e0e2b524 100644 --- a/src/providers/spatialite/qgsspatialiteprovider.h +++ b/src/providers/spatialite/qgsspatialiteprovider.h @@ -382,6 +382,9 @@ class QgsSpatiaLiteProvider: public QgsVectorDataProvider friend class QgsSpatiaLiteFeatureSource; + // QgsVectorDataProvider interface + public: + virtual QString defaultValueClause( int fieldIndex ) const override; }; class QgsSpatiaLiteProviderMetadata: public QgsProviderMetadata diff --git a/tests/src/python/test_provider_spatialite.py b/tests/src/python/test_provider_spatialite.py index a41484af24a9..6c1d918f0a48 100644 --- a/tests/src/python/test_provider_spatialite.py +++ b/tests/src/python/test_provider_spatialite.py @@ -17,6 +17,7 @@ import sys import shutil import tempfile +from datetime import datetime from qgis.core import (QgsProviderRegistry, QgsVectorLayer, @@ -1159,6 +1160,59 @@ def testBigint(self): self.assertEqual(l.uniqueValues(1), {1, 2}) self.assertEqual(l.uniqueValues(0), {987654321012345, 987654321012346}) + def testSpatialiteDefaultValues(self): + """Test wether in spatialite table with default values like CURRENT_TIMESTAMP or + (datetime('now','localtime')) they are respected. See GH #33383""" + + # Create the test table + + dbname = os.path.join(tempfile.gettempdir(), "test.sqlite") + if os.path.exists(dbname): + os.remove(dbname) + con = spatialite_connect(dbname, isolation_level=None) + cur = con.cursor() + cur.execute("BEGIN") + sql = "SELECT InitSpatialMetadata()" + cur.execute(sql) + + # simple table with primary key + sql = """ + CREATE TABLE test_table ( + id integer primary key autoincrement, + comment text, + created_at_01 text DEFAULT (datetime('now','localtime')), + created_at_02 text DEFAULT CURRENT_TIMESTAMP, + anumber INTEGER DEFAULT 123 + ) + """ + cur.execute(sql) + cur.execute("COMMIT") + con.close() + + vl = QgsVectorLayer("dbname='%s' table='test_table'" % dbname, 'test_table', 'spatialite') + self.assertTrue(vl.isValid()) + feature = QgsFeature(vl.fields()) + for idx in range(vl.fields().count()): + default = vl.dataProvider().defaultValueClause(idx) + if default != '': + feature.setAttribute(idx, default) + else: + feature.setAttribute(idx, 'A comment') + + # Save it for the test + now = datetime.now() + + self.assertTrue(vl.dataProvider().addFeature(feature)) + del(vl) + + # Verify + vl2 = QgsVectorLayer("dbname='%s' table='test_table'" % dbname, 'test_table', 'spatialite') + self.assertTrue(vl2.isValid()) + feature = next(vl2.getFeatures()) + self.assertTrue(feature.attribute(2).startswith(now.strftime('%Y-%m-%d'))) + self.assertTrue(feature.attribute(3).startswith(now.strftime('%Y-%m-%d'))) + self.assertEqual(feature.attribute(4), 123) + if __name__ == '__main__': unittest.main()