Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
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 fea5831
Showing 1 changed file with 122 additions and 0 deletions.
122 changes: 122 additions & 0 deletions src/core/expression/qgsexpressionfunction.cpp
Expand Up @@ -47,6 +47,7 @@
#include "qgsfieldformatter.h"
#include "qgsvectorlayerfeatureiterator.h"
#include "qgsproviderregistry.h"
#include "sqlite3.h"

const QString QgsExpressionFunction::helpText() const
{
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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" ) );

Expand Down

0 comments on commit fea5831

Please sign in to comment.