Skip to content

Commit

Permalink
[spatialite provider] auto-generate auto-incrementing primary keys (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
nirvn committed Dec 13, 2017
1 parent 7a1a4ed commit 73674e8
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 26 deletions.
100 changes: 75 additions & 25 deletions src/providers/spatialite/qgsspatialiteprovider.cpp
Expand Up @@ -582,7 +582,7 @@ QgsSpatiaLiteProvider::QgsSpatiaLiteProvider( QString const &uri )
return;
}

if ( mTableBased && hasRowid() )
if ( mTableBased && hasRowid() && mPrimaryKey.isEmpty() )
{
mPrimaryKey = QStringLiteral( "ROWID" );
}
Expand Down Expand Up @@ -860,39 +860,65 @@ void QgsSpatiaLiteProvider::fetchConstraints()

Q_FOREACH ( int fieldIdx, mPrimaryKeyAttrs )
{
//primary keys are unique, not null
QgsFieldConstraints constraints = mAttributeFields.at( fieldIdx ).constraints();
constraints.setConstraint( QgsFieldConstraints::ConstraintUnique, QgsFieldConstraints::ConstraintOriginProvider );
constraints.setConstraint( QgsFieldConstraints::ConstraintNotNull, QgsFieldConstraints::ConstraintOriginProvider );
mAttributeFields[ fieldIdx ].setConstraints( constraints );

if ( mAttributeFields[ fieldIdx ].name() == mPrimaryKey )
{
QString sql = QStringLiteral( "SELECT sql FROM sqlite_master WHERE tbl_name=%1" ).arg( quotedIdentifier( mTableName ) );
int ret = sqlite3_get_table( mSqliteHandle, sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
if ( ret != SQLITE_OK )
{
handleError( sql, errMsg );
return;
}

if ( rows >= 1 )
{
QString tableSql = QString::fromUtf8( results[ 1 ] );
QRegularExpression rx( QStringLiteral( "[(,]\\s*%1\\s+INTEGER PRIMARY KEY AUTOINCREMENT" ).arg( mPrimaryKey ), QRegularExpression::CaseInsensitiveOption );
if ( tableSql.contains( rx ) )
{
mPrimaryKeyAutoIncrement = true;
insertDefaultValue( fieldIdx, tr( "Autogenerate" ) );
}
}
sqlite3_free_table( results );
}
}
}

void QgsSpatiaLiteProvider::insertDefaultValue( int fieldIndex, QString defaultVal )
{
if ( !defaultVal.isEmpty() )
{
QVariant defaultVariant;
switch ( mAttributeFields.at( fieldIndex ).type() )
QVariant defaultVariant = defaultVal;

if ( mAttributeFields.at( fieldIndex ).name() != mPrimaryKey || ( mAttributeFields.at( fieldIndex ).name() == mPrimaryKey && !mPrimaryKeyAutoIncrement ) )
{
case QVariant::LongLong:
defaultVariant = defaultVal.toLongLong();
break;
switch ( mAttributeFields.at( fieldIndex ).type() )
{
case QVariant::LongLong:
defaultVariant = defaultVal.toLongLong();
break;

case QVariant::Double:
defaultVariant = defaultVal.toDouble();
break;
case QVariant::Double:
defaultVariant = defaultVal.toDouble();
break;

default:
{
if ( defaultVal.startsWith( '\'' ) )
defaultVal = defaultVal.remove( 0, 1 );
if ( defaultVal.endsWith( '\'' ) )
defaultVal.chop( 1 );
defaultVal.replace( QLatin1String( "''" ), QLatin1String( "'" ) );
default:
{
if ( defaultVal.startsWith( '\'' ) )
defaultVal = defaultVal.remove( 0, 1 );
if ( defaultVal.endsWith( '\'' ) )
defaultVal.chop( 1 );
defaultVal.replace( QLatin1String( "''" ), QLatin1String( "'" ) );

defaultVariant = defaultVal;
break;
defaultVariant = defaultVal;
break;
}
}
}
mDefaultValues.insert( fieldIndex, defaultVariant );
Expand Down Expand Up @@ -978,6 +1004,12 @@ void QgsSpatiaLiteProvider::loadFields()
}
sqlite3_free_table( results );

if ( pkCount == 1 )
{
// setting the Primary Key column name
mPrimaryKey = pkName;
}

// check for constraints
fetchConstraints();

Expand All @@ -986,7 +1018,6 @@ void QgsSpatiaLiteProvider::loadFields()
{
determineViewPrimaryKey();
}

}
else
{
Expand Down Expand Up @@ -1037,12 +1068,12 @@ void QgsSpatiaLiteProvider::loadFields()
}
}
sqlite3_finalize( stmt );
}

