Skip to content
Permalink
Browse files

New expression function sqlite_fetch_and_increment [FEATURE]

SQlite default values can only be applied on insert and not prefetched.
This makes it impossible to acquire an incremented primary key via AUTO_INCREMENT before creating the row in the database. Sidenote: with postgres, this works via the option "evaluate default values".

When adding new features with relations, it's really nice to be able to already add children for a parent, while the parents form is still open and hence the parent feature uncommitted.
To get around this limitation, this function can be used to manage an incrementing value (or multiple sequences) in a separate table on sqlite based formats like gpkg.
  • Loading branch information
m-kuhn committed Dec 20, 2018
1 parent 3dd3609 commit fea58316a42e57c4a20c4b4c657acc4934c4a4bd
Showing with 122 additions and 0 deletions.
  1. +122 −0 src/core/expression/qgsexpressionfunction.cpp
@@ -47,6 +47,7 @@
#include "qgsfieldformatter.h"
#include "qgsvectorlayerfeatureiterator.h"
#include "qgsproviderregistry.h"
#include "sqlite3.h"

const QString QgsExpressionFunction::helpText() const
{
@@ -1360,6 +1361,113 @@ static QVariant fcnNumSelected( const QVariantList &values, const QgsExpressionC
return layer->selectedFeatureCount();
}

template <class T>
T getParameterValue( const QString &name, const QVariantList &values, const QgsExpressionNodeFunction *nodeFunction )
{
T result;
QgsExpressionFunction *fd = QgsExpression::Functions().value( nodeFunction->fnIndex() );
if ( fd )
{
const auto &params = fd->parameters();
int idx = 0;
for ( const auto &param : params )
{
if ( param.name() == name )
{
return values.at( idx ).value<T>();
}
++idx;
}
}

return QVariant().value<T>();
};

static QVariant fcnSqliteFetchAndIncrement( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
const QString database = values.at( 0 ).toString();
const QString table = values.at( 1 ).toString();
const QString idColumn = values.at( 2 ).toString();
const QString filterAttribute = values.at( 3 ).toString();
const QVariant filterValue = values.at( 4 ).toString();
const QVariantMap defaultValues = values.at( 5 ).toMap();


// read from database
sqlite3_database_unique_ptr sqliteDb;
sqlite3_statement_unique_ptr sqliteStatement;

if ( sqliteDb.open_v2( database, SQLITE_OPEN_READWRITE, nullptr ) != SQLITE_OK )
{
parent->setEvalErrorString( QObject::tr( "Could not open sqlite database %1. Error %2. " ).arg( database, sqliteDb.errorMessage() ) );
return QVariant();
}

QString currentValSql;
currentValSql = QStringLiteral( "SELECT %1 FROM %2" ).arg( QgsSqliteUtils::quotedIdentifier( idColumn ), QgsSqliteUtils::quotedIdentifier( table ) );
if ( !filterAttribute.isNull() )
{
currentValSql += QStringLiteral( " WHERE %1 = %2" ).arg( QgsSqliteUtils::quotedIdentifier( filterAttribute ), QgsSqliteUtils::quotedValue( filterValue ) );
}

int result;
sqliteStatement = sqliteDb.prepare( currentValSql, result );
if ( result == SQLITE_OK )
{
qlonglong nextId = 0;
if ( sqliteStatement.step() == SQLITE_ROW )
{
nextId = sqliteStatement.columnAsInt64( 0 ) + 1;
}

QString upsertSql;
upsertSql = QStringLiteral( "INSERT OR REPLACE INTO %1" ).arg( QgsSqliteUtils::quotedIdentifier( table ) );
QStringList cols;
QStringList vals;
cols << QgsSqliteUtils::quotedIdentifier( idColumn );
vals << QgsSqliteUtils::quotedValue( nextId );

if ( !filterAttribute.isNull() )
{
cols << QgsSqliteUtils::quotedIdentifier( filterAttribute );
vals << QgsSqliteUtils::quotedValue( filterValue );
}

for ( QVariantMap::const_iterator iter = defaultValues.constBegin(); iter != defaultValues.constEnd(); ++iter )
{
cols << QgsSqliteUtils::quotedIdentifier( iter.key() );
vals << iter.value().toString();
}

upsertSql += QLatin1String( " (" ) + cols.join( ',' ) + ')';
upsertSql += QLatin1String( " VALUES " );
upsertSql += '(' + vals.join( ',' ) + ')';

sqliteStatement = sqliteDb.prepare( upsertSql, result );

if ( result == SQLITE_OK )
{
result = sqliteStatement.step();
if ( result == SQLITE_DONE )
{
return nextId;
}
else
{
parent->setEvalErrorString( QStringLiteral( "Could not increment value: SQLite error: \"%1\" (%2)." ).arg( sqliteDb.errorMessage(), QString::number( result ) ) );
return QVariant();
}
}
else
{
parent->setEvalErrorString( QStringLiteral( "Could not increment value: SQLite error: \"%1\" (%2)." ).arg( sqliteDb.errorMessage(), QString::number( result ) ) );
return QVariant();
}
}

return QVariant(); // really?
}

static QVariant fcnConcat( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
QString concat;
@@ -4916,6 +5024,20 @@ const QList<QgsExpressionFunction *> &QgsExpression::Functions()
QSet<QString>()
);

sFunctions
<< new QgsStaticExpressionFunction(
QStringLiteral( "sqlite_fetch_and_increment" ),
QgsExpressionFunction::ParameterList()
<< QgsExpressionFunction::Parameter( QStringLiteral( "database" ) )
<< QgsExpressionFunction::Parameter( QStringLiteral( "table" ) )
<< QgsExpressionFunction::Parameter( QStringLiteral( "id_field" ) )
<< QgsExpressionFunction::Parameter( QStringLiteral( "filter_attribute" ) )
<< QgsExpressionFunction::Parameter( QStringLiteral( "filter_value" ) )
<< QgsExpressionFunction::Parameter( QStringLiteral( "default_values" ), true ),
fcnSqliteFetchAndIncrement,
QStringLiteral( "Record and Attributes" )
);

// **Fields and Values** functions
QgsStaticExpressionFunction *representValueFunc = new QgsStaticExpressionFunction( QStringLiteral( "represent_value" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "attribute" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "field_name" ), true ), fcnRepresentValue, QStringLiteral( "Record and Attributes" ) );

0 comments on commit fea5831

Please sign in to comment.
You can’t perform that action at this time.