mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-15 00:04:00 -04:00
The file qgsexpressions.h has grown to one monolithic piece over the years. This makes it hard to maintain and slows down compilation because even small changes at one end will result in recompiling big parts of the source tree. It also requires the compiler to keep track of all these implementation details for said big parts of the source tree. This splits this implementation into smaller pieces. There are soe API changes connected to this, but since these can be considered implementation details, on which not many plugins rely, this shouldn't have a big impact on the ecosystem outside the source tree.
941 lines
28 KiB
C++
941 lines
28 KiB
C++
/***************************************************************************
|
|
qgsvirtuallayersqlitemodule.cpp : SQLite module for QGIS virtual layers
|
|
begin : Nov 2015
|
|
copyright : (C) 2015 Hugo Mercier, Oslandia
|
|
email : hugo dot mercier at oslandia dot com
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include <string.h>
|
|
#include <iostream>
|
|
#include <stdint.h>
|
|
#include <stdexcept>
|
|
|
|
#include <QCoreApplication>
|
|
#include <QBuffer>
|
|
|
|
#include <qgsapplication.h>
|
|
#include <qgsvectorlayer.h>
|
|
#include <qgsvectordataprovider.h>
|
|
#include <qgsgeometry.h>
|
|
#include <qgsproject.h>
|
|
#include <qgsproviderregistry.h>
|
|
#include "qgsinterval.h"
|
|
#include <sqlite3.h>
|
|
#include <spatialite.h>
|
|
#include <stdio.h>
|
|
#include "qgsvirtuallayersqlitemodule.h"
|
|
#include "qgsvirtuallayerblob.h"
|
|
#include "qgsslottofunction.h"
|
|
#include "qgsfeatureiterator.h"
|
|
|
|
/**
|
|
* Create metadata tables if needed
|
|
*/
|
|
void initVirtualLayerMetadata( sqlite3 *db )
|
|
{
|
|
bool create_meta = false;
|
|
|
|
sqlite3_stmt *stmt = nullptr;
|
|
int r;
|
|
r = sqlite3_prepare_v2( db, "SELECT name FROM sqlite_master WHERE name='_meta'", -1, &stmt, nullptr );
|
|
if ( r )
|
|
{
|
|
throw std::runtime_error( sqlite3_errmsg( db ) );
|
|
}
|
|
create_meta = sqlite3_step( stmt ) != SQLITE_ROW;
|
|
sqlite3_finalize( stmt );
|
|
|
|
char *errMsg = nullptr;
|
|
if ( create_meta )
|
|
{
|
|
r = sqlite3_exec( db, QStringLiteral( "CREATE TABLE _meta (version INT, url TEXT); INSERT INTO _meta (version) VALUES(%1);" ).arg( VIRTUAL_LAYER_VERSION ).toUtf8().constData(), nullptr, nullptr, &errMsg );
|
|
if ( r )
|
|
{
|
|
throw std::runtime_error( errMsg );
|
|
}
|
|
}
|
|
}
|
|
|
|
void deleteGeometryBlob( void *p )
|
|
{
|
|
delete[]( reinterpret_cast< unsigned char * >( p ) );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
//
|
|
// Functions and structures used by the SQLite virtual table module
|
|
//
|
|
//-----------------------------------------------------------------------
|
|
|
|
// function called when a lived layer is deleted
|
|
void invalidateTable( void *b );
|
|
|
|
struct VTable
|
|
{
|
|
// minimal set of members (see sqlite3.h)
|
|
const sqlite3_module *pModule; /* The module for this virtual table */
|
|
int nRef; /* NO LONGER USED */
|
|
char *zErrMsg; /* Error message from sqlite3_mprintf() */
|
|
|
|
VTable( sqlite3 *db, QgsVectorLayer *layer )
|
|
: pModule( nullptr )
|
|
, nRef( 0 )
|
|
, zErrMsg( nullptr )
|
|
, mSql( db )
|
|
, mProvider( nullptr )
|
|
, mLayer( layer )
|
|
, mSlotToFunction( invalidateTable, this )
|
|
, mName( layer->name() )
|
|
, mPkColumn( -1 )
|
|
, mCrs( -1 )
|
|
, mValid( true )
|
|
{
|
|
if ( mLayer )
|
|
{
|
|
QObject::connect( layer, &QObject::destroyed, &mSlotToFunction, &QgsSlotToFunction::onSignal );
|
|
init_();
|
|
}
|
|
}
|
|
|
|
VTable( sqlite3 *db, const QString &provider, const QString &source, const QString &name, const QString &encoding )
|
|
: pModule( nullptr )
|
|
, nRef( 0 )
|
|
, zErrMsg( nullptr )
|
|
, mSql( db )
|
|
, mLayer( nullptr )
|
|
, mName( name )
|
|
, mEncoding( encoding )
|
|
, mPkColumn( -1 )
|
|
, mCrs( -1 )
|
|
, mValid( true )
|
|
{
|
|
mProvider = static_cast<QgsVectorDataProvider *>( QgsProviderRegistry::instance()->createProvider( provider, source ) );
|
|
if ( !mProvider )
|
|
{
|
|
throw std::runtime_error( "Invalid provider" );
|
|
}
|
|
else if ( mProvider && !mProvider->isValid() )
|
|
{
|
|
throw std::runtime_error( ( "Provider error:" + mProvider->error().message() ).toUtf8().constData() );
|
|
}
|
|
if ( mProvider->capabilities() & QgsVectorDataProvider::SelectEncoding )
|
|
{
|
|
mProvider->setEncoding( mEncoding );
|
|
}
|
|
init_();
|
|
}
|
|
|
|
~VTable()
|
|
{
|
|
if ( mProvider )
|
|
{
|
|
delete mProvider;
|
|
}
|
|
}
|
|
|
|
QgsVectorDataProvider *provider() { return mProvider; }
|
|
|
|
QgsVectorLayer *layer() { return mLayer; }
|
|
|
|
QString name() const { return mName; }
|
|
|
|
QString creationString() const { return mCreationStr; }
|
|
|
|
long crs() const { return mCrs; }
|
|
|
|
sqlite3 *sql() { return mSql; }
|
|
|
|
int pkColumn() const { return mPkColumn; }
|
|
|
|
void invalidate() { mValid = false; }
|
|
|
|
bool valid() const { return mValid; }
|
|
|
|
QgsFields fields() const { return mFields; }
|
|
|
|
private:
|
|
|
|
VTable( const VTable &other );
|
|
VTable &operator=( const VTable &other );
|
|
|
|
// connection
|
|
sqlite3 *mSql = nullptr;
|
|
|
|
// pointer to the underlying vector provider
|
|
QgsVectorDataProvider *mProvider = nullptr;
|
|
// pointer to the vector layer, for referenced layer
|
|
QgsVectorLayer *mLayer = nullptr;
|
|
// the QObjet responsible of receiving the deletion signal
|
|
QgsSlotToFunction mSlotToFunction;
|
|
|
|
QString mName;
|
|
|
|
QString mEncoding;
|
|
|
|
// primary key column (default = -1: none)
|
|
int mPkColumn;
|
|
|
|
// CREATE TABLE string
|
|
QString mCreationStr;
|
|
|
|
long mCrs;
|
|
|
|
bool mValid;
|
|
|
|
QgsFields mFields;
|
|
|
|
void init_()
|
|
{
|
|
mFields = mLayer ? mLayer->fields() : mProvider->fields();
|
|
QStringList sqlFields;
|
|
|
|
// add a hidden field for rtree filtering
|
|
sqlFields << QStringLiteral( "_search_frame_ HIDDEN BLOB" );
|
|
|
|
Q_FOREACH ( const QgsField &field, mFields )
|
|
{
|
|
QString typeName = QStringLiteral( "text" );
|
|
switch ( field.type() )
|
|
{
|
|
case QVariant::Int:
|
|
case QVariant::UInt:
|
|
case QVariant::Bool:
|
|
case QVariant::LongLong:
|
|
typeName = QStringLiteral( "int" );
|
|
break;
|
|
case QVariant::Double:
|
|
typeName = QStringLiteral( "real" );
|
|
break;
|
|
case QVariant::String:
|
|
default:
|
|
typeName = QStringLiteral( "text" );
|
|
break;
|
|
}
|
|
sqlFields << field.name() + " " + typeName;
|
|
}
|
|
|
|
QgsVectorDataProvider *provider = mLayer ? mLayer->dataProvider() : mProvider;
|
|
if ( provider->wkbType() != QgsWkbTypes::NoGeometry )
|
|
{
|
|
// we have here a convenient hack
|
|
// the type of a column can be declared with two numeric arguments, usually for setting numeric precision
|
|
// we are using them to set the geometry type and srid
|
|
// these will be reused by the provider when it will introspect the query to detect types
|
|
sqlFields << QStringLiteral( "geometry geometry(%1,%2)" ).arg( provider->wkbType() ).arg( provider->crs().postgisSrid() );
|
|
}
|
|
|
|
QgsAttributeList pkAttributeIndexes = provider->pkAttributeIndexes();
|
|
if ( pkAttributeIndexes.size() == 1 )
|
|
{
|
|
mPkColumn = pkAttributeIndexes.at( 0 ) + 1;
|
|
}
|
|
|
|
mCreationStr = "CREATE TABLE vtable (" + sqlFields.join( QStringLiteral( "," ) ) + ")";
|
|
|
|
mCrs = provider->crs().postgisSrid();
|
|
}
|
|
};
|
|
|
|
// function called when a lived layer is deleted
|
|
void invalidateTable( void *p )
|
|
{
|
|
reinterpret_cast<VTable *>( p )->invalidate();
|
|
}
|
|
|
|
struct VTableCursor
|
|
{
|
|
// minimal set of members (see sqlite3.h)
|
|
VTable *mVtab = nullptr;
|
|
|
|
// specific members
|
|
QgsFeature mCurrentFeature;
|
|
QgsFeatureIterator mIterator;
|
|
bool mEof;
|
|
|
|
explicit VTableCursor( VTable *vtab )
|
|
: mVtab( vtab )
|
|
, mEof( true )
|
|
{}
|
|
|
|
void filter( const QgsFeatureRequest &request )
|
|
{
|
|
if ( !mVtab->valid() )
|
|
{
|
|
mEof = true;
|
|
return;
|
|
}
|
|
|
|
mIterator = mVtab->layer() ? mVtab->layer()->getFeatures( request ) : mVtab->provider()->getFeatures( request );
|
|
// get on the first record
|
|
mEof = false;
|
|
next();
|
|
}
|
|
|
|
void next()
|
|
{
|
|
if ( !mEof )
|
|
{
|
|
mEof = !mIterator.nextFeature( mCurrentFeature );
|
|
}
|
|
}
|
|
|
|
bool eof() const { return mEof; }
|
|
|
|
int nColumns() const
|
|
{
|
|
if ( !mVtab->valid() )
|
|
return 0;
|
|
return mVtab->layer() ? mVtab->layer()->fields().count() : mVtab->provider()->fields().count();
|
|
}
|
|
|
|
sqlite3_int64 currentId() const { return mCurrentFeature.id(); }
|
|
|
|
QVariant currentAttribute( int column ) const { return mCurrentFeature.attribute( column ); }
|
|
|
|
QPair<char *, int> currentGeometry() const
|
|
{
|
|
int blob_len = 0;
|
|
char *blob = nullptr;
|
|
QgsGeometry g = mCurrentFeature.geometry();
|
|
if ( ! g.isNull() )
|
|
{
|
|
qgsGeometryToSpatialiteBlob( g, mVtab->crs(), blob, blob_len );
|
|
}
|
|
return qMakePair( blob, blob_len );
|
|
}
|
|
};
|
|
|
|
void getGeometryType( const QgsVectorDataProvider *provider, QString &geometryTypeStr, int &geometryDim, int &geometryWkbType, long &srid )
|
|
{
|
|
srid = const_cast<QgsVectorDataProvider *>( provider )->crs().postgisSrid();
|
|
QgsWkbTypes::Type t = provider->wkbType();
|
|
geometryTypeStr = QgsWkbTypes::displayString( t );
|
|
geometryDim = QgsWkbTypes::coordDimensions( t );
|
|
if ( ( t != QgsWkbTypes::NoGeometry ) && ( t != QgsWkbTypes::Unknown ) )
|
|
geometryWkbType = static_cast<int>( t );
|
|
else
|
|
geometryWkbType = 0;
|
|
}
|
|
|
|
int vtableCreateConnect( sqlite3 *sql, void *aux, int argc, const char *const *argv, sqlite3_vtab **outVtab, char **outErr, bool isCreated )
|
|
{
|
|
Q_UNUSED( aux );
|
|
Q_UNUSED( isCreated );
|
|
|
|
#define RETURN_CSTR_ERROR(err) if (outErr) {size_t s = strlen(err); *outErr=reinterpret_cast<char*>(sqlite3_malloc( static_cast<int>( s ) +1)); strncpy(*outErr, err, s);}
|
|
#define RETURN_CPPSTR_ERROR(err) if (outErr) {*outErr=reinterpret_cast<char*>(sqlite3_malloc( static_cast<int>( err.toUtf8().size() )+1)); strncpy(*outErr, err.toUtf8().constData(), err.toUtf8().size());}
|
|
|
|
if ( argc < 4 )
|
|
{
|
|
QString err( QStringLiteral( "Missing arguments: layer_id | provider, source" ) );
|
|
RETURN_CPPSTR_ERROR( err );
|
|
return SQLITE_ERROR;
|
|
}
|
|
|
|
std::unique_ptr<VTable> newVtab;
|
|
|
|
int r;
|
|
if ( argc == 4 )
|
|
{
|
|
// CREATE VIRTUAL TABLE vtab USING QgsVLayer(layer_id)
|
|
// vtab = argv[2]
|
|
// layer_id = argv[3]
|
|
QString layerid = QString::fromUtf8( argv[3] );
|
|
if ( layerid.size() >= 1 && layerid[0] == '\'' )
|
|
{
|
|
layerid = layerid.mid( 1, layerid.size() - 2 );
|
|
}
|
|
QgsMapLayer *l = QgsProject::instance()->mapLayer( layerid );
|
|
if ( !l || l->type() != QgsMapLayer::VectorLayer )
|
|
{
|
|
if ( outErr )
|
|
{
|
|
QString err( QStringLiteral( "Cannot find layer " ) );
|
|
err += QString::fromUtf8( argv[3] );
|
|
RETURN_CPPSTR_ERROR( err );
|
|
}
|
|
return SQLITE_ERROR;
|
|
}
|
|
newVtab.reset( new VTable( sql, static_cast<QgsVectorLayer *>( l ) ) );
|
|
|
|
}
|
|
else if ( argc == 5 || argc == 6 )
|
|
{
|
|
// CREATE VIRTUAL TABLE vtab USING QgsVLayer(provider,source[,encoding])
|
|
// vtab = argv[2]
|
|
// provider = argv[3]
|
|
// source = argv[4]
|
|
// encoding = argv[5]
|
|
QString provider = argv[3];
|
|
QString source = QString::fromUtf8( argv[4] );
|
|
QString encoding = QStringLiteral( "UTF-8" );
|
|
if ( argc == 6 )
|
|
{
|
|
encoding = argv[5];
|
|
}
|
|
if ( provider.size() >= 1 && provider[0] == '\'' )
|
|
{
|
|
// trim and undouble single quotes
|
|
provider = provider.mid( 1, provider.size() - 2 ).replace( QLatin1String( "''" ), QLatin1String( "'" ) );
|
|
}
|
|
if ( source.size() >= 1 && source[0] == '\'' )
|
|
{
|
|
// trim and undouble single quotes
|
|
source = source.mid( 1, source.size() - 2 ).replace( QLatin1String( "''" ), QLatin1String( "'" ) );
|
|
}
|
|
try
|
|
{
|
|
newVtab.reset( new VTable( sql, provider, source, QString::fromUtf8( argv[2] ), encoding ) );
|
|
}
|
|
catch ( std::runtime_error &e )
|
|
{
|
|
RETURN_CSTR_ERROR( e.what() );
|
|
return SQLITE_ERROR;
|
|
}
|
|
}
|
|
|
|
r = sqlite3_declare_vtab( sql, newVtab->creationString().toUtf8().constData() );
|
|
if ( r )
|
|
{
|
|
RETURN_CSTR_ERROR( sqlite3_errmsg( sql ) );
|
|
return r;
|
|
}
|
|
|
|
*outVtab = reinterpret_cast< sqlite3_vtab * >( newVtab.release() );
|
|
return SQLITE_OK;
|
|
#undef RETURN_CSTR_ERROR
|
|
#undef RETURN_CPPSTR_ERROR
|
|
}
|
|
|
|
void dbInit( sqlite3 *db )
|
|
{
|
|
// create metadata tables
|
|
initVirtualLayerMetadata( db );
|
|
}
|
|
|
|
int vtableCreate( sqlite3 *sql, void *aux, int argc, const char *const *argv, sqlite3_vtab **outVtab, char **outErr )
|
|
{
|
|
try
|
|
{
|
|
dbInit( sql );
|
|
}
|
|
catch ( std::runtime_error &e )
|
|
{
|
|
if ( outErr )
|
|
{
|
|
*outErr = reinterpret_cast< char * >( sqlite3_malloc( static_cast< int >( strlen( e.what() ) ) + 1 ) );
|
|
strcpy( *outErr, e.what() );
|
|
}
|
|
return SQLITE_ERROR;
|
|
}
|
|
|
|
return vtableCreateConnect( sql, aux, argc, argv, outVtab, outErr, /* is_created */ true );
|
|
}
|
|
|
|
int vtableConnect( sqlite3 *sql, void *aux, int argc, const char *const *argv, sqlite3_vtab **outVtab, char **outErr )
|
|
{
|
|
return vtableCreateConnect( sql, aux, argc, argv, outVtab, outErr, /* is_created */ false );
|
|
}
|
|
|
|
int vtableDestroy( sqlite3_vtab *vtab )
|
|
{
|
|
if ( vtab )
|
|
{
|
|
delete reinterpret_cast<VTable *>( vtab );
|
|
}
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
int vtableDisconnect( sqlite3_vtab *vtab )
|
|
{
|
|
if ( vtab )
|
|
{
|
|
delete reinterpret_cast<VTable *>( vtab );
|
|
}
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
int vtableRename( sqlite3_vtab *vtab, const char *newName )
|
|
{
|
|
Q_UNUSED( vtab );
|
|
Q_UNUSED( newName );
|
|
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
int vtableBestIndex( sqlite3_vtab *pvtab, sqlite3_index_info *indexInfo )
|
|
{
|
|
VTable *vtab = reinterpret_cast< VTable * >( pvtab );
|
|
for ( int i = 0; i < indexInfo->nConstraint; i++ )
|
|
{
|
|
// request for primary key filter with '='
|
|
if ( ( indexInfo->aConstraint[i].usable ) &&
|
|
( vtab->pkColumn() == indexInfo->aConstraint[i].iColumn ) &&
|
|
( indexInfo->aConstraint[i].op == SQLITE_INDEX_CONSTRAINT_EQ ) )
|
|
{
|
|
indexInfo->aConstraintUsage[i].argvIndex = 1;
|
|
indexInfo->aConstraintUsage[i].omit = 1;
|
|
indexInfo->idxNum = 1; // PK filter
|
|
indexInfo->estimatedCost = 1.0;
|
|
indexInfo->idxStr = nullptr;
|
|
indexInfo->needToFreeIdxStr = 0;
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
// request for filter with a comparison operator
|
|
if ( ( indexInfo->aConstraint[i].usable ) &&
|
|
( indexInfo->aConstraint[i].iColumn > 0 ) &&
|
|
( indexInfo->aConstraint[i].iColumn <= vtab->fields().count() ) &&
|
|
( ( indexInfo->aConstraint[i].op == SQLITE_INDEX_CONSTRAINT_EQ ) || // if no PK
|
|
( indexInfo->aConstraint[i].op == SQLITE_INDEX_CONSTRAINT_GT ) ||
|
|
( indexInfo->aConstraint[i].op == SQLITE_INDEX_CONSTRAINT_LE ) ||
|
|
( indexInfo->aConstraint[i].op == SQLITE_INDEX_CONSTRAINT_LT ) ||
|
|
( indexInfo->aConstraint[i].op == SQLITE_INDEX_CONSTRAINT_GE )
|
|
#ifdef SQLITE_INDEX_CONSTRAINT_LIKE
|
|
|| ( indexInfo->aConstraint[i].op == SQLITE_INDEX_CONSTRAINT_LIKE )
|
|
#endif
|
|
) )
|
|
{
|
|
indexInfo->aConstraintUsage[i].argvIndex = 1;
|
|
indexInfo->aConstraintUsage[i].omit = 1;
|
|
indexInfo->idxNum = 3; // expression filter
|
|
indexInfo->estimatedCost = 2.0; // probably better than no index
|
|
|
|
QString expr = vtab->fields().at( indexInfo->aConstraint[i].iColumn - 1 ).name();
|
|
switch ( indexInfo->aConstraint[i].op )
|
|
{
|
|
case SQLITE_INDEX_CONSTRAINT_EQ:
|
|
expr += QLatin1String( " = " );
|
|
break;
|
|
case SQLITE_INDEX_CONSTRAINT_GT:
|
|
expr += QLatin1String( " > " );
|
|
break;
|
|
case SQLITE_INDEX_CONSTRAINT_LE:
|
|
expr += QLatin1String( " <= " );
|
|
break;
|
|
case SQLITE_INDEX_CONSTRAINT_LT:
|
|
expr += QLatin1String( " < " );
|
|
break;
|
|
case SQLITE_INDEX_CONSTRAINT_GE:
|
|
expr += QLatin1String( " >= " );
|
|
break;
|
|
#ifdef SQLITE_INDEX_CONSTRAINT_LIKE
|
|
case SQLITE_INDEX_CONSTRAINT_LIKE:
|
|
expr += QLatin1String( " LIKE " );
|
|
break;
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
|
|
QByteArray ba = expr.toUtf8();
|
|
char *cp = ( char * )sqlite3_malloc( ba.size() + 1 );
|
|
memcpy( cp, ba.constData(), ba.size() + 1 );
|
|
|
|
indexInfo->idxStr = cp;
|
|
indexInfo->needToFreeIdxStr = 1;
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
// request for rtree filtering
|
|
if ( ( indexInfo->aConstraint[i].usable ) &&
|
|
( 0 == indexInfo->aConstraint[i].iColumn ) &&
|
|
( indexInfo->aConstraint[i].op == SQLITE_INDEX_CONSTRAINT_EQ ) )
|
|
{
|
|
indexInfo->aConstraintUsage[i].argvIndex = 1;
|
|
// do not test for equality, since it is used for filtering, not to return an actual value
|
|
indexInfo->aConstraintUsage[i].omit = 1;
|
|
indexInfo->idxNum = 2; // RTree filter
|
|
indexInfo->estimatedCost = 1.0;
|
|
indexInfo->idxStr = nullptr;
|
|
indexInfo->needToFreeIdxStr = 0;
|
|
return SQLITE_OK;
|
|
}
|
|
}
|
|
indexInfo->idxNum = 0;
|
|
indexInfo->estimatedCost = 10.0;
|
|
indexInfo->idxStr = nullptr;
|
|
indexInfo->needToFreeIdxStr = 0;
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
int vtableOpen( sqlite3_vtab *vtab, sqlite3_vtab_cursor **outCursor )
|
|
{
|
|
VTableCursor *ncursor = new VTableCursor( reinterpret_cast< VTable * >( vtab ) );
|
|
*outCursor = reinterpret_cast< sqlite3_vtab_cursor * >( ncursor );
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
int vtableClose( sqlite3_vtab_cursor *cursor )
|
|
{
|
|
if ( cursor )
|
|
{
|
|
delete reinterpret_cast<VTableCursor *>( cursor );
|
|
}
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
int vtableFilter( sqlite3_vtab_cursor *cursor, int idxNum, const char *idxStr, int argc, sqlite3_value **argv )
|
|
{
|
|
Q_UNUSED( argc );
|
|
Q_UNUSED( idxStr );
|
|
|
|
QgsFeatureRequest request;
|
|
if ( idxNum == 1 )
|
|
{
|
|
// id filter
|
|
request.setFilterFid( sqlite3_value_int( argv[0] ) );
|
|
}
|
|
else if ( idxNum == 2 )
|
|
{
|
|
// rtree filter
|
|
const char *blob = reinterpret_cast< const char * >( sqlite3_value_blob( argv[0] ) );
|
|
int bytes = sqlite3_value_bytes( argv[0] );
|
|
QgsRectangle r( spatialiteBlobBbox( blob, bytes ) );
|
|
request.setFilterRect( r );
|
|
}
|
|
else if ( idxNum == 3 )
|
|
{
|
|
// comparison operator filter
|
|
// build an expression filter and rely on expression compiler if available
|
|
QString expr = idxStr;
|
|
switch ( sqlite3_value_type( argv[0] ) )
|
|
{
|
|
case SQLITE_INTEGER:
|
|
expr += QString::number( sqlite3_value_int64( argv[0] ) );
|
|
break;
|
|
case SQLITE_FLOAT:
|
|
expr += QString::number( sqlite3_value_double( argv[0] ) );
|
|
break;
|
|
case SQLITE_TEXT:
|
|
{
|
|
int n = sqlite3_value_bytes( argv[0] );
|
|
const char *t = reinterpret_cast<const char *>( sqlite3_value_text( argv[0] ) );
|
|
QString str = QString::fromUtf8( t, n );
|
|
expr += "'" + str.replace( QLatin1String( "'" ), QLatin1String( "''" ) ) + "'";
|
|
break;
|
|
}
|
|
case SQLITE_NULL:
|
|
case SQLITE_BLOB: // comparison to blob ignored
|
|
default:
|
|
expr += QLatin1String( " is null" );
|
|
break;
|
|
}
|
|
request.setFilterExpression( expr );
|
|
}
|
|
VTableCursor *c = reinterpret_cast<VTableCursor *>( cursor );
|
|
c->filter( request );
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
int vtableNext( sqlite3_vtab_cursor *cursor )
|
|
{
|
|
VTableCursor *c = reinterpret_cast<VTableCursor *>( cursor );
|
|
c->next();
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
int vtableEof( sqlite3_vtab_cursor *cursor )
|
|
{
|
|
VTableCursor *c = reinterpret_cast<VTableCursor *>( cursor );
|
|
return c->eof();
|
|
}
|
|
|
|
int vtableRowId( sqlite3_vtab_cursor *cursor, sqlite3_int64 *outRowid )
|
|
{
|
|
VTableCursor *c = reinterpret_cast<VTableCursor *>( cursor );
|
|
*outRowid = c->currentId();
|
|
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
int vtableColumn( sqlite3_vtab_cursor *cursor, sqlite3_context *ctxt, int idx )
|
|
{
|
|
VTableCursor *c = reinterpret_cast<VTableCursor *>( cursor );
|
|
if ( idx == 0 )
|
|
{
|
|
// _search_frame_, return null
|
|
sqlite3_result_null( ctxt );
|
|
return SQLITE_OK;
|
|
}
|
|
if ( idx == c->nColumns() + 1 )
|
|
{
|
|
QPair<char *, int> g = c->currentGeometry();
|
|
if ( !g.first )
|
|
sqlite3_result_null( ctxt );
|
|
else
|
|
sqlite3_result_blob( ctxt, g.first, g.second, deleteGeometryBlob );
|
|
return SQLITE_OK;
|
|
}
|
|
QVariant v = c->currentAttribute( idx - 1 );
|
|
if ( v.isNull() )
|
|
{
|
|
sqlite3_result_null( ctxt );
|
|
}
|
|
else
|
|
{
|
|
switch ( v.type() )
|
|
{
|
|
case QVariant::Int:
|
|
case QVariant::UInt:
|
|
sqlite3_result_int( ctxt, v.toInt() );
|
|
break;
|
|
case QVariant::LongLong:
|
|
sqlite3_result_int64( ctxt, v.toLongLong() );
|
|
break;
|
|
case QVariant::Double:
|
|
sqlite3_result_double( ctxt, v.toDouble() );
|
|
break;
|
|
default:
|
|
{
|
|
sqlite3_result_text( ctxt, v.toString().toUtf8(), -1, SQLITE_TRANSIENT );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
|
|
static QCoreApplication *sCoreApp = nullptr;
|
|
|
|
void moduleDestroy( void * )
|
|
{
|
|
if ( sCoreApp )
|
|
{
|
|
delete sCoreApp;
|
|
}
|
|
}
|
|
|
|
// the expression context used for calling qgis functions
|
|
QgsExpressionContext qgisFunctionExpressionContext;
|
|
|
|
void qgisFunctionWrapper( sqlite3_context *ctxt, int nArgs, sqlite3_value **args )
|
|
{
|
|
// convert from sqlite3 value to QVariant and then call the qgis expression function
|
|
// the 3 basic sqlite3 types (int, float, text) are converted to their QVariant equivalent
|
|
// Expression::Interval is handled specifically
|
|
// geometries are converted between spatialite and QgsGeometry
|
|
// other data types (datetime mainly) are represented as BLOBs thanks to QVariant serializing functions
|
|
|
|
QgsExpressionFunction *foo = reinterpret_cast<QgsExpressionFunction *>( sqlite3_user_data( ctxt ) );
|
|
|
|
QVariantList variants;
|
|
for ( int i = 0; i < nArgs; i++ )
|
|
{
|
|
int t = sqlite3_value_type( args[i] );
|
|
switch ( t )
|
|
{
|
|
case SQLITE_INTEGER:
|
|
variants << QVariant( sqlite3_value_int64( args[i] ) );
|
|
break;
|
|
case SQLITE_FLOAT:
|
|
variants << QVariant( sqlite3_value_double( args[i] ) );
|
|
break;
|
|
case SQLITE_TEXT:
|
|
{
|
|
int n = sqlite3_value_bytes( args[i] );
|
|
const char *t = reinterpret_cast<const char *>( sqlite3_value_text( args[i] ) );
|
|
QString str = QString::fromUtf8( t, n );
|
|
variants << QVariant( str );
|
|
break;
|
|
}
|
|
case SQLITE_BLOB:
|
|
{
|
|
int n = sqlite3_value_bytes( args[i] );
|
|
const char *blob = reinterpret_cast<const char *>( sqlite3_value_blob( args[i] ) );
|
|
// spatialite blobs start with a 0 byte
|
|
if ( n > 0 && blob[0] == 0 )
|
|
{
|
|
QgsGeometry geom = spatialiteBlobToQgsGeometry( blob, n );
|
|
variants << QVariant::fromValue( geom );
|
|
}
|
|
else
|
|
{
|
|
// else it is another type
|
|
QByteArray ba = QByteArray::fromRawData( blob + 1, n - 1 );
|
|
QBuffer buffer( &ba );
|
|
buffer.open( QIODevice::ReadOnly );
|
|
QDataStream ds( &buffer );
|
|
QVariant v;
|
|
ds >> v;
|
|
buffer.close();
|
|
variants << v;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
variants << QVariant(); // null
|
|
break;
|
|
};
|
|
}
|
|
|
|
QgsExpression parentExpr( QLatin1String( "" ) );
|
|
QVariant ret = foo->func( variants, &qgisFunctionExpressionContext, &parentExpr );
|
|
if ( parentExpr.hasEvalError() )
|
|
{
|
|
QByteArray ba = parentExpr.evalErrorString().toUtf8();
|
|
sqlite3_result_error( ctxt, ba.constData(), ba.size() );
|
|
return;
|
|
}
|
|
|
|
if ( ret.isNull() )
|
|
{
|
|
sqlite3_result_null( ctxt );
|
|
return;
|
|
}
|
|
|
|
switch ( ret.type() )
|
|
{
|
|
case QVariant::Bool:
|
|
case QVariant::Int:
|
|
case QVariant::UInt:
|
|
case QVariant::LongLong:
|
|
sqlite3_result_int64( ctxt, ret.toLongLong() );
|
|
break;
|
|
case QVariant::Double:
|
|
sqlite3_result_double( ctxt, ret.toDouble() );
|
|
break;
|
|
case QVariant::String:
|
|
{
|
|
QByteArray ba( ret.toByteArray() );
|
|
sqlite3_result_text( ctxt, ba.constData(), ba.size(), SQLITE_TRANSIENT );
|
|
break;
|
|
}
|
|
case QVariant::UserType:
|
|
{
|
|
if ( ret.canConvert<QgsGeometry>() )
|
|
{
|
|
char *blob = nullptr;
|
|
int size = 0;
|
|
qgsGeometryToSpatialiteBlob( ret.value<QgsGeometry>(), /*srid*/0, blob, size );
|
|
sqlite3_result_blob( ctxt, blob, size, deleteGeometryBlob );
|
|
}
|
|
else if ( ret.canConvert<QgsInterval>() )
|
|
{
|
|
sqlite3_result_double( ctxt, ret.value<QgsInterval>().seconds() );
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
QBuffer buffer;
|
|
buffer.open( QBuffer::ReadWrite );
|
|
QDataStream ds( &buffer );
|
|
// something different from 0 (to distinguish from the first byte of a geometry blob)
|
|
char type = 1;
|
|
buffer.write( &type, 1 );
|
|
// then the serialized version of the variant
|
|
ds << ret;
|
|
buffer.close();
|
|
sqlite3_result_blob( ctxt, buffer.buffer().constData(), buffer.buffer().size(), SQLITE_TRANSIENT );
|
|
}
|
|
};
|
|
}
|
|
|
|
void registerQgisFunctions( sqlite3 *db )
|
|
{
|
|
QStringList excludedFunctions;
|
|
excludedFunctions << QStringLiteral( "min" ) << QStringLiteral( "max" ) << QStringLiteral( "coalesce" ) << QStringLiteral( "get_feature" ) << QStringLiteral( "getFeature" ) << QStringLiteral( "attribute" );
|
|
QStringList reservedFunctions;
|
|
reservedFunctions << QStringLiteral( "left" ) << QStringLiteral( "right" ) << QStringLiteral( "union" );
|
|
// register QGIS expression functions
|
|
Q_FOREACH ( QgsExpressionFunction *foo, QgsExpression::Functions() )
|
|
{
|
|
if ( foo->usesGeometry( nullptr ) || foo->lazyEval() )
|
|
{
|
|
// there is no "current" feature here, so calling functions that access "the" geometry does not make sense
|
|
// also, we can't pass Node values for lazy evaluations
|
|
continue;
|
|
}
|
|
if ( excludedFunctions.contains( foo->name() ) )
|
|
continue;
|
|
|
|
QStringList names;
|
|
names << foo->name();
|
|
names << foo->aliases();
|
|
|
|
Q_FOREACH ( QString name, names ) // for each alias
|
|
{
|
|
if ( reservedFunctions.contains( name ) ) // reserved keyword
|
|
name = "_" + name;
|
|
if ( name.startsWith( QLatin1String( "$" ) ) )
|
|
continue;
|
|
|
|
// register the function and pass the pointer to the Function* as user data
|
|
int r = sqlite3_create_function( db, name.toUtf8().constData(), foo->params(), SQLITE_UTF8, foo, qgisFunctionWrapper, nullptr, nullptr );
|
|
if ( r != SQLITE_OK )
|
|
{
|
|
// is it because a function of the same name already exist (in Spatialite for instance ?)
|
|
// we then try to recreate it with a prefix
|
|
name = "qgis_" + name;
|
|
sqlite3_create_function( db, name.toUtf8().constData(), foo->params(), SQLITE_UTF8, foo, qgisFunctionWrapper, nullptr, nullptr );
|
|
}
|
|
}
|
|
}
|
|
|
|
// initialize the expression context
|
|
qgisFunctionExpressionContext << QgsExpressionContextUtils::globalScope();
|
|
qgisFunctionExpressionContext << QgsExpressionContextUtils::projectScope( QgsProject::instance() );
|
|
}
|
|
|
|
int qgsvlayerModuleInit( sqlite3 *db, char **pzErrMsg, void *unused /*const sqlite3_api_routines *pApi*/ )
|
|
{
|
|
Q_UNUSED( pzErrMsg );
|
|
Q_UNUSED( unused );
|
|
|
|
int rc = SQLITE_OK;
|
|
|
|
// check if qgis providers are loaded
|
|
if ( !QCoreApplication::instance() )
|
|
{
|
|
// if run standalone
|
|
static int moduleArgc = 1;
|
|
static char moduleName[] = "qgsvlayer_module";
|
|
static char *moduleArgv[] = { moduleName };
|
|
sCoreApp = new QCoreApplication( moduleArgc, moduleArgv );
|
|
QgsApplication::init();
|
|
QgsApplication::initQgis();
|
|
}
|
|
|
|
static sqlite3_module module;
|
|
module.xCreate = vtableCreate;
|
|
module.xConnect = vtableConnect;
|
|
module.xBestIndex = vtableBestIndex;
|
|
module.xDisconnect = vtableDisconnect;
|
|
module.xDestroy = vtableDestroy;
|
|
module.xOpen = vtableOpen;
|
|
module.xClose = vtableClose;
|
|
module.xFilter = vtableFilter;
|
|
module.xNext = vtableNext;
|
|
module.xEof = vtableEof;
|
|
module.xColumn = vtableColumn;
|
|
module.xRowid = vtableRowId;
|
|
module.xRename = vtableRename;
|
|
|
|
module.xUpdate = nullptr;
|
|
module.xBegin = nullptr;
|
|
module.xSync = nullptr;
|
|
module.xCommit = nullptr;
|
|
module.xRollback = nullptr;
|
|
module.xFindFunction = nullptr;
|
|
module.xSavepoint = nullptr;
|
|
module.xRelease = nullptr;
|
|
module.xRollbackTo = nullptr;
|
|
|
|
sqlite3_create_module_v2( db, "QgsVLayer", &module, nullptr, moduleDestroy );
|
|
|
|
registerQgisFunctions( db );
|
|
|
|
return rc;
|
|
}
|