if ( pkCount == 1 )
{
// setting the Primary Key column name
mPrimaryKey = pkName;
if ( pkCount == 1 )
{
// setting the Primary Key column name
mPrimaryKey = pkName;
}
}

updatePrimaryKeyCapabilities();
Expand Down Expand Up @@ -3932,6 +3963,11 @@ bool QgsSpatiaLiteProvider::addFeatures( QgsFeatureList &flist, Flags flags )
{
++ia;
}
else if ( fieldname == mPrimaryKey && mPrimaryKeyAutoIncrement && v == QVariant( tr( "Autogenerate" ) ) )
{
// use auto-generated value if user hasn't specified a unique value
++ia;
}
else if ( v.isNull() )
{
// binding a NULL value
Expand Down Expand Up @@ -4379,6 +4415,20 @@ QVariant QgsSpatiaLiteProvider::defaultValue( int fieldId ) const
return mDefaultValues.value( fieldId, QVariant() );
}

bool QgsSpatiaLiteProvider::skipConstraintCheck( int fieldIndex, QgsFieldConstraints::Constraint constraint, const QVariant &value ) const
{
Q_UNUSED( constraint );

// If the field is the primary key, skip in case it's autog-enerated / auto-incrementing
if ( mAttributeFields.at( fieldIndex ).name() == mPrimaryKey && mPrimaryKeyAutoIncrement )
{
const QVariant defVal = mDefaultValues.value( fieldIndex );
return defVal.toInt() == value.toInt();
}

return false;
}

void QgsSpatiaLiteProvider::closeDb()
{
// trying to close the SQLite DB
Expand Down
4 changes: 4 additions & 0 deletions src/providers/spatialite/qgsspatialiteprovider.h
Expand Up @@ -113,6 +113,7 @@ class QgsSpatiaLiteProvider: public QgsVectorDataProvider
bool changeGeometryValues( const QgsGeometryMap &geometry_map ) override;
QgsVectorDataProvider::Capabilities capabilities() const override;
QVariant defaultValue( int fieldId ) const override;
virtual bool skipConstraintCheck( int fieldIndex, QgsFieldConstraints::Constraint constraint, const QVariant &value = QVariant() ) const override;
bool createAttributeIndex( int field ) override;

/**
Expand Down Expand Up @@ -250,6 +251,9 @@ class QgsSpatiaLiteProvider: public QgsVectorDataProvider
//! Name of the primary key column in the table
QString mPrimaryKey;

//! Flag indicating whether the primary key is auto-generated
bool mPrimaryKeyAutoIncrement = false;

//! List of primary key columns in the table
QgsAttributeList mPrimaryKeyAttrs;

Expand Down
16 changes: 15 additions & 1 deletion tests/src/python/test_provider_spatialite.py
Expand Up @@ -34,7 +34,7 @@
from qgis.testing import start_app, unittest
from utilities import unitTestDataPath
from providertestbase import ProviderTestCase
from qgis.PyQt.QtCore import QVariant
from qgis.PyQt.QtCore import QObject, QVariant

from qgis.utils import spatialite_connect

Expand Down Expand Up @@ -173,6 +173,12 @@ def setUpClass(cls):
sql = "SELECT AddGeometryColumn('test_relation_b', 'Geometry', 4326, 'POLYGON', 'XY')"
cur.execute(sql)

# table to test auto increment
sql = "CREATE TABLE test_autoincrement(id INTEGER PRIMARY KEY AUTOINCREMENT, num INTEGER);"
cur.execute(sql)
sql = "INSERT INTO test_autoincrement (num) VALUES (123);"
cur.execute(sql)

# tables with constraints
sql = "CREATE TABLE test_constraints(id INTEGER PRIMARY KEY, num INTEGER NOT NULL, desc TEXT UNIQUE, desc2 TEXT, num2 INTEGER NOT NULL UNIQUE)"
cur.execute(sql)
Expand Down Expand Up @@ -568,6 +574,14 @@ def testUniqueConstraint(self):
self.assertTrue(fields.at(4).constraints().constraints() & QgsFieldConstraints.ConstraintUnique)
self.assertEqual(fields.at(4).constraints().constraintOrigin(QgsFieldConstraints.ConstraintUnique), QgsFieldConstraints.ConstraintOriginProvider)

def testSkipConstraintCheck(self):
vl = QgsVectorLayer("dbname=%s table=test_autoincrement" % self.dbname, "test_autoincrement",
"spatialite")
self.assertTrue(vl.isValid())

self.assertTrue(vl.dataProvider().skipConstraintCheck(0, QgsFieldConstraints.ConstraintUnique, str("Autogenerate")))
self.assertFalse(vl.dataProvider().skipConstraintCheck(0, QgsFieldConstraints.ConstraintUnique, 123))

# This test would fail. It would require turning on WAL
def XXXXXtestLocking(self):

Expand Down

0 comments on commit 73674e8

Please sign in to comment.