Skip to content

Commit

Permalink
Spatialite: respect provider default values
Browse files Browse the repository at this point in the history
Fixes #33383
  • Loading branch information
elpaso committed Jan 27, 2020
1 parent 05c6150 commit 13681cc
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 25 deletions.
74 changes: 49 additions & 25 deletions src/providers/spatialite/qgsspatialiteprovider.cpp
Expand Up @@ -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" ) );
Expand Down Expand Up @@ -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;
Expand All @@ -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<int> 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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions src/providers/spatialite/qgsspatialiteprovider.h
Expand Up @@ -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
Expand Down
54 changes: 54 additions & 0 deletions tests/src/python/test_provider_spatialite.py
Expand Up @@ -17,6 +17,7 @@
import sys
import shutil
import tempfile
from datetime import datetime

from qgis.core import (QgsProviderRegistry,
QgsVectorLayer,
Expand Down Expand Up @@ -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()

0 comments on commit 13681cc

Please sign in to comment.