Merge pull request #2568 from mhugo/vlayers

Add support for virtual layers
This commit is contained in:
Hugo Mercier 2015-12-18 21:54:49 +02:00
commit 60f246a350
35 changed files with 5305 additions and 2 deletions

View File

@ -141,6 +141,7 @@
%Include qgsvectorlayerfeatureiterator.sip
%Include qgsvisibilitypresetcollection.sip
%Include qgslayerdefinition.sip
%Include qgsvirtuallayerdefinition.sip
%Include auth/qgsauthcertutils.sip
%Include auth/qgsauthconfig.sip

View File

@ -0,0 +1,125 @@
/**
* Class to manipulate the definition of a virtual layer
*
* It is used to extract parameters from an initial virtual layer definition as well as
* to store the complete, expanded definition once types have been detected.
*/
class QgsVirtualLayerDefinition
{
%TypeHeaderCode
#include <qgsvirtuallayerdefinition.h>
%End
public:
/**
* A SourceLayer is either a reference to a live layer in the registry
* or all the parameters needed to load it (provider key, source, etc.)
*/
class SourceLayer
{
public:
//! Constructor variant to build a live layer reference
SourceLayer( const QString& name, const QString& ref );
//! Constructor variant to build a layer with a provider and a source
SourceLayer( const QString& name, const QString& source, const QString& provider, const QString& encoding );
//! Is it a live layer or not ?
bool isReferenced() const;
//! The reference (id) of the live layer
QString reference() const;
//! Name of the layer
QString name() const;
//! Provider key
QString provider() const;
//! The source url used by the provider to build the layer
QString source() const;
//! Optional encoding for the provider
QString encoding() const;
};
//! Constructor with an optional file path
QgsVirtualLayerDefinition( const QString& filePath = "" );
//! Constructor to build a definition from a QUrl
//! The path part of the URL is extracted as well as the following optional keys:
//! layer_ref=layer_id[:name] represents a live layer referenced by its ID. An optional name can be given
//! layer=provider:source[:name[:encoding]] represents a layer given by its provider key, its source url (URL-encoded).
//! An optional name and encoding can be given
//! geometry=column_name[:type:srid] gives the definition of the geometry column.
//! Type can be either a WKB type code or a string (point, linestring, etc.)
//! srid is an integer
//! uid=column_name is the name of a column with unique integer values.
//! nogeometry is a flag to force the layer to be a non-geometry layer
//! query=sql represents the SQL query. Must be URL-encoded
//! field=column_name:[int|real|text] represents a field with its name and its type
static QgsVirtualLayerDefinition fromUrl( const QUrl& url );
//! Convert the definition into a QUrl
QUrl toUrl() const;
//! Convert into a QString that can be read by the virtual layer provider
QString toString() const;
//! Add a live layer source layer
void addSource( const QString& name, const QString ref );
//! Add a layer with a source, a provider and an encoding
void addSource( const QString& name, const QString source, const QString& provider, const QString& encoding = "" );
//! List of source layers
typedef QList<QgsVirtualLayerDefinition::SourceLayer> SourceLayers;
//! Get access to the source layers
const SourceLayers& sourceLayers() const;
//! Get the SQL query
QString query() const;
//! Set the SQL query
void setQuery( const QString& query );
//! Get the file path. May be empty
QString filePath() const;
//! Set the file path
void setFilePath( const QString& filePath );
//! Get the name of the field with unique identifiers
QString uid() const;
//! Set the name of the field with unique identifiers
void setUid( const QString& uid );
//! Get the name of the geometry field. Empty if no geometry field
QString geometryField() const;
//! Set the name of the geometry field
void setGeometryField( const QString& geometryField );
//! Get the type of the geometry
//! QgsWKBTypes::NoGeometry to hide any geometry
//! QgsWKBTypes::Unknown for unknown types
QgsWKBTypes::Type geometryWkbType() const;
//! Set the type of the geometry
void setGeometryWkbType( QgsWKBTypes::Type t );
//! Get the SRID of the geometry
long geometrySrid() const;
//! Set the SRID of the geometry
void setGeometrySrid( long srid );
//! Get field definitions
const QgsFields& fields() const;
//! Set field definitions
void setFields( const QgsFields& fields );
//! Convenience method to test if a given source layer is part of the definition
bool hasSourceLayer( QString name ) const;
//! Convenience method to test whether the definition has referenced (live) layers
bool hasReferencedLayers() const;
//! Convenient method to test if the geometry is defined (not NoGeometry and not Unknown)
bool hasDefinedGeometry() const;
};

View File

@ -3,6 +3,7 @@ ADD_SUBDIRECTORY(spatialite)
IF(WITH_ORACLE)
ADD_SUBDIRECTORY(oracle)
ENDIF(WITH_ORACLE)
ADD_SUBDIRECTORY(vlayers)
FILE(GLOB PY_FILES *.py)
PLUGIN_INSTALL(db_manager db_plugins ${PY_FILES})

View File

@ -0,0 +1,7 @@
FILE(GLOB PY_FILES *.py)
PYQT_ADD_RESOURCES(PYRC_FILES resources.qrc)
PLUGIN_INSTALL(db_manager db_plugins/vlayers ${PY_FILES} ${PYRC_FILES})

View File

@ -0,0 +1,430 @@
# -*- coding: utf-8 -*-
"""
/***************************************************************************
Name : Virtual layers plugin for DB Manager
Date : December 2015
copyright : (C) 2015 by Hugo Mercier
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. *
* *
***************************************************************************/
"""
from PyQt4.QtCore import QFile, QUrl, QTemporaryFile
from PyQt4.QtGui import QApplication
from ..connector import DBConnector
from ..plugin import ConnectionError, DbError, Table
from qgis.core import *
import sqlite3
class sqlite3_connection:
def __init__(self, sqlite_file):
self.conn = sqlite3.connect(sqlite_file)
def __enter__(self):
return self.conn
def __exit__(self, type, value, traceback):
self.conn.close()
def getQueryGeometryName(sqlite_file):
# introspect the file
with sqlite3_connection(sqlite_file) as conn:
c = conn.cursor()
for r in c.execute("SELECT url FROM _meta"):
d = QgsVirtualLayerDefinition.fromUrl(QUrl.fromEncoded(r[0]))
if d.hasDefinedGeometry():
return d.geometryField()
return None
def classFactory():
return VLayerConnector
# Tables in DB Manager are identified by their display names
# This global registry maps a display name with a layer id
# It is filled when getVectorTables is called
class VLayerRegistry:
_instance = None
@classmethod
def instance(cls):
if cls._instance == None:
cls._instance = VLayerRegistry()
return cls._instance
def __init__(self):
self.layers = {}
def reset(self):
self.layers = {}
def has(self, k):
return k in self.layers
def get(self, k):
return self.layers.get(k)
def __getitem__(self, k):
return self.get(k)
def set(self, k, l):
self.layers[k] = l
def __setitem__(self, k, l):
self.set(k, l)
def items(self):
return self.layers.items()
def getLayer(self, l):
lid = self.layers.get(l)
if lid is None:
return lid
return QgsMapLayerRegistry.instance().mapLayer(lid)
class VLayerConnector(DBConnector):
def __init__(self, uri):
pass
def _execute(self, cursor, sql):
# This is only used to get list of fields
class DummyCursor:
def __init__(self, sql):
self.sql = sql
def close(self):
pass
return DummyCursor(sql)
def _get_cursor(self, name=None):
print "_get_cursor_", name
def _get_cursor_columns(self, c):
tf = QTemporaryFile()
tf.open()
tmp = tf.fileName()
tf.close()
q = QUrl.toPercentEncoding(c.sql)
p = QgsVectorLayer("%s?query=%s" % (tmp, q), "vv", "virtual")
if not p.isValid():
return []
f = [f.name() for f in p.fields()]
if p.geometryType() != QGis.WKBNoGeometry:
gn = getQueryGeometryName(tmp)
if gn:
f += [gn]
return f
def uri(self):
return QgsDataSourceURI("qgis")
def getInfo(self):
return "info"
def getSpatialInfo(self):
return None
def hasSpatialSupport(self):
return True
def hasRasterSupport(self):
return False
def hasCustomQuerySupport(self):
return True
def hasTableColumnEditingSupport(self):
return False
def fieldTypes(self):
return [
"integer", "bigint", "smallint", # integers
"real", "double", "float", "numeric", # floats
"varchar", "varchar(255)", "character(20)", "text", # strings
"date", "datetime" # date/time
]
def getSchemas(self):
return None
def getTables(self, schema=None, add_sys_tables=False):
""" get list of tables """
return self.getVectorTables()
def getVectorTables(self, schema=None):
""" get list of table with a geometry column
it returns:
name (table name)
is_system_table
type = 'view' (is a view?)
geometry_column:
f_table_name (the table name in geometry_columns may be in a wrong case, use this to load the layer)
f_geometry_column
type
coord_dimension
srid
"""
reg = VLayerRegistry.instance()
VLayerRegistry.instance().reset()
lst = []
for _, l in QgsMapLayerRegistry.instance().mapLayers().items():
if l.type() == QgsMapLayer.VectorLayer:
lname = l.name()
# if there is already a layer with this name, use the layer id
# as name
if reg.has(lname):
lname = l.id()
VLayerRegistry.instance().set(lname, l.id())
geomType = None
dim = None
g = l.dataProvider().geometryType()
if g == QGis.WKBPoint:
geomType = 'POINT'
dim = 'XY'
elif g == QGis.WKBLineString:
geomType = 'LINESTRING'
dim = 'XY'
elif g == QGis.WKBPolygon:
geomType = 'POLYGON'
dim = 'XY'
elif g == QGis.WKBMultiPoint:
geomType = 'MULTIPOINT'
dim = 'XY'
elif g == QGis.WKBMultiLineString:
geomType = 'MULTILINESTRING'
dim = 'XY'
elif g == QGis.WKBMultiPolygon:
geomType = 'MULTIPOLYGON'
dim = 'XY'
elif g == QGis.WKBPoint25D:
geomType = 'POINT'
dim = 'XYZ'
elif g == QGis.WKBLineString25D:
geomType = 'LINESTRING'
dim = 'XYZ'
elif g == QGis.WKBPolygon25D:
geomType = 'POLYGON'
dim = 'XYZ'
elif g == QGis.WKBMultiPoint25D:
geomType = 'MULTIPOINT'
dim = 'XYZ'
elif g == QGis.WKBMultiLineString25D:
geomType = 'MULTILINESTRING'
dim = 'XYZ'
elif g == QGis.WKBMultiPolygon25D:
geomType = 'MULTIPOLYGON'
dim = 'XYZ'
lst.append(
(Table.VectorType, lname, False, False, l.id(), 'geometry', geomType, dim, l.crs().postgisSrid()))
return lst
def getRasterTables(self, schema=None):
return []
def getTableRowCount(self, table):
t = table[1]
l = VLayerRegistry.instance().getLayer(t)
return l.featureCount()
def getTableFields(self, table):
""" return list of columns in table """
t = table[1]
l = VLayerRegistry.instance().getLayer(t)
# id, name, type, nonnull, default, pk
n = l.dataProvider().fields().size()
f = [(i, f.name(), f.typeName(), False, None, False)
for i, f in enumerate(l.dataProvider().fields())]
f += [(n, "geometry", "geometry", False, None, False)]
return f
def getTableIndexes(self, table):
return []
def getTableConstraints(self, table):
return None
def getTableTriggers(self, table):
return []
def deleteTableTrigger(self, trigger, table=None):
return
def getTableExtent(self, table, geom):
is_id, t = table
if is_id:
l = QgsMapLayerRegistry.instance().mapLayer(t)
else:
l = VLayerRegistry.instance().getLayer(t)
e = l.extent()
r = (e.xMinimum(), e.yMinimum(), e.xMaximum(), e.yMaximum())
return r
def getViewDefinition(self, view):
print "**unimplemented** getViewDefinition"
def getSpatialRefInfo(self, srid):
crs = QgsCoordinateReferenceSystem(srid)
return crs.description()
def isVectorTable(self, table):
return True
def isRasterTable(self, table):
return False
def createTable(self, table, field_defs, pkey):
print "**unimplemented** createTable"
return False
def deleteTable(self, table):
print "**unimplemented** deleteTable"
return False
def emptyTable(self, table):
print "**unimplemented** emptyTable"
return False
def renameTable(self, table, new_table):
print "**unimplemented** renameTable"
return False
def moveTable(self, table, new_table, new_schema=None):
print "**unimplemented** moveTable"
return False
def createView(self, view, query):
print "**unimplemented** createView"
return False
def deleteView(self, view):
print "**unimplemented** deleteView"
return False
def renameView(self, view, new_name):
print "**unimplemented** renameView"
return False
def runVacuum(self):
print "**unimplemented** runVacuum"
return False
def addTableColumn(self, table, field_def):
print "**unimplemented** addTableColumn"
return False
def deleteTableColumn(self, table, column):
print "**unimplemented** deleteTableColumn"
def updateTableColumn(self, table, column, new_name, new_data_type=None, new_not_null=None, new_default=None):
print "**unimplemented** updateTableColumn"
def renameTableColumn(self, table, column, new_name):
print "**unimplemented** renameTableColumn"
return False
def setColumnType(self, table, column, data_type):
print "**unimplemented** setColumnType"
return False
def setColumnDefault(self, table, column, default):
print "**unimplemented** setColumnDefault"
return False
def setColumnNull(self, table, column, is_null):
print "**unimplemented** setColumnNull"
return False
def isGeometryColumn(self, table, column):
print "**unimplemented** isGeometryColumn"
return False
def addGeometryColumn(self, table, geom_column='geometry', geom_type='POINT', srid=-1, dim=2):
print "**unimplemented** addGeometryColumn"
return False
def deleteGeometryColumn(self, table, geom_column):
print "**unimplemented** deleteGeometryColumn"
return False
def addTableUniqueConstraint(self, table, column):
print "**unimplemented** addTableUniqueConstraint"
return False
def deleteTableConstraint(self, table, constraint):
print "**unimplemented** deleteTableConstraint"
return False
def addTablePrimaryKey(self, table, column):
print "**unimplemented** addTablePrimaryKey"
return False
def createTableIndex(self, table, name, column, unique=False):
print "**unimplemented** createTableIndex"
return False
def deleteTableIndex(self, table, name):
print "**unimplemented** deleteTableIndex"
return False
def createSpatialIndex(self, table, geom_column='geometry'):
print "**unimplemented** createSpatialIndex"
return False
def deleteSpatialIndex(self, table, geom_column='geometry'):
print "**unimplemented** deleteSpatialIndex"
return False
def hasSpatialIndex(self, table, geom_column='geometry'):
print "**unimplemented** hasSpatialIndex"
return False
def execution_error_types(self):
print "**unimplemented** execution_error_types"
return False
def connection_error_types(self):
print "**unimplemented** connection_error_types"
return False
def getSqlDictionary(self):
from .sql_dictionary import getSqlDictionary
sql_dict = getSqlDictionary()
if True:
items = []
for tbl in self.getTables():
items.append(tbl[1]) # table name
for fld in self.getTableFields((None, tbl[1])):
items.append(fld[1]) # field name
sql_dict["identifier"] = items
return sql_dict
def getQueryBuilderDictionary(self):
from .sql_dictionary import getQueryBuilderDictionary
return getQueryBuilderDictionary()

View File

@ -0,0 +1,111 @@
# -*- coding: utf-8 -*-
"""
/***************************************************************************
Name : Virtual layers plugin for DB Manager
Date : December 2015
copyright : (C) 2015 by Hugo Mercier
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. *
* *
***************************************************************************/
"""
from ..data_model import TableDataModel, BaseTableModel
from .connector import VLayerRegistry, getQueryGeometryName
from .plugin import LVectorTable
from ..plugin import DbError
from PyQt4.QtCore import QUrl, QTime, QTemporaryFile
from qgis.core import QgsProviderRegistry, QgsErrorMessage, QGis, QgsVectorLayer
import os
class LTableDataModel(TableDataModel):
def __init__(self, table, parent=None):
TableDataModel.__init__(self, table, parent)
self.layer = None
if isinstance(table, LVectorTable):
self.layer = VLayerRegistry.instance().getLayer(table.name)
else:
self.layer = VLayerRegistry.instance().getLayer(table)
if not self.layer:
return
# populate self.resdata
self.resdata = []
for f in self.layer.getFeatures():
self.resdata.append(f.attributes())
self.fetchedFrom = 0
self.fetchedCount = len(self.resdata)
def rowCount(self, index=None):
if self.layer:
return self.layer.featureCount()
return 0
class LSqlResultModel(BaseTableModel):
# BaseTableModel
def __init__(self, db, sql, parent=None):
# create a virtual layer with non-geometry results
q = QUrl.toPercentEncoding(sql)
t = QTime()
t.start()
tf = QTemporaryFile()
tf.open()
tmp = tf.fileName()
tf.close()
p = QgsVectorLayer("%s?query=%s" % (tmp, q), "vv", "virtual")
self._secs = t.elapsed() / 1000.0
if not p.isValid():
data = []
header = []
raise DbError(p.dataProvider().error().summary(), sql)
else:
header = [f.name() for f in p.fields()]
has_geometry = False
if p.geometryType() != QGis.WKBNoGeometry:
gn = getQueryGeometryName(tmp)
if gn:
has_geometry = True
header += [gn]
data = []
for f in p.getFeatures():
a = f.attributes()
if has_geometry:
if f.geometry():
a += [f.geometry().exportToWkt()]
else:
a += [None]
data += [a]
self._secs = 0
self._affectedRows = len(data)
BaseTableModel.__init__(self, header, data, parent)
def secs(self):
return self._secs
def affectedRows(self):
return self._affectedRows

View File

@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
"""
/***************************************************************************
Name : Virtual layers plugin for DB Manager
Date : December 2015
copyright : (C) 2015 by Hugo Mercier
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. *
* *
***************************************************************************/
"""
from PyQt4.QtGui import QApplication
from ..info_model import DatabaseInfo
from ..html_elems import HtmlTable
class LDatabaseInfo(DatabaseInfo):
def __init__(self, db):
self.db = db
def connectionDetails(self):
tbl = [
]
return HtmlTable(tbl)
def generalInfo(self):
info = self.db.connector.getInfo()
tbl = [
(QApplication.translate("DBManagerPlugin", "SQLite version:"), "3")
]
return HtmlTable(tbl)
def privilegesDetails(self):
return None

View File

@ -0,0 +1,191 @@
# -*- coding: utf-8 -*-
"""
/***************************************************************************
Name : DB Manager plugin for virtual layers
Date : December 2015
copyright : (C) 2015 by Hugo Mercier
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. *
* *
***************************************************************************/
"""
# this will disable the dbplugin if the connector raise an ImportError
from .connector import VLayerConnector
from PyQt4.QtCore import Qt, QSettings, QUrl
from PyQt4.QtGui import QIcon, QApplication, QAction
from qgis.core import QgsVectorLayer, QgsMapLayerRegistry
from qgis.gui import QgsMessageBar
from ..plugin import DBPlugin, Database, Table, VectorTable, RasterTable, TableField, TableIndex, TableTrigger, InvalidDataException
try:
from . import resources_rc
except ImportError:
pass
def classFactory():
return VLayerDBPlugin
class VLayerDBPlugin(DBPlugin):
@classmethod
def icon(self):
return QIcon(":/db_manager/vlayers/icon")
@classmethod
def typeName(self):
return 'vlayers'
@classmethod
def typeNameString(self):
return 'Virtual Layers'
@classmethod
def providerName(self):
return 'virtual'
@classmethod
def connectionSettingsKey(self):
return 'vlayers'
@classmethod
def connections(self):
return [VLayerDBPlugin('QGIS layers')]
def databasesFactory(self, connection, uri):
return FakeDatabase(connection, uri)
def database(self):
return self.db
# def info( self ):
def connect(self, parent=None):
self.connectToUri("qgis")
return True
class FakeDatabase(Database):
def __init__(self, connection, uri):
Database.__init__(self, connection, uri)
def connectorsFactory(self, uri):
return VLayerConnector(uri)
def dataTablesFactory(self, row, db, schema=None):
return LTable(row, db, schema)
def vectorTablesFactory(self, row, db, schema=None):
return LVectorTable(row, db, schema)
def rasterTablesFactory(self, row, db, schema=None):
return None
def info(self):
from .info_model import LDatabaseInfo
return LDatabaseInfo(self)
def sqlResultModel(self, sql, parent):
from .data_model import LSqlResultModel
return LSqlResultModel(self, sql, parent)
def toSqlLayer(self, sql, geomCol, uniqueCol, layerName="QueryLayer", layerType=None, avoidSelectById=False, _filter=""):
q = QUrl.toPercentEncoding(sql)
s = "?query=%s" % q
if uniqueCol is not None:
s += "&uid=" + uniqueCol
if geomCol is not None:
s += "&geometry=" + geomCol
vl = QgsVectorLayer(s, layerName, "virtual")
if _filter:
vl.setSubsetString(_filter)
return vl
def registerDatabaseActions(self, mainWindow):
return
def runAction(self, action):
return
def uniqueIdFunction(self):
return None
def explicitSpatialIndex(self):
return True
def spatialIndexClause(self, src_table, src_column, dest_table, dest_column):
return '"%s"._search_frame_ = "%s"."%s"' % (src_table, dest_table, dest_column)
class LTable(Table):
def __init__(self, row, db, schema=None):
Table.__init__(self, db, None)
self.name, self.isView, self.isSysTable = row
def tableFieldsFactory(self, row, table):
return LTableField(row, table)
def tableDataModel(self, parent):
from .data_model import LTableDataModel
return LTableDataModel(self, parent)
def canBeAddedToCanvas(self):
return False
class LVectorTable(LTable, VectorTable):
def __init__(self, row, db, schema=None):
LTable.__init__(self, row[:-5], db, schema)
VectorTable.__init__(self, db, schema)
# SpatiaLite does case-insensitive checks for table names, but the
# SL provider didn't do the same in QGis < 1.9, so self.geomTableName
# stores the table name like stored in the geometry_columns table
self.geomTableName, self.geomColumn, self.geomType, self.geomDim, self.srid = row[
-5:]
def uri(self):
uri = self.database().uri()
uri.setDataSource('', self.geomTableName, self.geomColumn)
return uri
def hasSpatialIndex(self, geom_column=None):
return True
def createSpatialIndex(self, geom_column=None):
return
def deleteSpatialIndex(self, geom_column=None):
return
def refreshTableEstimatedExtent(self):
self.extent = self.database().connector.getTableExtent(
("id", self.geomTableName), None)
def runAction(self, action):
return
def toMapLayer(self):
return QgsMapLayerRegistry.instance().mapLayer(self.geomTableName)
class LTableField(TableField):
def __init__(self, row, table):
TableField.__init__(self, table)
self.num, self.name, self.dataType, self.notNull, self.default, self.primaryKey = row
self.hasDefault = self.default

View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/db_manager/vlayers">
<file alias="icon">vlayer.svg</file>
</qresource>
</RCC>

View File

@ -0,0 +1,136 @@
# -*- coding: utf-8 -*-
# keywords
keywords = [
# TODO get them from a reference page
"action", "add", "after", "all", "alter", "analyze", "and", "as", "asc",
"before", "begin", "between", "by", "cascade", "case", "cast", "check",
"collate", "column", "commit", "constraint", "create", "cross", "current_date",
"current_time", "current_timestamp", "default", "deferrable", "deferred",
"delete", "desc", "distinct", "drop", "each", "else", "end", "escape",
"except", "exists", "for", "foreign", "from", "full", "group", "having",
"ignore", "immediate", "in", "initially", "inner", "insert", "intersect",
"into", "is", "isnull", "join", "key", "left", "like", "limit", "match",
"natural", "no", "not", "notnull", "null", "of", "offset", "on", "or", "order",
"outer", "primary", "references", "release", "restrict", "right", "rollback",
"row", "savepoint", "select", "set", "table", "temporary", "then", "to",
"transaction", "trigger", "union", "unique", "update", "using", "values",
"view", "when", "where",
"abort", "attach", "autoincrement", "conflict", "database", "detach",
"exclusive", "explain", "fail", "glob", "if", "index", "indexed", "instead",
"plan", "pragma", "query", "raise", "regexp", "reindex", "rename", "replace",
"temp", "vacuum", "virtual"
]
spatialite_keywords = []
# functions
functions = [
# TODO get them from a reference page
"changes", "coalesce", "glob", "ifnull", "hex", "last_insert_rowid",
"nullif", "quote", "random",
"randomblob", "replace", "round", "soundex", "total_change",
"typeof", "zeroblob", "date", "datetime", "julianday", "strftime"
]
operators = [
' AND ', ' OR ', '||', ' < ', ' <= ', ' > ', ' >= ', ' = ', ' <> ', ' IS ', ' IS NOT ', ' IN ', ' LIKE ', ' GLOB ', ' MATCH ', ' REGEXP '
]
math_functions = [
# SQL math functions
"Abs", "ACos", "ASin", "ATan", "Cos", "Cot", "Degrees", "Exp", "Floor", "Log", "Log2",
"Log10", "Pi", "Radians", "Round", "Sign", "Sin", "Sqrt", "StdDev_Pop", "StdDev_Samp", "Tan",
"Var_Pop", "Var_Samp"]
string_functions = ["Length", "Lower", "Upper", "Like", "Trim", "LTrim", "RTrim", "Replace", "Substr"]
aggregate_functions = [
"Max", "Min", "Avg", "Count", "Sum", "Group_Concat", "Total", "Var_Pop", "Var_Samp", "StdDev_Pop", "StdDev_Samp"
]
spatialite_functions = [ # from www.gaia-gis.it/spatialite-2.3.0/spatialite-sql-2.3.0.html
# SQL utility functions for BLOB objects
"*iszipblob", "*ispdfblob", "*isgifblob", "*ispngblob", "*isjpegblob", "*isexifblob",
"*isexifgpsblob", "*geomfromexifgpsblob", "MakePoint", "BuildMbr", "*buildcirclembr", "ST_MinX",
"ST_MinY", "ST_MaxX", "ST_MaxY",
# SQL functions for constructing a geometric object given its Well-known Text Representation
"ST_GeomFromText", "*pointfromtext",
# SQL functions for constructing a geometric object given its Well-known Binary Representation
"*geomfromwkb", "*pointfromwkb",
# SQL functions for obtaining the Well-known Text / Well-known Binary Representation of a geometric object
"ST_AsText", "ST_AsBinary",
# SQL functions supporting exotic geometric formats
"*assvg", "*asfgf", "*geomfromfgf",
# SQL functions on type Geometry
"ST_Dimension", "ST_GeometryType", "ST_Srid", "ST_SetSrid", "ST_isEmpty", "ST_isSimple", "ST_isValid", "ST_Boundary",
"ST_Envelope",
# SQL functions on type Point
"ST_X", "ST_Y",
# SQL functions on type Curve [Linestring or Ring]
"ST_StartPoint", "ST_EndPoint", "ST_Length", "ST_isClosed", "ST_isRing", "ST_Simplify",
"*simplifypreservetopology",
# SQL functions on type LineString
"ST_NumPoints", "ST_PointN",
# SQL functions on type Surface [Polygon or Ring]
"ST_Centroid", "ST_PointOnSurface", "ST_Area",
# SQL functions on type Polygon
"ST_ExteriorRing", "ST_InteriorRingN",
# SQL functions on type GeomCollection
"ST_NumGeometries", "ST_GeometryN",
# SQL functions that test approximative spatial relationships via MBRs
"MbrEqual", "MbrDisjoint", "MbrTouches", "MbrWithin", "MbrOverlaps", "MbrIntersects",
"MbrContains",
# SQL functions that test spatial relationships
"ST_Equals", "ST_Disjoint", "ST_Touches", "ST_Within", "ST_Overlaps", "ST_Crosses", "ST_Intersects", "ST_Contains",
"ST_Relate",
# SQL functions for distance relationships
"ST_Distance",
# SQL functions that implement spatial operators
"ST_Intersection", "ST_Difference", "ST_Union", "ST_SymDifference", "ST_Buffer", "ST_ConvexHull",
# SQL functions for coordinate transformations
"ST_Transform",
# SQL functions for Spatial-MetaData and Spatial-Index handling
"*initspatialmetadata", "*addgeometrycolumn", "*recovergeometrycolumn", "*discardgeometrycolumn",
"*createspatialindex", "*creatembrcache", "*disablespatialindex",
# SQL functions implementing FDO/OGR compatibily
"*checkspatialmetadata", "*autofdostart", "*autofdostop", "*initfdospatialmetadata",
"*addfdogeometrycolumn", "*recoverfdogeometrycolumn", "*discardfdogeometrycolumn",
# SQL functions for MbrCache-based queries
"*filtermbrwithin", "*filtermbrcontains", "*filtermbrintersects", "*buildmbrfilter"
]
# constants
constants = ["null", "false", "true"]
spatialite_constants = []
def getSqlDictionary(spatial=True):
def strip_star(s):
if s[0] == '*':
return s.lower()[1:]
else:
return s.lower()
k, c, f = list(keywords), list(constants), list(functions)
if spatial:
k += spatialite_keywords
f += spatialite_functions
c += spatialite_constants
return {'keyword': map(strip_star, k), 'constant': map(strip_star, c), 'function': map(strip_star, f)}
def getQueryBuilderDictionary():
# concat functions
def ff(l):
return filter(lambda s: s[0] != '*', l)
def add_paren(l):
return map(lambda s: s + "(", l)
foo = sorted(add_paren(ff(list(set.union(set(functions), set(spatialite_functions))))))
m = sorted(add_paren(ff(math_functions)))
agg = sorted(add_paren(ff(aggregate_functions)))
op = ff(operators)
s = sorted(add_paren(ff(string_functions)))
return {'function': foo, 'math': m, 'aggregate': agg, 'operator': op, 'string': s}

View File

@ -0,0 +1,242 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="32px"
height="32px"
id="svg5692"
version="1.1"
inkscape:version="0.48.4 r9939"
sodipodi:docname="vlayer.svg"
inkscape:export-filename="/media/home1/robert/svn/graphics/trunk/toolbar-icons/32x32/layer-vector.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<title
id="title2829">GIS icon theme 0.2</title>
<defs
id="defs5694">
<linearGradient
id="linearGradient3812">
<stop
style="stop-color:#6e97c4;stop-opacity:1;"
offset="0"
id="stop3814" />
<stop
style="stop-color:#6e97c4;stop-opacity:0;"
offset="1"
id="stop3816" />
</linearGradient>
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 16 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="32 : 16 : 1"
inkscape:persp3d-origin="16 : 10.666667 : 1"
id="perspective3486" />
<inkscape:perspective
id="perspective3496"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective3600"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective7871"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective8710"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective9811"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective4762"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3812"
id="linearGradient3818"
x1="37.316311"
y1="36.925365"
x2="3.1572213"
y2="3.1572211"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="15.9375"
inkscape:cx="8.4239343"
inkscape:cy="11.653661"
inkscape:current-layer="layer2"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
borderlayer="false"
inkscape:window-width="1377"
inkscape:window-height="807"
inkscape:window-x="1932"
inkscape:window-y="134"
inkscape:window-maximized="0"
inkscape:snap-global="true"
showguides="true"
inkscape:guide-bbox="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-grids="false">
<inkscape:grid
type="xygrid"
id="grid5700"
empspacing="5"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true"
dotted="true"
originx="2.5px"
originy="2.5px" />
</sodipodi:namedview>
<metadata
id="metadata5697">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>GIS icon theme 0.2</dc:title>
<dc:creator>
<cc:Agent>
<dc:title>Robert Szczepanek</dc:title>
</cc:Agent>
</dc:creator>
<dc:rights>
<cc:Agent>
<dc:title>Robert Szczepanek</dc:title>
</cc:Agent>
</dc:rights>
<dc:subject>
<rdf:Bag>
<rdf:li>GIS icons</rdf:li>
</rdf:Bag>
</dc:subject>
<dc:coverage>GIS icons</dc:coverage>
<dc:description>http://robert.szczepanek.pl/</dc:description>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/3.0/" />
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-sa/3.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF>
</metadata>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Layer"
style="display:inline">
<path
inkscape:connector-curvature="0"
style="color:#000000;fill:none;stroke:#415a75;stroke-width:1.78710628;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
d="M 3.1572212,3.1572206 11.707585,23.749318 21.110969,3.3492624 28.84278,3.3200046"
id="path2960"
sodipodi:nodetypes="cccc" />
<path
sodipodi:type="arc"
style="color:#000000;fill:#eeeeec;fill-opacity:1;fill-rule:evenodd;stroke:#415a75;stroke-width:0.78947365;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="path2958"
sodipodi:cx="3.5"
sodipodi:cy="12.5"
sodipodi:rx="1"
sodipodi:ry="1"
d="m 4.5,12.5 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
transform="matrix(2.263668,0,0,2.263668,-4.7656167,-25.138629)" />
<path
sodipodi:type="arc"
style="color:#000000;fill:#eeeeec;fill-opacity:1;fill-rule:evenodd;stroke:#415a75;stroke-width:1.05263162;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="path2958-0"
sodipodi:cx="3.5"
sodipodi:cy="12.5"
sodipodi:rx="1"
sodipodi:ry="1"
d="m 4.5,12.5 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
transform="matrix(2.263668,0,0,2.263668,3.8127783,-5.6260389)" />
<path
sodipodi:type="arc"
style="color:#000000;fill:#eeeeec;fill-opacity:1;fill-rule:evenodd;stroke:#415a75;stroke-width:0.78947365;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="path2958-9"
sodipodi:cx="3.5"
sodipodi:cy="12.5"
sodipodi:rx="1"
sodipodi:ry="1"
d="m 4.5,12.5 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
transform="matrix(2.263668,0,0,2.263668,12.949214,-24.994533)" />
<path
sodipodi:type="arc"
style="color:#000000;fill:#eeeeec;fill-opacity:1;fill-rule:evenodd;stroke:#415a75;stroke-width:0.78947365;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="path2958-6"
sodipodi:cx="3.5"
sodipodi:cy="12.5"
sodipodi:rx="1"
sodipodi:ry="1"
d="m 4.5,12.5 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
transform="matrix(2.263668,0,0,2.263668,20.919941,-24.975845)" />
<path
style="color:#000000;fill:url(#linearGradient3818);fill-opacity:1;stroke:#2e4e72;stroke-width:1.12999999999999989;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;fill-rule:nonzero;opacity:1"
d="m 4.8072919,3.0420338 22.3854161,0 c 1.042824,0 1.875,0.832177 1.875,1.875001 l 0,22.1971812 c 0,1.042824 -0.832176,1.90625 -1.875,1.90625 l -22.3854161,0 c -1.0428239,0 -1.875,-0.863426 -1.875,-1.90625 l 0,-22.1971812 c 0,-1.042824 0.8321761,-1.875001 1.875,-1.875001 z"
id="rect3022"
inkscape:connector-curvature="0"
sodipodi:nodetypes="sssssssss" />
<path
style="color:#000000;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.12999999999999989;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
d="m 0,0 0,32.0625 32,0 L 32,0 z m 4.7761029,3.0844363 22.3854161,0 c 1.042824,0 1.875,0.8321765 1.875,1.8750003 l 0,22.1971814 c 0,1.042824 -0.832176,1.90625 -1.875,1.90625 l -22.3854161,0 c -1.0428235,0 -1.875,-0.863426 -1.875,-1.90625 l 0,-22.1971814 c 0,-1.0428238 0.8321765,-1.8750003 1.875,-1.8750003 z"
id="path3078"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccsssssssss" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -202,6 +202,7 @@ SET(QGIS_CORE_SRCS
qgsvectorlayerundocommand.cpp
qgsvectorsimplifymethod.cpp
qgsvisibilitypresetcollection.cpp
qgsvirtuallayerdefinition.cpp
qgsxmlutils.cpp
qgsslconnect.cpp
qgslocalec.cpp

View File

@ -0,0 +1,250 @@
/***************************************************************************
qgsvirtuallayerdefinition.cpp
begin : December 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 <QUrl>
#include <QRegExp>
#include <QStringList>
#include "qgsvirtuallayerdefinition.h"
QgsVirtualLayerDefinition::QgsVirtualLayerDefinition( const QString& filePath ) :
mFilePath( filePath ),
mGeometryWkbType( QgsWKBTypes::Unknown ),
mGeometrySrid( 0 )
{
}
QgsVirtualLayerDefinition QgsVirtualLayerDefinition::fromUrl( const QUrl& url )
{
QgsVirtualLayerDefinition def;
def.setFilePath( url.path() );
// regexp for column name
const QString columnNameRx( "[a-zA-Z_\x80-\xFF][a-zA-Z0-9_\x80-\xFF]*" );
QgsFields fields;
int layerIdx = 0;
QList<QPair<QString, QString> > items = url.queryItems();
for ( int i = 0; i < items.size(); i++ )
{
QString key = items.at( i ).first;
QString value = items.at( i ).second;
if ( key == "layer_ref" )
{
layerIdx++;
// layer id, with optional layer_name
int pos = value.indexOf( ':' );
QString layerId, vlayerName;
if ( pos == -1 )
{
layerId = value;
vlayerName = QString( "vtab%1" ).arg( layerIdx );
}
else
{
layerId = value.left( pos );
vlayerName = QUrl::fromPercentEncoding( value.mid( pos + 1 ).toUtf8() );
}
// add the layer to the list
def.addSource( vlayerName, layerId );
}
else if ( key == "layer" )
{
layerIdx++;
// syntax: layer=provider:url_encoded_source_URI(:name(:encoding)?)?
int pos = value.indexOf( ':' );
if ( pos != -1 )
{
QString providerKey, source, vlayerName, encoding = "UTF-8";
providerKey = value.left( pos );
int pos2 = value.indexOf( ':', pos + 1 );
if ( pos2 != -1 )
{
source = QUrl::fromPercentEncoding( value.mid( pos + 1, pos2 - pos - 1 ).toUtf8() );
int pos3 = value.indexOf( ':', pos2 + 1 );
if ( pos3 != -1 )
{
vlayerName = QUrl::fromPercentEncoding( value.mid( pos2 + 1, pos3 - pos2 - 1 ).toUtf8() );
encoding = value.mid( pos3 + 1 );
}
else
{
vlayerName = QUrl::fromPercentEncoding( value.mid( pos2 + 1 ).toUtf8() );
}
}
else
{
source = QUrl::fromPercentEncoding( value.mid( pos + 1 ).toUtf8() );
vlayerName = QString( "vtab%1" ).arg( layerIdx );
}
def.addSource( vlayerName, source, providerKey, encoding );
}
}
else if ( key == "geometry" )
{
// geometry field definition, optional
// geometry_column(:wkb_type:srid)?
QRegExp reGeom( "(" + columnNameRx + ")(?::([a-zA-Z0-9]+):(\\d+))?" );
int pos = reGeom.indexIn( value );
if ( pos >= 0 )
{
def.setGeometryField( reGeom.cap( 1 ) );
if ( reGeom.captureCount() > 1 )
{
// not used by the spatialite provider for now ...
QgsWKBTypes::Type wkbType = QgsWKBTypes::parseType( reGeom.cap( 2 ) );
if ( wkbType == QgsWKBTypes::Unknown )
{
wkbType = static_cast<QgsWKBTypes::Type>( reGeom.cap( 2 ).toLong() );
}
def.setGeometryWkbType( wkbType );
def.setGeometrySrid( reGeom.cap( 3 ).toLong() );
}
}
}
else if ( key == "nogeometry" )
{
def.setGeometryWkbType( QgsWKBTypes::NoGeometry );
}
else if ( key == "uid" )
{
def.setUid( value );
}
else if ( key == "query" )
{
// url encoded query
def.setQuery( value );
}
else if ( key == "field" )
{
// field_name:type (int, real, text)
QRegExp reField( "(" + columnNameRx + "):(int|real|text)" );
int pos = reField.indexIn( value );
if ( pos >= 0 )
{
QString fieldName( reField.cap( 1 ) );
QString fieldType( reField.cap( 2 ) );
if ( fieldType == "int" )
{
fields.append( QgsField( fieldName, QVariant::Int, fieldType ) );
}
else if ( fieldType == "real" )
{
fields.append( QgsField( fieldName, QVariant::Double, fieldType ) );
}
if ( fieldType == "text" )
{
fields.append( QgsField( fieldName, QVariant::String, fieldType ) );
}
}
}
}
def.setFields( fields );
return def;
}
QUrl QgsVirtualLayerDefinition::toUrl() const
{
QUrl url;
url.setPath( filePath() );
foreach ( const QgsVirtualLayerDefinition::SourceLayer& l, sourceLayers() )
{
if ( l.isReferenced() )
url.addQueryItem( "layer_ref", QString( "%1:%2" ).arg( l.reference() ).arg( l.name() ) );
else
url.addQueryItem( "layer", QString( "%1:%4:%2:%3" ) // the order is important, since the 4th argument may contain '%2' as well
.arg( l.provider() )
.arg( QString( QUrl::toPercentEncoding( l.name() ) ) )
.arg( l.encoding() )
.arg( QString( QUrl::toPercentEncoding( l.source() ) ) ) );
}
if ( !query().isEmpty() )
{
url.addQueryItem( "query", query() );
}
if ( !uid().isEmpty() )
url.addQueryItem( "uid", uid() );
if ( geometryWkbType() == QgsWKBTypes::NoGeometry )
url.addQueryItem( "nogeometry", "" );
else if ( !geometryField().isEmpty() )
{
if ( hasDefinedGeometry() )
url.addQueryItem( "geometry", QString( "%1:%2:%3" ).arg( geometryField() ). arg( geometryWkbType() ).arg( geometrySrid() ).toUtf8() );
else
url.addQueryItem( "geometry", geometryField() );
}
for ( int i = 0; i < fields().count(); i++ )
{
const QgsField& f = fields()[i];
if ( f.type() == QVariant::Int )
url.addQueryItem( "field", f.name() + ":int" );
else if ( f.type() == QVariant::Double )
url.addQueryItem( "field", f.name() + ":real" );
else if ( f.type() == QVariant::String )
url.addQueryItem( "field", f.name() + ":text" );
}
return url;
}
QString QgsVirtualLayerDefinition::toString() const
{
return QString( toUrl().toEncoded() );
}
void QgsVirtualLayerDefinition::addSource( const QString& name, const QString ref )
{
mSourceLayers.append( SourceLayer( name, ref ) );
}
void QgsVirtualLayerDefinition::addSource( const QString& name, const QString source, const QString& provider, const QString& encoding )
{
mSourceLayers.append( SourceLayer( name, source, provider, encoding ) );
}
bool QgsVirtualLayerDefinition::hasSourceLayer( QString name ) const
{
foreach ( const QgsVirtualLayerDefinition::SourceLayer& l, sourceLayers() )
{
if ( l.name() == name )
{
return true;
}
}
return false;
}
bool QgsVirtualLayerDefinition::hasReferencedLayers() const
{
foreach ( const QgsVirtualLayerDefinition::SourceLayer& l, sourceLayers() )
{
if ( l.isReferenced() )
{
return true;
}
}
return false;
}

View File

@ -0,0 +1,166 @@
/***************************************************************************
qgsvirtuallayerdefinition.h
begin : Feb, 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. *
* *
***************************************************************************/
#ifndef QGSVIRTUALLAYERDEFINITION_H
#define QGSVIRTUALLAYERDEFINITION_H
#include <qgsfield.h>
#include <qgis.h>
/**
* Class to manipulate the definition of a virtual layer
*
* It is used to extract parameters from an initial virtual layer definition as well as
* to store the complete, expanded definition once types have been detected.
*/
class CORE_EXPORT QgsVirtualLayerDefinition
{
public:
/**
* A SourceLayer is either a reference to a live layer in the registry
* or all the parameters needed to load it (provider key, source, etc.)
*/
class CORE_EXPORT SourceLayer
{
public:
//! Constructor variant to build a live layer reference
SourceLayer( const QString& name, const QString& ref ) : mName( name ), mRef( ref ) {}
//! Constructor variant to build a layer with a provider and a source
SourceLayer( const QString& name, const QString& source, const QString& provider, const QString& encoding )
: mName( name ), mSource( source ), mProvider( provider ), mEncoding( encoding ) {}
//! Is it a live layer or not ?
bool isReferenced() const { return !mRef.isEmpty(); }
//! The reference (id) of the live layer
QString reference() const { return mRef; }
//! Name of the layer
QString name() const { return mName; }
//! Provider key
QString provider() const { return mProvider; }
//! The source url used by the provider to build the layer
QString source() const { return mSource; }
//! Optional encoding for the provider
QString encoding() const { return mEncoding; }
private:
QString mName;
QString mSource;
QString mProvider;
QString mRef;
QString mEncoding;
};
//! Constructor with an optional file path
QgsVirtualLayerDefinition( const QString& filePath = "" );
//! Constructor to build a definition from a QUrl
//! The path part of the URL is extracted as well as the following optional keys:
//! layer_ref=layer_id[:name] represents a live layer referenced by its ID. An optional name can be given
//! layer=provider:source[:name[:encoding]] represents a layer given by its provider key, its source url (URL-encoded).
//! An optional name and encoding can be given
//! geometry=column_name[:type:srid] gives the definition of the geometry column.
//! Type can be either a WKB type code or a string (point, linestring, etc.)
//! srid is an integer
//! uid=column_name is the name of a column with unique integer values.
//! nogeometry is a flag to force the layer to be a non-geometry layer
//! query=sql represents the SQL query. Must be URL-encoded
//! field=column_name:[int|real|text] represents a field with its name and its type
static QgsVirtualLayerDefinition fromUrl( const QUrl& url );
//! Convert the definition into a QUrl
QUrl toUrl() const;
//! Convert into a QString that can be read by the virtual layer provider
QString toString() const;
//! Add a live layer source layer
void addSource( const QString& name, const QString ref );
//! Add a layer with a source, a provider and an encoding
void addSource( const QString& name, const QString source, const QString& provider, const QString& encoding = "" );
//! List of source layers
typedef QList<SourceLayer> SourceLayers;
//! Get access to the source layers
const SourceLayers& sourceLayers() const { return mSourceLayers; }
//! Get the SQL query
QString query() const { return mQuery; }
//! Set the SQL query
void setQuery( const QString& query ) { mQuery = query; }
//! Get the file path. May be empty
QString filePath() const { return mFilePath; }
//! Set the file path
void setFilePath( const QString& filePath ) { mFilePath = filePath; }
//! Get the name of the field with unique identifiers
QString uid() const { return mUid; }
//! Set the name of the field with unique identifiers
void setUid( const QString& uid ) { mUid = uid; }
//! Get the name of the geometry field. Empty if no geometry field
QString geometryField() const { return mGeometryField; }
//! Set the name of the geometry field
void setGeometryField( const QString& geometryField ) { mGeometryField = geometryField; }
//! Get the type of the geometry
//! QgsWKBTypes::NoGeometry to hide any geometry
//! QgsWKBTypes::Unknown for unknown types
QgsWKBTypes::Type geometryWkbType() const { return mGeometryWkbType; }
//! Set the type of the geometry
void setGeometryWkbType( QgsWKBTypes::Type t ) { mGeometryWkbType = t; }
//! Get the SRID of the geometry
long geometrySrid() const { return mGeometrySrid; }
//! Set the SRID of the geometry
void setGeometrySrid( long srid ) { mGeometrySrid = srid; }
//! Get field definitions
const QgsFields& fields() const { return mFields; }
//! Set field definitions
void setFields( const QgsFields& fields ) { mFields = fields; }
//! Convenience method to test if a given source layer is part of the definition
bool hasSourceLayer( QString name ) const;
//! Convenience method to test whether the definition has referenced (live) layers
bool hasReferencedLayers() const;
//! Convenient method to test if the geometry is defined (not NoGeometry and not Unknown)
bool hasDefinedGeometry() const
{
return geometryWkbType() != QgsWKBTypes::NoGeometry && geometryWkbType() != QgsWKBTypes::Unknown;
}
private:
SourceLayers mSourceLayers;
QString mQuery;
QString mUid;
QString mGeometryField;
QString mFilePath;
QgsFields mFields;
QgsWKBTypes::Type mGeometryWkbType;
long mGeometrySrid;
};
#endif

View File

@ -13,6 +13,7 @@ ADD_SUBDIRECTORY(wcs)
ADD_SUBDIRECTORY(gpx)
ADD_SUBDIRECTORY(wfs)
ADD_SUBDIRECTORY(spatialite)
ADD_SUBDIRECTORY(virtual)
IF (WITH_ORACLE)
ADD_SUBDIRECTORY(oracle)

View File

@ -0,0 +1,56 @@
########################################################
# Files
QT4_WRAP_CPP(vlayer_provider_MOC_SRCS qgsvirtuallayerprovider.h qgsslottofunction.h
)
QT4_WRAP_UI(vlayer_provider_UI_H qgsvirtuallayersourceselectbase.ui qgsembeddedlayerselect.ui)
SET(QGIS_VLAYER_PROVIDER_SRCS
${vlayer_provider_MOC_SRCS}
qgsvirtuallayerprovider.cpp
qgsvirtuallayerfeatureiterator.cpp
qgsvirtuallayerblob.cpp
qgsvirtuallayersqlitemodule.cpp
qgsvirtuallayersqlitehelper.cpp
qgsvirtuallayerqueryparser.cpp
)
ADD_LIBRARY(virtuallayerprovider MODULE
${QGIS_VLAYER_PROVIDER_SRCS}
)
TARGET_LINK_LIBRARIES( virtuallayerprovider
qgis_core
qgis_gui
${QT_QTCORE_LIBRARY}
${QT_QTGUI_LIBRARY}
${SQLITE3_LIBRARY}
${SPATIALITE_LIBRARY}
)
INCLUDE_DIRECTORIES(
.
../../core
../../core/auth
../../core/geometry
)
INCLUDE_DIRECTORIES(SYSTEM
${POSTGRES_INCLUDE_DIR}
${GEOS_INCLUDE_DIR}
${QSCINTILLA_INCLUDE_DIR}
${QCA_INCLUDE_DIR}
)
INCLUDE_DIRECTORIES(
../../core
../../gui
../../gui/auth
../../ui
${CMAKE_CURRENT_BINARY_DIR}/../../ui
)
INSTALL(TARGETS virtuallayerprovider
RUNTIME DESTINATION ${QGIS_PLUGIN_DIR}
LIBRARY DESTINATION ${QGIS_PLUGIN_DIR}
)

View File

@ -0,0 +1,25 @@
#ifndef QGSSLOT_TO_FUNCTION_H
#define QGSSLOT_TO_FUNCTION_H
#include <QObject>
/**
* This is an helper Qt object used by the SQLite virtual layer module
* in order to connect to the deletion signal of a vector layer,
* since internal classes of the SQLite module cannot derive from QObject
*/
class QgsSlotToFunction : public QObject
{
Q_OBJECT
public:
QgsSlotToFunction() : mCallback( nullptr ), mArg( nullptr ) {}
QgsSlotToFunction( void ( *callback )( void* ), void* arg ) : mCallback( callback ), mArg( arg ) {}
private slots:
void onSignal() { if ( mCallback ) mCallback( mArg ); }
private:
void ( *mCallback )( void* );
void* mArg;
};
#endif

View File

@ -0,0 +1,220 @@
/***************************************************************************
qgsvirtuallayerblob.cpp : Functions to manipulate Spatialite geometry blobs
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 "qgsvirtuallayerblob.h"
#include <string.h>
SpatialiteBlobHeader::SpatialiteBlobHeader() :
start( 0x00 ), endianness( 0x01 ), end( 0x7C )
{}
void SpatialiteBlobHeader::readFrom( const char* p )
{
// we cannot use directly memcpy( this, p, sizeof(this) ),
// since there may be padding between struct members
memcpy( &start, p, 1 ); p++;
memcpy( &endianness, p, 1 ); p++;
memcpy( &srid, p, 4 ); p += 4;
memcpy( &mbrMinX, p, 8 ); p += 8;
memcpy( &mbrMinY, p, 8 ); p += 8;
memcpy( &mbrMaxX, p, 8 ); p += 8;
memcpy( &mbrMaxY, p, 8 ); p += 8;
memcpy( &end, p, 1 );
}
void SpatialiteBlobHeader::writeTo( char* p ) const
{
// we cannot use directly memcpy( this, p, sizeof(this) ),
// since there may be padding between struct members
memcpy( p, &start, 1 ); p++;
memcpy( p, &endianness, 1 ); p++;
memcpy( p, &srid, 4 ); p += 4;
memcpy( p, &mbrMinX, 8 ); p += 8;
memcpy( p, &mbrMinY, 8 ); p += 8;
memcpy( p, &mbrMaxX, 8 ); p += 8;
memcpy( p, &mbrMaxY, 8 ); p += 8;
memcpy( p, &end, 1 );
}
//
// Convert a QgsGeometry into a Spatialite geometry BLOB
void qgsGeometryToSpatialiteBlob( const QgsGeometry& geom, int32_t srid, char *&blob, size_t& size )
{
const size_t header_len = SpatialiteBlobHeader::length;
const size_t wkb_size = geom.wkbSize();
size = header_len + wkb_size;
blob = new char[size];
char* p = blob;
// write the header
SpatialiteBlobHeader pHeader;
QgsRectangle bbox = const_cast<QgsGeometry&>( geom ).boundingBox(); // boundingBox should be const
pHeader.srid = srid;
pHeader.mbrMinX = bbox.xMinimum();
pHeader.mbrMinY = bbox.yMinimum();
pHeader.mbrMaxX = bbox.xMaximum();
pHeader.mbrMaxY = bbox.yMaximum();
pHeader.writeTo( blob );
p += header_len;
// wkb of the geometry is
// name size value
// endianness 1 01
// type 4 int
// blob geometry = header + wkb[1:] + 'end'
// copy wkb
const unsigned char* wkb = geom.asWkb();
memcpy( p, wkb + 1, wkb_size - 1 );
p += wkb_size - 1;
// end marker
*p = 0xFE;
}
//
// Return the bouding box of a spatialite geometry blob
QgsRectangle spatialiteBlobBbox( const char* blob, size_t size )
{
Q_UNUSED( size );
SpatialiteBlobHeader h;
h.readFrom( blob );
return QgsRectangle( h.mbrMinX, h.mbrMinY, h.mbrMaxX, h.mbrMaxY );
}
void copySpatialiteSingleWkbToQgsGeometry( QgsWKBTypes::Type type, const char* iwkb, char* owkb, uint32_t& osize )
{
int n_dims = QgsWKBTypes::coordDimensions( type );
switch ( QgsWKBTypes::flatType( type ) )
{
case QgsWKBTypes::Point:
memcpy( owkb, iwkb, n_dims*8 );
iwkb += n_dims * 8;
iwkb += n_dims * 8;
osize = n_dims * 8;
break;
case QgsWKBTypes::LineString:
{
uint32_t n_points = *( uint32_t* )iwkb;
memcpy( owkb, iwkb, 4 );
iwkb += 4; owkb += 4;
for ( uint32_t i = 0; i < n_points; i++ )
{
memcpy( owkb, iwkb, n_dims*8 );
iwkb += n_dims * 8;
owkb += n_dims * 8;
}
osize += n_dims * 8 * n_points + 4;
break;
}
case QgsWKBTypes::Polygon:
{
uint32_t n_rings = *( uint32_t* )iwkb;
memcpy( owkb, iwkb, 4 );
iwkb += 4; owkb += 4;
osize = 4;
for ( uint32_t i = 0; i < n_rings; i++ )
{
uint32_t n_points = *( uint32_t* )iwkb;
memcpy( owkb, iwkb, 4 );
iwkb += 4; owkb += 4;
osize += 4;
for ( uint32_t j = 0; j < n_points; j++ )
{
memcpy( owkb, iwkb, n_dims*8 );
iwkb += n_dims * 8;
owkb += n_dims * 8;
osize += n_dims * 8;
}
}
break;
}
default:
break;
}
}
//
// copy the spatialite blob to wkb for qgsgeometry
// the only difference is
// each spatialite sub geometry begins with the byte 0x69 (ENTITY)
// which should be converted to an endianness code
void copySpatialiteCollectionWkbToQgsGeometry( const char* iwkb, char* owkb, uint32_t& osize, int endianness )
{
// copy first byte + type
memcpy( owkb, iwkb, 5 );
// replace 0x69 by the endianness
owkb[0] = endianness;
QgsWKBTypes::Type type = static_cast<QgsWKBTypes::Type>( *( uint32_t* )( iwkb + 1 ) );
if ( QgsWKBTypes::isMultiType( type ) )
{
// multi type
uint32_t n_elements = *( uint32_t* )( iwkb + 5 );
memcpy( owkb + 5, iwkb + 5, 4 );
uint32_t p = 0;
for ( uint32_t i = 0; i < n_elements; i++ )
{
uint32_t rsize = 0;
copySpatialiteCollectionWkbToQgsGeometry( iwkb + 9 + p, owkb + 9 + p, rsize, endianness );
p += rsize;
}
osize = p + 9;
}
else
{
osize = 0;
copySpatialiteSingleWkbToQgsGeometry( type, iwkb + 5, owkb + 5, osize );
osize += 5;
}
}
QgsGeometry spatialiteBlobToQgsGeometry( const char* blob, size_t size )
{
const size_t header_size = SpatialiteBlobHeader::length;
const size_t wkb_size = size - header_size;
char* wkb = new char[wkb_size];
uint32_t osize = 0;
copySpatialiteCollectionWkbToQgsGeometry( blob + header_size - 1, wkb, osize, /*endianness*/blob[1] );
QgsGeometry geom;
geom.fromWkb(( unsigned char* )wkb, wkb_size );
return geom;
}
QPair<QgsWKBTypes::Type, long> spatialiteBlobGeometryType( const char* blob, size_t size )
{
if ( size < SpatialiteBlobHeader::length + 4 ) // the header + the type on 4 bytes
{
return qMakePair( QgsWKBTypes::NoGeometry, long( 0 ) );
}
uint32_t srid = *( uint32_t* )( blob + 2 );
uint32_t type = *( uint32_t* )( blob + SpatialiteBlobHeader::length );
return qMakePair( static_cast<QgsWKBTypes::Type>( type ), long( srid ) );
}

View File

@ -0,0 +1,71 @@
/***************************************************************************
qgsvirtuallayerblob.h : Functions to manipulate Spatialite geometry blobs
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. *
* *
***************************************************************************/
#ifndef QGSVIRTUALLAYER_BLOB_H
#define QGSVIRTUALLAYER_BLOB_H
#include <stdint.h>
#include <qgsgeometry.h>
// BLOB header
// name size value
// start 1 00
// endian 1 01
// srid 4 int
// mbr_min_x 8 double
// mbr_min_y 8 double
// mbr_max_x 8 double
// mbr_max_y 8 double
// mbr_end 1 7C
struct SpatialiteBlobHeader
{
unsigned char start;
unsigned char endianness;
uint32_t srid;
double mbrMinX;
double mbrMinY;
double mbrMaxX;
double mbrMaxY;
unsigned char end;
SpatialiteBlobHeader();
static const size_t length = 39;
void readFrom( const char* p );
void writeTo( char* p ) const;
};
//!
//! Convert a QgsGeometry into a Spatialite geometry BLOB
//! The blob will be allocated and must be handled by the caller
void qgsGeometryToSpatialiteBlob( const QgsGeometry& geom, int32_t srid, char *&blob, size_t& size );
//!
//! Return the bouding box of a spatialite geometry blob
QgsRectangle spatialiteBlobBbox( const char* blob, size_t size );
//!
//! Convert a Spatialite geometry BLOB to a QgsGeometry
QgsGeometry spatialiteBlobToQgsGeometry( const char* blob, size_t size );
//!
//! Get geometry type and srid from a spatialite geometry blob
QPair<QgsWKBTypes::Type, long> spatialiteBlobGeometryType( const char* blob, size_t size );
#endif

View File

@ -0,0 +1,236 @@
/***************************************************************************
qgsvirtuallayerfeatureiterator.cpp
Feature iterator for the virtual layer provider
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 <qgsvirtuallayerfeatureiterator.h>
#include <qgsmessagelog.h>
#include <qgsgeometry.h>
#include <stdexcept>
#include "qgsvirtuallayerblob.h"
static QString quotedColumn( QString name )
{
return "\"" + name.replace( "\"", "\"\"" ) + "\"";
}
QgsVirtualLayerFeatureIterator::QgsVirtualLayerFeatureIterator( QgsVirtualLayerFeatureSource* source, bool ownSource, const QgsFeatureRequest& request )
: QgsAbstractFeatureIteratorFromSource<QgsVirtualLayerFeatureSource>( source, ownSource, request )
{
try
{
mSqlite = mSource->provider()->mSqlite.get();
mDefinition = mSource->provider()->mDefinition;
QString tableName = mSource->provider()->mTableName;
QStringList wheres;
QString subset = mSource->provider()->mSubset;
if ( !subset.isNull() )
{
wheres << subset;
}
if ( mDefinition.hasDefinedGeometry() && !request.filterRect().isNull() )
{
bool do_exact = request.flags() & QgsFeatureRequest::ExactIntersect;
QgsRectangle rect( request.filterRect() );
QString mbr = QString( "%1,%2,%3,%4" ).arg( rect.xMinimum() ).arg( rect.yMinimum() ).arg( rect.xMaximum() ).arg( rect.yMaximum() );
wheres << quotedColumn( mDefinition.geometryField() ) + " is not null";
wheres << QString( "%1Intersects(%2,BuildMbr(%3))" )
.arg( do_exact ? "" : "Mbr" )
.arg( quotedColumn( mDefinition.geometryField() ) )
.arg( mbr );
}
else if ( !mDefinition.uid().isNull() && request.filterType() == QgsFeatureRequest::FilterFid )
{
wheres << QString( "%1=%2" )
.arg( quotedColumn( mDefinition.uid() ) )
.arg( request.filterFid() );
}
else if ( !mDefinition.uid().isNull() && request.filterType() == QgsFeatureRequest::FilterFids )
{
QString values = quotedColumn( mDefinition.uid() ) + " IN (";
bool first = true;
foreach ( auto& v, request.filterFids() )
{
if ( !first )
{
values += ",";
}
first = false;
values += QString::number( v );
}
values += ")";
wheres << values;
}
mFields = mSource->provider()->fields();
if ( request.flags() & QgsFeatureRequest::SubsetOfAttributes )
{
// copy only selected fields
foreach ( int idx, request.subsetOfAttributes() )
{
mAttributes << idx;
}
}
else
{
mAttributes = mFields.allAttributesList();
}
QString columns;
{
// the first column is always the uid (or 0)
if ( !mDefinition.uid().isNull() )
{
columns = quotedColumn( mDefinition.uid() );
}
else
{
columns = "0";
}
foreach ( int i, mAttributes )
{
columns += ",";
QString cname = mFields.at( i ).name().toLower();
columns += quotedColumn( cname );
}
}
// the last column is the geometry, if any
if ( !( request.flags() & QgsFeatureRequest::NoGeometry ) && !mDefinition.geometryField().isNull() && mDefinition.geometryField() != "*no*" )
{
columns += "," + quotedColumn( mDefinition.geometryField() );
}
mSqlQuery = "SELECT " + columns + " FROM " + tableName;
if ( !wheres.isEmpty() )
{
mSqlQuery += " WHERE " + wheres.join( " AND " );
}
mQuery.reset( new Sqlite::Query( mSqlite, mSqlQuery ) );
mFid = 0;
}
catch ( std::runtime_error& e )
{
QgsMessageLog::logMessage( e.what(), QObject::tr( "VLayer" ) );
close();
}
}
QgsVirtualLayerFeatureIterator::~QgsVirtualLayerFeatureIterator()
{
close();
}
bool QgsVirtualLayerFeatureIterator::rewind()
{
if ( mClosed )
{
return false;
}
mQuery->reset();
return true;
}
bool QgsVirtualLayerFeatureIterator::close()
{
if ( mClosed )
{
return false;
}
// this call is absolutely needed
iteratorClosed();
mClosed = true;
return true;
}
bool QgsVirtualLayerFeatureIterator::fetchFeature( QgsFeature& feature )
{
if ( mClosed )
{
return false;
}
if ( mQuery->step() != SQLITE_ROW )
{
return false;
}
feature.setFields( mFields, /* init */ true );
if ( mDefinition.uid().isNull() )
{
// no id column => autoincrement
feature.setFeatureId( mFid++ );
}
else
{
// first column: uid
feature.setFeatureId( mQuery->columnInt64( 0 ) );
}
int n = mQuery->columnCount();
int i = 0;
foreach ( int idx, mAttributes )
{
int type = mQuery->columnType( i + 1 );
switch ( type )
{
case SQLITE_INTEGER:
feature.setAttribute( idx, mQuery->columnInt64( i + 1 ) );
break;
case SQLITE_FLOAT:
feature.setAttribute( idx, mQuery->columnDouble( i + 1 ) );
break;
case SQLITE_TEXT:
default:
feature.setAttribute( idx, mQuery->columnText( i + 1 ) );
break;
};
i++;
}
if ( n > mAttributes.size() + 1 )
{
// geometry field
QByteArray blob( mQuery->columnBlob( n - 1 ) );
if ( blob.size() > 0 )
{
feature.setGeometry( spatialiteBlobToQgsGeometry( blob.constData(), blob.size() ) );
}
}
return true;
}
QgsVirtualLayerFeatureSource::QgsVirtualLayerFeatureSource( const QgsVirtualLayerProvider* p ) :
mProvider( p )
{
}
QgsVirtualLayerFeatureSource::~QgsVirtualLayerFeatureSource()
{
}
QgsFeatureIterator QgsVirtualLayerFeatureSource::getFeatures( const QgsFeatureRequest& request )
{
return QgsFeatureIterator( new QgsVirtualLayerFeatureIterator( this, false, request ) );
}

View File

@ -0,0 +1,71 @@
/***************************************************************************
qgsvirtuallayerfeatureiterator.h
Feature iterator for the virtual layer provider
begin : Feb 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. *
* *
***************************************************************************/
#ifndef QGSVIRTUALLAYER_FEATURE_ITERATOR_H
#define QGSVIRTUALLAYER_FEATURE_ITERATOR_H
#include <qgsvirtuallayerprovider.h>
#include <qgsfeatureiterator.h>
class QgsVirtualLayerFeatureSource : public QgsAbstractFeatureSource
{
public:
QgsVirtualLayerFeatureSource( const QgsVirtualLayerProvider* p );
~QgsVirtualLayerFeatureSource();
virtual QgsFeatureIterator getFeatures( const QgsFeatureRequest& request ) override;
const QgsVirtualLayerProvider* provider() const { return mProvider; }
private:
const QgsVirtualLayerProvider* mProvider;
};
class QgsVirtualLayerFeatureIterator : public QgsAbstractFeatureIteratorFromSource<QgsVirtualLayerFeatureSource>
{
public:
QgsVirtualLayerFeatureIterator( QgsVirtualLayerFeatureSource* source, bool ownSource, const QgsFeatureRequest& request );
~QgsVirtualLayerFeatureIterator();
//! reset the iterator to the starting position
virtual bool rewind() override;
//! end of iterating: free the resources / lock
virtual bool close() override;
protected:
//! fetch next feature, return true on success
virtual bool fetchFeature( QgsFeature& feature ) override;
QScopedPointer<Sqlite::Query> mQuery;
QgsFeatureId mFid;
QString mPath;
sqlite3* mSqlite;
QgsVirtualLayerDefinition mDefinition;
QgsFields mFields;
QString mSqlQuery;
// Index of the id column, -1 if none
int mUidColumn;
QgsAttributeList mAttributes;
};
#endif

View File

@ -0,0 +1,636 @@
/***************************************************************************
qgsvirtuallayerprovider.cpp Virtual layer data provider
begin : Jan, 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. *
* *
***************************************************************************/
extern "C"
{
#include <sqlite3.h>
#include <spatialite.h>
}
#include <QUrl>
#include <stdexcept>
#include <qgsvirtuallayerprovider.h>
#include <qgsvirtuallayerdefinition.h>
#include <qgsvirtuallayerfeatureiterator.h>
#include <qgsvectorlayer.h>
#include <qgsmaplayerregistry.h>
#include <qgsdatasourceuri.h>
#include "qgsvirtuallayerprovider.h"
#include "qgsvirtuallayersqlitemodule.h"
#include "qgsvirtuallayerqueryparser.h"
const QString VIRTUAL_LAYER_KEY = "virtual";
const QString VIRTUAL_LAYER_DESCRIPTION = "Virtual layer data provider";
const QString VIRTUAL_LAYER_QUERY_VIEW = "_query";
static QString quotedColumn( QString name )
{
return "\"" + name.replace( "\"", "\"\"" ) + "\"";
}
#define PROVIDER_ERROR( msg ) do { mError = QgsError( msg, VIRTUAL_LAYER_KEY ); QgsDebugMsg( msg ); } while(0)
QgsVirtualLayerProvider::QgsVirtualLayerProvider( QString const &uri )
: QgsVectorDataProvider( uri ),
mValid( true ),
mCachedStatistics( false )
{
mError.clear();
QUrl url = QUrl::fromEncoded( uri.toUtf8() );
if ( !url.isValid() )
{
mValid = false;
PROVIDER_ERROR( "Malformed URL" );
return;
}
// xxxxx = open a virtual layer
// xxxxx?key=value&key=value = create a virtual layer
// ?key=value = create a temporary virtual layer
// read url
try
{
mDefinition = QgsVirtualLayerDefinition::fromUrl( url );
if ( mDefinition.sourceLayers().empty() && !mDefinition.filePath().isEmpty() && mDefinition.query().isEmpty() )
{
// open the file
mValid = openIt();
}
else
{
// create the file
mValid = createIt();
}
}
catch ( std::runtime_error& e )
{
mValid = false;
PROVIDER_ERROR( e.what() );
return;
}
if ( mDefinition.geometrySrid() != -1 )
{
mCrs = QgsCoordinateReferenceSystem( mDefinition.geometrySrid() );
}
}
bool QgsVirtualLayerProvider::loadSourceLayers()
{
foreach ( const QgsVirtualLayerDefinition::SourceLayer& layer, mDefinition.sourceLayers() )
{
if ( layer.isReferenced() )
{
QgsMapLayer *l = QgsMapLayerRegistry::instance()->mapLayer( layer.reference() );
if ( l == 0 )
{
PROVIDER_ERROR( QString( "Cannot find layer %1" ).arg( layer.reference() ) );
return false;
}
if ( l->type() != QgsMapLayer::VectorLayer )
{
PROVIDER_ERROR( QString( "Layer %1 is not a vector layer" ).arg( layer.reference() ) );
return false;
}
// add the layer to the list
QgsVectorLayer* vl = static_cast<QgsVectorLayer*>( l );
mLayers << SourceLayer( vl, layer.name() );
// connect to modification signals to invalidate statistics
connect( vl, SIGNAL( featureAdded( QgsFeatureId ) ), this, SLOT( invalidateStatistics() ) );
connect( vl, SIGNAL( featureDeleted( QgsFeatureId ) ), this, SLOT( invalidateStatistics() ) );
connect( vl, SIGNAL( geometryChanged( QgsFeatureId, QgsGeometry& ) ), this, SLOT( invalidateStatistics() ) );
}
else
{
mLayers << SourceLayer( layer.provider(), layer.source(), layer.name(), layer.encoding() );
}
}
return true;
}
bool QgsVirtualLayerProvider::openIt()
{
spatialite_init( 0 );
mPath = mDefinition.filePath();
try
{
QgsScopedSqlite p( mPath );
mSqlite = p;
}
catch ( std::runtime_error& e )
{
PROVIDER_ERROR( e.what() );
return false;
}
{
Sqlite::Query q( mSqlite.get(), "SELECT name FROM sqlite_master WHERE name='_meta'" );
if ( q.step() != SQLITE_ROW )
{
PROVIDER_ERROR( "No metadata tables !" );
return false;
}
}
// look for the correct version and the stored url
{
Sqlite::Query q( mSqlite.get(), "SELECT version, url FROM _meta" );
int version = 0;
if ( q.step() == SQLITE_ROW )
{
version = q.columnInt( 0 );
if ( version != VIRTUAL_LAYER_VERSION )
{
PROVIDER_ERROR( "Wrong virtual layer version !" );
return false;
}
mDefinition = QgsVirtualLayerDefinition::fromUrl( QUrl( q.columnText( 1 ) ) );
}
}
// overwrite the uri part of the definition
mDefinition.setFilePath( mPath );
// load source layers
if ( !loadSourceLayers() )
{
return false;
}
/* only one table */
if ( mDefinition.query().isEmpty() )
{
mTableName = mLayers[0].name;
}
else
{
mTableName = VIRTUAL_LAYER_QUERY_VIEW;
}
return true;
}
bool QgsVirtualLayerProvider::createIt()
{
using namespace QgsVirtualLayerQueryParser;
// consistency check
if ( mDefinition.sourceLayers().size() > 1 && mDefinition.query().isEmpty() )
{
PROVIDER_ERROR( QString( "Don't know how to join layers, please specify a query" ) );
return false;
}
if ( mDefinition.sourceLayers().empty() && mDefinition.filePath().isEmpty() && mDefinition.query().isEmpty() )
{
PROVIDER_ERROR( QString( "Please specify at least one source layer or a query" ) );
return false;
}
if ( !mDefinition.filePath().isEmpty() && mDefinition.hasReferencedLayers() )
{
PROVIDER_ERROR( QString( "Cannot store referenced layers" ) );
return false;
}
QList<ColumnDef> fields, gFields;
QMap<QString, TableDef> refTables;
if ( !mDefinition.query().isEmpty() )
{
QStringList tables = referencedTables( mDefinition.query() );
foreach ( const QString& tname, tables )
{
// is it in source layers ?
if ( mDefinition.hasSourceLayer( tname ) )
{
continue;
}
// is it in loaded layers ?
bool found = false;
foreach ( const QgsMapLayer* l, QgsMapLayerRegistry::instance()->mapLayers() )
{
if ( l->type() != QgsMapLayer::VectorLayer )
continue;
const QgsVectorLayer* vl = static_cast<const QgsVectorLayer*>( l );
if (( vl->name() == tname ) || ( vl->id() == tname ) )
{
mDefinition.addSource( tname, vl->id() );
found = true;
break;
}
}
if ( !found )
{
PROVIDER_ERROR( QString( "Referenced table %1 in query not found!" ).arg( tname ) );
return false;
}
}
}
QString path;
mPath = mDefinition.filePath();
// use a temporary file if needed
if ( mPath.isEmpty() )
path = ":memory:";
else
path = mPath;
spatialite_init( 0 );
try
{
QgsScopedSqlite sqlite( path );
mSqlite = sqlite;
}
catch ( std::runtime_error& e )
{
PROVIDER_ERROR( e.what() );
return false;
}
resetSqlite();
initVirtualLayerMetadata( mSqlite.get() );
bool noGeometry = mDefinition.geometryWkbType() == QgsWKBTypes::NoGeometry;
// load source layers (and populate mLayers)
if ( !loadSourceLayers() )
{
return false;
}
// now create virtual tables based on layers
for ( int i = 0; i < mLayers.size(); i++ )
{
QgsVectorLayer* vlayer = mLayers.at( i ).layer;
QString vname = mLayers.at( i ).name;
if ( vlayer )
{
QString createStr = QString( "DROP TABLE IF EXISTS \"%1\"; CREATE VIRTUAL TABLE \"%1\" USING QgsVLayer(%2);" ).arg( vname ).arg( vlayer->id() );
Sqlite::Query::exec( mSqlite.get(), createStr );
}
else
{
QString provider = mLayers.at( i ).provider;
// double each single quote
provider.replace( "'", "''" );
QString source = mLayers.at( i ).source;
source.replace( "'", "''" );
QString encoding = mLayers.at( i ).encoding;
QString createStr = QString( "DROP TABLE IF EXISTS \"%1\"; CREATE VIRTUAL TABLE \"%1\" USING QgsVLayer('%2','%4',%3)" )
.arg( vname )
.arg( provider )
.arg( encoding )
.arg( source ); // source must be the last argument here, since it can contains '%x' strings that would be replaced
Sqlite::Query::exec( mSqlite.get(), createStr );
}
}
QgsFields tfields;
QList<QString> geometryFields;
if ( !mDefinition.query().isEmpty() )
{
// look for column types of the query
TableDef columns = columnDefinitionsFromQuery( mSqlite.get(), mDefinition.query() );
for ( int i = 0; i < columns.size(); i++ )
{
ColumnDef& c = columns[i];
if ( c.name().isEmpty() )
{
PROVIDER_ERROR( QString( "Result column #%1 has no name !" ).arg( i + 1 ) );
return false;
}
// then override types by the ones defined in the url
if ( mDefinition.fields().indexFromName( c.name() ) != -1 )
{
c.setScalarType( mDefinition.fields().field( c.name() ).type() );
}
if ( c.isGeometry() )
{
gFields << c;
}
// if the geometry field is not detected as a geometry, move it to the geometry fields
// with the provided type and srid
else if ( mDefinition.hasDefinedGeometry() && c.name() == mDefinition.geometryField() )
{
ColumnDef g;
g.setName( mDefinition.geometryField() );
g.setGeometry( mDefinition.geometryWkbType() );
g.setSrid( mDefinition.geometrySrid() );
gFields << g;
}
// default type: string
else if ( c.scalarType() == QVariant::Invalid )
{
c.setScalarType( QVariant::String );
}
else
{
tfields.append( QgsField( c.name(), c.scalarType() ) );
}
}
// process geometry field
if ( !noGeometry )
{
// no geometry field defined yet, take the first detected
if ( mDefinition.geometryField().isEmpty() )
{
if ( gFields.count() > 0 )
{
mDefinition.setGeometryField( gFields[0].name() );
mDefinition.setGeometryWkbType( gFields[0].wkbType() );
mDefinition.setGeometrySrid( gFields[0].srid() );
}
}
// a geometry field is named, but has no type yet
// look for a detected type
else if ( !mDefinition.hasDefinedGeometry() )
{
bool found = false;
for ( int i = 0; i < gFields.size(); i++ )
{
if ( gFields[i].name() == mDefinition.geometryField() )
{
// override the geometry type
mDefinition.setGeometryWkbType( gFields[i].wkbType() );
mDefinition.setGeometrySrid( gFields[i].srid() );
found = true;
break;
}
}
if ( !found )
{
PROVIDER_ERROR( "Cannot find the specified geometry field !" );
return false;
}
}
if ( !mDefinition.geometryField().isEmpty() && !mDefinition.hasDefinedGeometry() )
{
PROVIDER_ERROR( "Can't deduce the geometry type of the geometry field !" );
return false;
}
}
// save field definitions
mDefinition.setFields( tfields );
mTableName = VIRTUAL_LAYER_QUERY_VIEW;
// create a view
QString viewStr = QString( "DROP VIEW IF EXISTS %1; CREATE VIEW %1 AS %2" )
.arg( VIRTUAL_LAYER_QUERY_VIEW )
.arg( mDefinition.query() );
Sqlite::Query::exec( mSqlite.get(), viewStr );
}
else
{
// no query => implies we must only have one virtual table
mTableName = mLayers[0].name;
TableDef td = tableDefinitionFromVirtualTable( mSqlite.get(), mTableName );
foreach ( const ColumnDef& c, td )
{
if ( !c.isGeometry() )
{
tfields.append( QgsField( c.name(), c.scalarType() ) );
}
else if ( !noGeometry )
{
mDefinition.setGeometryField( "geometry" );
mDefinition.setGeometryWkbType( c.wkbType() );
mDefinition.setGeometrySrid( c.srid() );
}
}
mDefinition.setFields( tfields );
}
// Save the definition back to the sqlite file
{
Sqlite::Query q( mSqlite.get(), "UPDATE _meta SET url=?" );
q.bind( mDefinition.toUrl().toString() );
q.step();
}
return true;
}
QgsVirtualLayerProvider::~QgsVirtualLayerProvider()
{
}
void QgsVirtualLayerProvider::resetSqlite()
{
bool hasSpatialrefsys = false;
{
Sqlite::Query q( mSqlite.get(), "SELECT name FROM sqlite_master WHERE name='spatial_ref_sys'" );
hasSpatialrefsys = q.step() == SQLITE_ROW;
}
QString sql = "DROP TABLE IF EXISTS _meta;";
if ( !hasSpatialrefsys )
{
sql += "SELECT InitSpatialMetadata(1);";
}
Sqlite::Query::exec( mSqlite.get(), sql );
}
QgsAbstractFeatureSource* QgsVirtualLayerProvider::featureSource() const
{
return new QgsVirtualLayerFeatureSource( this );
}
QString QgsVirtualLayerProvider::storageType() const
{
return "No storage per se, view data from other data sources";
}
QgsCoordinateReferenceSystem QgsVirtualLayerProvider::crs()
{
return mCrs;
}
QgsFeatureIterator QgsVirtualLayerProvider::getFeatures( const QgsFeatureRequest& request )
{
return QgsFeatureIterator( new QgsVirtualLayerFeatureIterator( new QgsVirtualLayerFeatureSource( this ), false, request ) );
}
QString QgsVirtualLayerProvider::subsetString()
{
return mSubset;
}
bool QgsVirtualLayerProvider::setSubsetString( const QString& subset, bool updateFeatureCount )
{
mSubset = subset;
if ( updateFeatureCount )
updateStatistics();
return true;
}
QGis::WkbType QgsVirtualLayerProvider::geometryType() const
{
return static_cast<QGis::WkbType>( mDefinition.geometryWkbType() );
}
long QgsVirtualLayerProvider::featureCount() const
{
if ( !mCachedStatistics )
{
updateStatistics();
}
return mFeatureCount;
}
QgsRectangle QgsVirtualLayerProvider::extent()
{
if ( !mCachedStatistics )
{
updateStatistics();
}
return mExtent;
}
void QgsVirtualLayerProvider::updateStatistics() const
{
bool hasGeometry = mDefinition.geometryWkbType() != QgsWKBTypes::NoGeometry;
QString subset = mSubset.isEmpty() ? "" : " WHERE " + mSubset;
QString sql = QString( "SELECT Count(*)%1 FROM %2%3" )
.arg( hasGeometry ? QString( ",Min(MbrMinX(%1)),Min(MbrMinY(%1)),Max(MbrMaxX(%1)),Max(MbrMaxY(%1))" ).arg( quotedColumn( mDefinition.geometryField() ) ) : "" )
.arg( mTableName )
.arg( subset );
Sqlite::Query q( mSqlite.get(), sql );
if ( q.step() == SQLITE_ROW )
{
mFeatureCount = q.columnInt64( 0 );
if ( hasGeometry )
{
double x1, y1, x2, y2;
x1 = q.columnDouble( 1 );
y1 = q.columnDouble( 2 );
x2 = q.columnDouble( 3 );
y2 = q.columnDouble( 4 );
mExtent = QgsRectangle( x1, y1, x2, y2 );
}
mCachedStatistics = true;
}
}
void QgsVirtualLayerProvider::invalidateStatistics()
{
mCachedStatistics = false;
}
const QgsFields & QgsVirtualLayerProvider::fields() const
{
return mDefinition.fields();
}
bool QgsVirtualLayerProvider::isValid()
{
return mValid;
}
int QgsVirtualLayerProvider::capabilities() const
{
if ( !mDefinition.uid().isNull() )
{
return SelectAtId | SelectGeometryAtId;
}
return 0;
}
QString QgsVirtualLayerProvider::name() const
{
return VIRTUAL_LAYER_KEY;
}
QString QgsVirtualLayerProvider::description() const
{
return VIRTUAL_LAYER_DESCRIPTION;
}
QgsAttributeList QgsVirtualLayerProvider::pkAttributeIndexes()
{
if ( !mDefinition.uid().isNull() )
{
const QgsFields& fields = mDefinition.fields();
for ( int i = 0; i < fields.size(); i++ )
{
if ( fields.at( i ).name().toLower() == mDefinition.uid().toLower() )
{
QgsAttributeList l;
l << i;
return l;
}
}
}
return QgsAttributeList();
}
/**
* Class factory to return a pointer to a newly created
* QgsSpatiaLiteProvider object
*/
QGISEXTERN QgsVirtualLayerProvider *classFactory( const QString * uri )
{
return new QgsVirtualLayerProvider( *uri );
}
/** Required key function (used to map the plugin to a data store type)
*/
QGISEXTERN QString providerKey()
{
return VIRTUAL_LAYER_KEY;
}
/**
* Required description function
*/
QGISEXTERN QString description()
{
return VIRTUAL_LAYER_DESCRIPTION;
}
/**
* Required isProvider function. Used to determine if this shared library
* is a data provider plugin
*/
QGISEXTERN bool isProvider()
{
return true;
}
QGISEXTERN void cleanupProvider()
{
}

View File

@ -0,0 +1,146 @@
/***************************************************************************
qgsvirtuallayerprovider.cpp Virtual layer data provider
begin : Jan, 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. *
* *
***************************************************************************/
#ifndef QGSVIRTUAL_LAYER_PROVIDER_H
#define QGSVIRTUAL_LAYER_PROVIDER_H
#include <qgsvectordataprovider.h>
#include "qgscoordinatereferencesystem.h"
#include "qgsvirtuallayerdefinition.h"
#include "qgsvirtuallayersqlitehelper.h"
class QgsVirtualLayerFeatureIterator;
class QgsVirtualLayerProvider: public QgsVectorDataProvider
{
Q_OBJECT
public:
/**
* Constructor of the vector provider
* @param uri uniform resource locator (URI) for a dataset
*/
QgsVirtualLayerProvider( QString const &uri = "" );
/** Destructor */
virtual ~QgsVirtualLayerProvider();
virtual QgsAbstractFeatureSource* featureSource() const override;
/** Returns the permanent storage type for this layer as a friendly name */
virtual QString storageType() const override;
/** Get the QgsCoordinateReferenceSystem for this layer */
virtual QgsCoordinateReferenceSystem crs() override;
/** Access features through an iterator */
virtual QgsFeatureIterator getFeatures( const QgsFeatureRequest& request ) override;
/** Get the feature geometry type */
QGis::WkbType geometryType() const override;
/** Get the number of features in the layer */
long featureCount() const override;
/** Return the extent for this data layer */
virtual QgsRectangle extent() override;
/** Accessor for sql where clause used to limit dataset */
virtual QString subsetString() override;
/** Set the subset string used to create a subset of features in the layer (WHERE clause) */
virtual bool setSubsetString( const QString& subset, bool updateFeatureCount = true ) override;
/** Provider supports setting of subset strings */
virtual bool supportsSubsetString() override { return true; }
/**
* Get the field information for the layer
* @return vector of QgsField objects
*/
const QgsFields & fields() const override;
/** Returns true if layer is valid */
bool isValid() override;
/** Returns a bitmask containing the supported capabilities*/
int capabilities() const override;
/** Return the provider name */
QString name() const override;
/** Return description */
QString description() const override;
/** Return list of indexes of fields that make up the primary key */
QgsAttributeList pkAttributeIndexes() override;
private:
// file on disk
QString mPath;
QgsScopedSqlite mSqlite;
// underlying vector layers
struct SourceLayer
{
SourceLayer(): layer( 0 ) {}
SourceLayer( QgsVectorLayer *l, const QString& n = "" ) : layer( l ), name( n ) {}
SourceLayer( const QString& p, const QString& s, const QString& n, const QString& e = "UTF-8" ) :
layer( 0 ), name( n ), source( s ), provider( p ), encoding( e ) {}
// non-null if it refers to a live layer
QgsVectorLayer* layer;
QString name;
// non-empty if it is an embedded layer
QString source;
QString provider;
QString encoding;
};
typedef QVector<SourceLayer> SourceLayers;
SourceLayers mLayers;
bool mValid;
QString mTableName;
QgsCoordinateReferenceSystem mCrs;
QgsVirtualLayerDefinition mDefinition;
QString mSubset;
void resetSqlite();
mutable bool mCachedStatistics;
mutable qint64 mFeatureCount;
mutable QgsRectangle mExtent;
void updateStatistics() const;
bool openIt();
bool createIt();
bool loadSourceLayers();
friend class QgsVirtualLayerFeatureIterator;
private slots:
void invalidateStatistics();
};
#endif

View File

@ -0,0 +1,274 @@
#include "qgsvirtuallayerqueryparser.h"
#include "qgsvirtuallayersqlitehelper.h"
#include "qgsvirtuallayerblob.h"
#include <QRegExp>
namespace QgsVirtualLayerQueryParser
{
QStringList referencedTables( const QString& query )
{
QStringList tables;
//
// open an empty in-memory sqlite database and execute the query
// sqlite will return an error for each missing table
// this way we know the list of tables referenced by the query
QgsScopedSqlite db( ":memory:", /*withExtension=*/ false );
const QString noSuchError = "no such table: ";
while ( true )
{
char *errMsg = 0;
int r = sqlite3_exec( db.get(), query.toLocal8Bit().constData(), NULL, NULL, &errMsg );
QString err = errMsg;
if ( r && err.startsWith( noSuchError ) )
{
QString tableName = err.mid( noSuchError.size() );
tables << tableName;
// create a dummy table to skip this error
QString createStr = QString( "CREATE TABLE \"%1\" (id int)" ).arg( tableName.replace( "\"", "\"\"" ) );
sqlite3_exec( db.get(), createStr.toLocal8Bit().constData(), NULL, NULL, NULL );
}
else
{
// no error, or another error
break;
}
}
return tables;
}
QMap<QString, ColumnDef> columnCommentDefinitions( const QString& query )
{
QMap<QString, ColumnDef> defs;
// look for special comments in SQL
// a column name followed by /*:type*/
QRegExp rx( "([a-zA-Z_\x80-\xFF][a-zA-Z0-9_\x80-\xFF]*)\\s*/\\*:(int|real|text|((?:multi)?(?:point|linestring|polygon)):(\\d+))\\s*\\*/", Qt::CaseInsensitive );
int pos = 0;
while (( pos = rx.indexIn( query, pos ) ) != -1 )
{
QString column = rx.cap( 1 );
QString type = rx.cap( 2 );
ColumnDef def;
def.setName( column );
if ( type == "int" )
def.setScalarType( QVariant::Int );
else if ( type == "real" )
def.setScalarType( QVariant::Double );
else if ( type == "text" )
def.setScalarType( QVariant::String );
else
{
// there should be 2 more captures
def.setGeometry( QgsWKBTypes::parseType( rx.cap( 3 ) ) );
def.setSrid( static_cast<QgsWKBTypes::Type>( rx.cap( 4 ).toLong() ) );
}
defs[column] = def;
pos += rx.matchedLength();
}
return defs;
}
bool isValidColumnName( const QString& columnName )
{
// identifier name with possible accents
static QRegExp columnNameRx( "[a-zA-Z_\x80-\xFF][a-zA-Z0-9_\x80-\xFF]*" );
return columnNameRx.exactMatch( columnName );
}
// set the type of the column type, given its text representation
void setColumnDefType( const QString& columnType, ColumnDef& d )
{
// geometry type
QRegExp geometryTypeRx( "\\(([0-9]+),([0-9]+)\\)" );
// see qgsvirtuallayersqlitemodule for possible declared types
// the type returned by PRAGMA table_info will be either
// the type declared by one of the virtual tables
// or null
if ( columnType == "int" )
d.setScalarType( QVariant::Int );
else if ( columnType == "real" )
d.setScalarType( QVariant::Double );
else if ( columnType == "text" )
d.setScalarType( QVariant::String );
else if ( columnType.startsWith( "geometry" ) )
{
// parse the geometry type and srid
// geometry(type,srid)
int pos = geometryTypeRx.indexIn( columnType, 0 );
if ( pos != -1 )
{
QgsWKBTypes::Type type = static_cast<QgsWKBTypes::Type>( geometryTypeRx.cap( 1 ).toInt() );
long srid = geometryTypeRx.cap( 2 ).toLong();
d.setGeometry( type );
d.setSrid( srid );
}
}
}
ColumnDef geometryDefinitionFromVirtualTable( sqlite3* db, const QString& tableName )
{
ColumnDef d;
Sqlite::Query q( db, QString( "PRAGMA table_info(%1)" ).arg( tableName ) );
while ( q.step() == SQLITE_ROW )
{
QString columnName = q.columnText( 1 );
QString columnType = q.columnText( 2 );
if ( ! columnType.startsWith( "geometry" ) )
continue;
d.setName( columnName );
setColumnDefType( columnType, d );
break;
}
return d;
}
TableDef columnDefinitionsFromQuery( sqlite3* db, const QString& query )
{
// get column types defined by comments
QMap<QString, ColumnDef> definedColumns = columnCommentDefinitions( query );
// create a view to detect column names and types, using PRAGMA table_info
QString viewStr = "CREATE TEMP VIEW _tview AS " + query;
Sqlite::Query::exec( db, viewStr );
QStringList columns;
bool hasInvalidName = false;
QVector<int> undefinedColumns;
TableDef tableDef;
{
Sqlite::Query q( db, "PRAGMA table_info(_tview)" );
int columnNumber = 0;
while ( q.step() == SQLITE_ROW )
{
QString columnName = q.columnText( 1 );
if ( !isValidColumnName( columnName ) )
{
std::cout << "Invalid name: " << columnName.toLocal8Bit().constData() << std::endl;
hasInvalidName = true;
// add an unnamed column
ColumnDef d;
tableDef << d;
break;
}
columns << columnName;
QString columnType = q.columnText( 2 );
// column type defined by comments
if ( definedColumns.contains( columnName ) )
{
tableDef << definedColumns[columnName];
}
else
{
ColumnDef d;
d.setName( columnName );
setColumnDefType( columnType, d );
if ( d.scalarType() == QVariant::Invalid )
{
// else no type is defined
undefinedColumns << columnNumber;
}
tableDef << d;
}
columnNumber++;
}
}
if ( hasInvalidName || undefinedColumns.size() == 0 )
return tableDef;
// get the first row to introspect types
{
QString qs = "SELECT ";
for ( int i = 0; i < undefinedColumns.size(); i++ )
{
qs += columns[undefinedColumns[i]];
if ( i != undefinedColumns.size() - 1 )
qs += ", ";
}
qs += " FROM _tview LIMIT 1";
std::cout << qs.toLocal8Bit().constData() << std::endl;
Sqlite::Query q( db, qs );
if ( q.step() == SQLITE_ROW )
{
for ( int i = 0; i < undefinedColumns.size(); i++ )
{
int colIdx = undefinedColumns[i];
int type = q.columnType( i );
switch ( type )
{
case SQLITE_INTEGER:
tableDef[colIdx].setScalarType( QVariant::Int );
break;
case SQLITE_FLOAT:
tableDef[colIdx].setScalarType( QVariant::Double );
break;
case SQLITE_BLOB:
{
// might be a geometry, parse the type
QByteArray ba( q.columnBlob( i ) );
QPair<QgsWKBTypes::Type, long> p( spatialiteBlobGeometryType( ba.constData(), ba.size() ) );
if ( p.first != QgsWKBTypes::NoGeometry )
{
tableDef[colIdx].setGeometry( p.first );
tableDef[colIdx].setSrid( p.second );
}
else
{
// interpret it as a string
tableDef[colIdx].setScalarType( QVariant::String );
}
}
break;
case SQLITE_TEXT:
default:
tableDef[colIdx].setScalarType( QVariant::String );
break;
};
}
}
}
return tableDef;
}
TableDef tableDefinitionFromVirtualTable( sqlite3* db, const QString& tableName )
{
TableDef td;
Sqlite::Query q( db, QString( "PRAGMA table_info(%1)" ).arg( tableName ) );
while ( q.step() == SQLITE_ROW )
{
ColumnDef d;
QString columnName = q.columnText( 1 );
QString columnType = q.columnText( 2 );
d.setName( columnName );
setColumnDefType( columnType, d );
td << d;
}
return td;
}
} // namespace

View File

@ -0,0 +1,70 @@
#ifndef QGSVIRTUALLAYER_QUERY_PARSER_H
#define QGSVIRTUALLAYER_QUERY_PARSER_H
#include <qgis.h>
#include <qgswkbtypes.h>
#include <qgsvectorlayer.h>
namespace QgsVirtualLayerQueryParser
{
//!
//! Return the list of tables referenced in the SQL query
QStringList referencedTables( const QString& q );
/**
* Type used to define a column
*
* It can hold a name and a type.
* The type can be a 'scalar' type (int, double, string) or a geometry type (WKB) and an SRID
*/
class ColumnDef
{
public:
ColumnDef()
: mType( QVariant::Invalid ), mWkbType( QgsWKBTypes::Unknown ), mSrid( -1 )
{}
ColumnDef( const QString& name, QgsWKBTypes::Type aWkbType, long aSrid )
: mName( name ), mType( QVariant::UserType ), mWkbType( aWkbType ), mSrid( aSrid )
{}
ColumnDef( const QString& name, QVariant::Type aType )
: mName( name ), mType( aType ), mWkbType( QgsWKBTypes::NoGeometry ), mSrid( -1 )
{}
QString name() const { return mName; }
void setName( QString name ) { mName = name; }
bool isGeometry() const { return mType == QVariant::UserType; }
void setGeometry( QgsWKBTypes::Type wkbType ) { mType = QVariant::UserType; mWkbType = wkbType; }
long srid() const { return mSrid; }
void setSrid( long srid ) { mSrid = srid; }
void setScalarType( QVariant::Type t ) { mType = t; mWkbType = QgsWKBTypes::NoGeometry; }
QVariant::Type scalarType() const { return mType; }
QgsWKBTypes::Type wkbType() const { return mWkbType; }
private:
QString mName;
QVariant::Type mType;
QgsWKBTypes::Type mWkbType;
long mSrid;
};
//!
//! Type used by the parser to type a query. It is slightly different from a QgsVirtualLayerDefinition since more than one geometry column can be represented
typedef QList<ColumnDef> TableDef;
//! Get the column names and types that can be deduced from the query, using SQLite introspection
//! Special comments can also be used in the query to type columns
//! Comments should be set after the name of the column and are introduced by "/*:"
//! For instance 'SELECT t+1 /*:int*/ FROM table' will type the column 't' as integer
//! A geometry column can also be set by specifying a type and an SRID
//! For instance 'SELECT t, GeomFromText('POINT(0 0)',4326) as geom /*:point:4326*/
TableDef columnDefinitionsFromQuery( sqlite3* db, const QString& query );
//! Get the column types of a virtual table
TableDef tableDefinitionFromVirtualTable( sqlite3* db, const QString& tableName );
}
#endif

View File

@ -0,0 +1,189 @@
/***************************************************************************
qgsvirtuallayersqlitehelper.cpp
begin : December 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 <QString>
#include <stdexcept>
#include "qgsvirtuallayersqlitehelper.h"
QgsScopedSqlite::QgsScopedSqlite( const QString& path, bool withExtension )
{
if ( withExtension )
{
// register a statically-linked function as extension
// for all future database connection
sqlite3_auto_extension(( void( * )() )qgsvlayer_module_init );
}
int r;
r = sqlite3_open( path.toLocal8Bit().constData(), &db_ );
if ( withExtension )
{
// reset the automatic extensions
sqlite3_reset_auto_extension();
}
if ( r )
{
throw std::runtime_error( sqlite3_errmsg( db_ ) );
}
// enable extended result codes
sqlite3_extended_result_codes( db_, 1 );
}
QgsScopedSqlite::QgsScopedSqlite( QgsScopedSqlite& other )
{
db_ = other.db_;
other.db_ = 0;
}
QgsScopedSqlite& QgsScopedSqlite::operator=( QgsScopedSqlite & other )
{
reset( other.release() );
return *this;
}
QgsScopedSqlite::~QgsScopedSqlite()
{
close_();
}
sqlite3* QgsScopedSqlite::get() const { return db_; }
sqlite3* QgsScopedSqlite::release()
{
sqlite3* pp = db_;
db_ = 0;
return pp;
}
void QgsScopedSqlite::reset( sqlite3* db )
{
close_();
db_ = db;
}
void QgsScopedSqlite::close_()
{
if ( db_ )
sqlite3_close( db_ );
}
namespace Sqlite
{
Query::Query( sqlite3* db, const QString& q ) : db_( db ), nBind_( 1 )
{
QByteArray ba( q.toLocal8Bit() );
int r = sqlite3_prepare_v2( db, ba.constData(), ba.size(), &stmt_, NULL );
if ( r )
{
QString err = QString( "Query preparation error on %1" ).arg( q );
throw std::runtime_error( err.toLocal8Bit().constData() );
}
}
Query::~Query()
{
sqlite3_finalize( stmt_ );
}
int Query::step() { return sqlite3_step( stmt_ ); }
Query& Query::bind( const QString& str, int idx )
{
QByteArray ba( str.toLocal8Bit() );
int r = sqlite3_bind_text( stmt_, idx, ba.constData(), ba.size(), SQLITE_TRANSIENT );
if ( r )
{
throw std::runtime_error( sqlite3_errmsg( db_ ) );
}
return *this;
}
Query& Query::bind( const QString& str )
{
return bind( str, nBind_++ );
}
void Query::exec( sqlite3* db, const QString& sql )
{
char *errMsg = 0;
int r = sqlite3_exec( db, sql.toLocal8Bit().constData(), NULL, NULL, &errMsg );
if ( r )
{
QString err = QString( "Query execution error on %1: %2 - %3" ).arg( sql ).arg( r ).arg( errMsg );
throw std::runtime_error( err.toLocal8Bit().constData() );
}
}
void Query::reset()
{
int r = sqlite3_reset( stmt_ );
if ( r )
{
throw std::runtime_error( sqlite3_errmsg( db_ ) );
}
nBind_ = 1;
}
int Query::columnCount() const
{
return sqlite3_column_count( stmt_ );
}
QString Query::columnName( int i ) const
{
return QString( sqlite3_column_name( stmt_, i ) );
}
int Query::columnType( int i ) const
{
return sqlite3_column_type( stmt_, i );
}
int Query::columnInt( int i ) const
{
return sqlite3_column_int( stmt_, i );
}
qint64 Query::columnInt64( int i ) const
{
return sqlite3_column_int64( stmt_, i );
}
double Query::columnDouble( int i ) const
{
return sqlite3_column_double( stmt_, i );
}
QString Query::columnText( int i ) const
{
int size = sqlite3_column_bytes( stmt_, i );
const char* str = ( const char* )sqlite3_column_text( stmt_, i );
return QString::fromUtf8( str, size );
}
QByteArray Query::columnBlob( int i ) const
{
int size = sqlite3_column_bytes( stmt_, i );
const char* data = ( const char* )sqlite3_column_blob( stmt_, i );
// data is not copied. QByteArray is just here a augmented pointer
return QByteArray::fromRawData( data, size );
}
sqlite3_stmt* Query::stmt() { return stmt_; }
}

View File

@ -0,0 +1,94 @@
/***************************************************************************
qgsvirtuallayersqlitehelper.h
begin : December 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. *
* *
***************************************************************************/
#ifndef QGSVIRTUALLAYER_SQLITE_UTILS_H
#define QGSVIRTUALLAYER_SQLITE_UTILS_H
extern "C"
{
#include <sqlite3.h>
int qgsvlayer_module_init( sqlite3 *db,
char **pzErrMsg,
void * unused /*const sqlite3_api_routines *pApi*/ );
}
// RAII class for sqlite3*
// Similar to std::unique_ptr
class QgsScopedSqlite
{
public:
QgsScopedSqlite() : db_( 0 ) {}
explicit QgsScopedSqlite( const QString& path, bool withExtension = true );
QgsScopedSqlite( QgsScopedSqlite& other );
QgsScopedSqlite& operator=( QgsScopedSqlite& other );
~QgsScopedSqlite();
sqlite3* get() const;
sqlite3* release();
void reset( sqlite3* db );
private:
sqlite3* db_;
void close_();
};
namespace Sqlite
{
struct Query
{
Query( sqlite3* db, const QString& q );
~Query();
int step();
Query& bind( const QString& str, int idx );
Query& bind( const QString& str );
static void exec( sqlite3* db, const QString& sql );
void reset();
int columnCount() const;
QString columnName( int i ) const;
int columnType( int i ) const;
int columnInt( int i ) const;
qint64 columnInt64( int i ) const;
double columnDouble( int i ) const;
QString columnText( int i ) const;
QByteArray columnBlob( int i ) const;
sqlite3_stmt* stmt();
private:
sqlite3* db_;
sqlite3_stmt* stmt_;
int nBind_;
};
}
#endif

View File

@ -0,0 +1,682 @@
/***************************************************************************
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 <qgsapplication.h>
#include <qgsvectorlayer.h>
#include <qgsvectordataprovider.h>
#include <qgsgeometry.h>
#include <qgsmaplayerregistry.h>
#include <qgsproviderregistry.h>
#include <sqlite3.h>
#include <spatialite.h>
#include <stdio.h>
#include "qgsvirtuallayersqlitemodule.h"
#include "qgsvirtuallayerblob.h"
#include "qgsslottofunction.h"
/**
* Structure created in SQLITE module creation and passed to xCreate/xConnect
*/
struct ModuleContext
{
// private pointer needed for spatialite_init_ex;
// allows to know whether the database has been initialied (null or not)
bool init;
ModuleContext() : init( false ) {}
};
/**
* Create metadata tables if needed
*/
void initVirtualLayerMetadata( sqlite3* db )
{
bool create_meta = false;
sqlite3_stmt *stmt;
int r;
r = sqlite3_prepare_v2( db, "SELECT name FROM sqlite_master WHERE name='_meta'", -1, &stmt, NULL );
if ( r )
{
throw std::runtime_error( sqlite3_errmsg( db ) );
}
create_meta = sqlite3_step( stmt ) != SQLITE_ROW;
sqlite3_finalize( stmt );
char *errMsg;
if ( create_meta )
{
r = sqlite3_exec( db, QString( "CREATE TABLE _meta (version INT, url TEXT); INSERT INTO _meta (version) VALUES(%1);" ).arg( VIRTUAL_LAYER_VERSION ).toLocal8Bit().constData(), NULL, NULL, &errMsg );
if ( r )
{
throw std::runtime_error( errMsg );
}
}
}
void deleteGeometryBlob( void * p )
{
delete[]( 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 )
: zErrMsg( 0 ), mSql( db ), mProvider( 0 ), mLayer( layer ), mSlotToFunction( invalidateTable, this ), mName( layer->name() ), mPkColumn( -1 ), mValid( true )
{
if ( mLayer )
{
QObject::connect( layer, SIGNAL( layerDeleted() ), &mSlotToFunction, SLOT( onSignal() ) );
}
init_();
}
VTable( sqlite3* db, const QString& provider, const QString& source, const QString& name, const QString& encoding )
: zErrMsg( 0 ), mSql( db ), mLayer( 0 ), mName( name ), mEncoding( encoding ), mPkColumn( -1 ), mValid( true )
{
mProvider = static_cast<QgsVectorDataProvider*>( QgsProviderRegistry::instance()->provider( provider, source ) );
if ( mProvider == 0 || !mProvider->isValid() )
{
throw std::runtime_error( "Invalid provider" );
}
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; }
private:
// connection
sqlite3* mSql;
// pointer to the underlying vector provider
QgsVectorDataProvider* mProvider;
// pointer to the vector layer, for referenced layer
QgsVectorLayer* mLayer;
// 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;
void init_()
{
const QgsFields& fields = mLayer ? mLayer->fields() : mProvider->fields();
QStringList sql_fields;
// add a hidden field for rtree filtering
sql_fields << "_search_frame_ HIDDEN BLOB";
for ( int i = 0; i < fields.count(); i++ )
{
QString typeName = "text";
switch ( fields.at( i ).type() )
{
case QVariant::Int:
case QVariant::UInt:
case QVariant::Bool:
typeName = "int";
break;
case QVariant::Double:
typeName = "real";
break;
case QVariant::String:
default:
typeName = "text";
break;
}
sql_fields << fields.at( i ).name() + " " + typeName;
}
QgsVectorDataProvider* provider = mLayer ? mLayer->dataProvider() : mProvider;
if ( provider->geometryType() != QGis::WKBNoGeometry )
{
// 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
sql_fields << QString( "geometry geometry(%1,%2)" ).arg( provider->geometryType() ).arg( provider->crs().postgisSrid() );
}
if ( provider->pkAttributeIndexes().size() == 1 )
{
mPkColumn = provider->pkAttributeIndexes()[0] + 1;
}
mCreationStr = "CREATE TABLE vtable (" + sql_fields.join( "," ) + ")";
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;
// specific members
QgsFeature mCurrentFeature;
QgsFeatureIterator mIterator;
bool mEof;
VTableCursor( VTable *vtab ) : mVtab( vtab ), mEof( true ) {}
void filter( 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*, size_t> currentGeometry() const
{
size_t blob_len = 0;
char* blob = 0;
const QgsGeometry* g = mCurrentFeature.constGeometry();
if ( g && ! g->isEmpty() )
{
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 = static_cast<QgsWKBTypes::Type>( provider->geometryType() );
geometryTypeStr = QgsWKBTypes::displayString( t );
geometryDim = QgsWKBTypes::coordDimensions( t );
if (( t != QgsWKBTypes::NoGeometry ) && ( t != QgsWKBTypes::Unknown ) )
geometryWkbType = static_cast<int>( t );
else
geometryWkbType = 0;
}
int vtable_create_connect( sqlite3* sql, void* aux, int argc, const char* const* argv, sqlite3_vtab **out_vtab, char** out_err, bool is_created )
{
Q_UNUSED( aux );
Q_UNUSED( is_created );
#define RETURN_CSTR_ERROR(err) if (out_err) {size_t s = strlen(err); *out_err=(char*)sqlite3_malloc(s+1); strncpy(*out_err, err, s);}
#define RETURN_CPPSTR_ERROR(err) if (out_err) {*out_err=(char*)sqlite3_malloc(err.size()+1); strncpy(*out_err, err.c_str(), err.size());}
if ( argc < 4 )
{
std::string err( "Missing arguments: layer_id | provider, source" );
RETURN_CPPSTR_ERROR( err );
return SQLITE_ERROR;
}
QScopedPointer<VTable> new_vtab;
QString vname( argv[2] );
int r;
if ( argc == 4 )
{
// CREATE VIRTUAL TABLE vtab USING QgsVLayer(layer_id)
// vtab = argv[2]
// layer_id = argv[3]
QString layerid( argv[3] );
if ( layerid.size() >= 1 && layerid[0] == '\'' )
{
layerid = layerid.mid( 1, layerid.size() - 2 );
}
QgsMapLayer *l = QgsMapLayerRegistry::instance()->mapLayer( layerid );
if ( l == 0 || l->type() != QgsMapLayer::VectorLayer )
{
if ( out_err )
{
std::string err( "Cannot find layer " );
err += argv[3];
RETURN_CPPSTR_ERROR( err );
}
return SQLITE_ERROR;
}
new_vtab.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 = argv[4];
QString encoding = "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( "''", "'" );
}
if ( source.size() >= 1 && source[0] == '\'' )
{
// trim and undouble single quotes
source = source.mid( 1, source.size() - 2 ).replace( "''", "'" );
}
try
{
new_vtab.reset( new VTable( sql, provider, source, argv[2], encoding ) );
}
catch ( std::runtime_error& e )
{
std::string err( e.what() );
RETURN_CPPSTR_ERROR( err );
return SQLITE_ERROR;
}
}
r = sqlite3_declare_vtab( sql, new_vtab->creationString().toLocal8Bit().constData() );
if ( r )
{
RETURN_CSTR_ERROR( sqlite3_errmsg( sql ) );
return r;
}
*out_vtab = ( sqlite3_vtab* )new_vtab.take();
return SQLITE_OK;
#undef RETURN_CSTR_ERROR
#undef RETURN_CPPSTR_ERROR
}
void db_init( sqlite3* db, ModuleContext* context )
{
if ( context->init )
{
// db already initialized
return;
}
// create metadata tables
initVirtualLayerMetadata( db );
}
int vtable_create( sqlite3* sql, void* aux, int argc, const char* const* argv, sqlite3_vtab **out_vtab, char** out_err )
{
try
{
db_init( sql, reinterpret_cast<ModuleContext*>( aux ) );
}
catch ( std::runtime_error& e )
{
if ( out_err )
{
*out_err = ( char* )sqlite3_malloc( strlen( e.what() ) + 1 );
strcpy( *out_err, e.what() );
}
return SQLITE_ERROR;
}
return vtable_create_connect( sql, aux, argc, argv, out_vtab, out_err, /* is_created */ true );
}
int vtable_connect( sqlite3* sql, void* aux, int argc, const char* const* argv, sqlite3_vtab **out_vtab, char** out_err )
{
return vtable_create_connect( sql, aux, argc, argv, out_vtab, out_err, /* is_created */ false );
}
int vtable_destroy( sqlite3_vtab *vtab )
{
if ( vtab )
{
delete reinterpret_cast<VTable*>( vtab );
}
return SQLITE_OK;
}
int vtable_disconnect( sqlite3_vtab *vtab )
{
if ( vtab )
{
delete reinterpret_cast<VTable*>( vtab );
}
return SQLITE_OK;
}
int vtable_rename( sqlite3_vtab *vtab, const char *new_name )
{
Q_UNUSED( vtab );
Q_UNUSED( new_name );
return SQLITE_OK;
}
int vtable_bestindex( sqlite3_vtab *pvtab, sqlite3_index_info* index_info )
{
VTable *vtab = ( VTable* )pvtab;
for ( int i = 0; i < index_info->nConstraint; i++ )
{
if (( index_info->aConstraint[i].usable ) &&
( vtab->pkColumn() == index_info->aConstraint[i].iColumn ) &&
( index_info->aConstraint[i].op == SQLITE_INDEX_CONSTRAINT_EQ ) )
{
// request for primary key filter
index_info->aConstraintUsage[i].argvIndex = 1;
index_info->aConstraintUsage[i].omit = 1;
index_info->idxNum = 1; // PK filter
index_info->estimatedCost = 1.0; // ??
//index_info->estimatedRows = 1;
index_info->idxStr = NULL;
index_info->needToFreeIdxStr = 0;
return SQLITE_OK;
}
if (( index_info->aConstraint[i].usable ) &&
( 0 == index_info->aConstraint[i].iColumn ) &&
( index_info->aConstraint[i].op == SQLITE_INDEX_CONSTRAINT_EQ ) )
{
// request for rtree filtering
index_info->aConstraintUsage[i].argvIndex = 1;
// do not test for equality, since it is used for filtering, not to return an actual value
index_info->aConstraintUsage[i].omit = 1;
index_info->idxNum = 2; // RTree filter
index_info->estimatedCost = 1.0; // ??
//index_info->estimatedRows = 1;
index_info->idxStr = NULL;
index_info->needToFreeIdxStr = 0;
return SQLITE_OK;
}
}
index_info->idxNum = 0;
index_info->estimatedCost = 10.0;
//index_info->estimatedRows = 10;
index_info->idxStr = NULL;
index_info->needToFreeIdxStr = 0;
return SQLITE_OK;
}
int vtable_open( sqlite3_vtab *vtab, sqlite3_vtab_cursor **out_cursor )
{
VTableCursor *ncursor = new VTableCursor(( VTable* )vtab );
*out_cursor = ( sqlite3_vtab_cursor* )ncursor;
return SQLITE_OK;
}
int vtable_close( sqlite3_vtab_cursor * cursor )
{
if ( cursor )
{
delete reinterpret_cast<VTableCursor*>( cursor );
}
return SQLITE_OK;
}
int vtable_filter( 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 = ( const char* )sqlite3_value_blob( argv[0] );
int bytes = sqlite3_value_bytes( argv[0] );
QgsRectangle r( spatialiteBlobBbox( blob, bytes ) );
request.setFilterRect( r );
}
VTableCursor *c = reinterpret_cast<VTableCursor*>( cursor );
c->filter( request );
return SQLITE_OK;
}
int vtable_next( sqlite3_vtab_cursor *cursor )
{
VTableCursor* c = reinterpret_cast<VTableCursor*>( cursor );
c->next();
return SQLITE_OK;
}
int vtable_eof( sqlite3_vtab_cursor *cursor )
{
VTableCursor* c = reinterpret_cast<VTableCursor*>( cursor );
return c->eof();
}
int vtable_rowid( sqlite3_vtab_cursor *cursor, sqlite3_int64 *out_rowid )
{
VTableCursor* c = reinterpret_cast<VTableCursor*>( cursor );
*out_rowid = c->currentId();
return SQLITE_OK;
}
int vtable_column( 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*, size_t> 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::Double:
sqlite3_result_double( ctxt, v.toDouble() );
break;
default:
{
sqlite3_result_text( ctxt, v.toString().toUtf8(), -1, SQLITE_TRANSIENT );
}
break;
}
}
return SQLITE_OK;
}
int vtable_findfunction( sqlite3_vtab *pVtab,
int nArg,
const char *zName,
void ( **pxFunc )( sqlite3_context*, int, sqlite3_value** ),
void **ppArg )
{
Q_UNUSED( pVtab );
Q_UNUSED( nArg );
Q_UNUSED( zName );
Q_UNUSED( pxFunc );
Q_UNUSED( ppArg );
return SQLITE_OK;
}
sqlite3_module module;
static QCoreApplication* core_app = 0;
static int module_argc = 1;
static char module_name[] = "qgsvlayer_module";
static char* module_argv[] = { module_name };
void module_destroy( void * d )
{
delete reinterpret_cast<ModuleContext*>( d );
if ( core_app )
{
delete core_app;
}
}
int qgsvlayer_module_init( 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() == 0 )
{
// if run standalone
core_app = new QCoreApplication( module_argc, module_argv );
QgsApplication::init();
QgsApplication::initQgis();
}
module.xCreate = vtable_create;
module.xConnect = vtable_connect;
module.xBestIndex = vtable_bestindex;
module.xDisconnect = vtable_disconnect;
module.xDestroy = vtable_destroy;
module.xOpen = vtable_open;
module.xClose = vtable_close;
module.xFilter = vtable_filter;
module.xNext = vtable_next;
module.xEof = vtable_eof;
module.xColumn = vtable_column;
module.xRowid = vtable_rowid;
module.xRename = vtable_rename;
module.xUpdate = NULL;
module.xBegin = NULL;
module.xSync = NULL;
module.xCommit = NULL;
module.xRollback = NULL;
module.xFindFunction = NULL;
module.xSavepoint = NULL;
module.xRelease = NULL;
module.xRollbackTo = NULL;
ModuleContext* context = new ModuleContext;
sqlite3_create_module_v2( db, "QgsVLayer", &module, context, module_destroy );
return rc;
}

View File

@ -0,0 +1,74 @@
/***************************************************************************
sqlite_vlayer_module.h : 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. *
* *
***************************************************************************/
#ifndef QGSVIRTUAL_SQLITE_LAYER_MODULE_H
#define QGSVIRTUAL_SQLITE_LAYER_MODULE_H
#ifdef __cplusplus
extern "C"
{
#endif
int vtable_create( sqlite3* sql, void* aux, int argc, const char* const* argv, sqlite3_vtab **out_vtab, char** out_err );
int vtable_connect( sqlite3* sql, void* aux, int argc, const char* const* argv, sqlite3_vtab **out_vtab, char** out_err );
int vtable_rename( sqlite3_vtab *vtab, const char *new_name );
int vtable_bestindex( sqlite3_vtab *vtab, sqlite3_index_info* );
int vtable_disconnect( sqlite3_vtab *vtab );
int vtable_destroy( sqlite3_vtab *vtab );
int vtable_open( sqlite3_vtab *vtab, sqlite3_vtab_cursor **out_cursor );
int vtable_close( sqlite3_vtab_cursor * );
int vtable_filter( sqlite3_vtab_cursor * cursor, int idxNum, const char *idxStr, int argc, sqlite3_value **argv );
int vtable_next( sqlite3_vtab_cursor *cursor );
int vtable_eof( sqlite3_vtab_cursor *cursor );
int vtable_column( sqlite3_vtab_cursor *cursor, sqlite3_context*, int );
int vtable_rowid( sqlite3_vtab_cursor *cursor, sqlite3_int64 *out_rowid );
int vtable_findfunction( sqlite3_vtab *pVtab,
int nArg,
const char *zName,
void ( **pxFunc )( sqlite3_context*, int, sqlite3_value** ),
void **ppArg );
int qgsvlayer_module_init( sqlite3 *db,
char **pzErrMsg,
void * unused /*const sqlite3_api_routines *pApi*/ );
#ifdef __cplusplus
}
#include <qgsgeometry.h>
/**
* Convert a spatialite geometry blob to a QgsGeometry
*
* \param blob Pointer to the raw blob memory
* \param size Size in bytes of the blob
* \returns a QgsGeometry (that are implicitly shared)
*/
QgsGeometry spatialite_blob_to_qgsgeometry( const unsigned char* blob, size_t size );
/**
* Init the SQLite file with proper metadata tables
*/
void initVirtualLayerMetadata( sqlite3* db );
#endif
#define VIRTUAL_LAYER_VERSION 1
#endif

View File

@ -64,7 +64,8 @@ ADD_PYTHON_TEST(PyQgsVectorFileWriter test_qgsvectorfilewriter.py)
ADD_PYTHON_TEST(PyQgsVectorLayer test_qgsvectorlayer.py)
ADD_PYTHON_TEST(PyQgsZonalStatistics test_qgszonalstatistics.py)
ADD_PYTHON_TEST(PyQgsMapLayerRegistry test_qgsmaplayerregistry.py)
ADD_PYTHON_TEST(PyQgsVirtualLayerProvider test_provider_virtual.py)
ADD_PYTHON_TEST(PyQgsVirtualLayerDefinition test_qgsvirtuallayerdefinition.py)
IF (NOT WIN32)
ADD_PYTHON_TEST(PyQgsLogger test_qgslogger.py)

View File

@ -0,0 +1,664 @@
# -*- coding: utf-8 -*-
"""QGIS Unit tests for QgsVirtualLayerProvider
.. note:: 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.
"""
__author__ = 'Hugo Mercier'
__date__ = '26/11/2015'
__copyright__ = 'Copyright 2015, The QGIS Project'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
import qgis
import os
import tempfile
import sys
from qgis.core import (QGis,
QgsVectorLayer,
QgsFeature,
QgsFeatureRequest,
QgsField,
QgsGeometry,
QgsPoint,
QgsMapLayerRegistry,
QgsRectangle,
QgsErrorMessage,
QgsProviderRegistry,
QgsVirtualLayerDefinition
)
from utilities import (unitTestDataPath,
getQgisTestApp,
TestCase,
unittest
)
from providertestbase import ProviderTestCase
from PyQt4.QtCore import *
try:
from pyspatialite import dbapi2 as sqlite3
except ImportError:
print "You should install pyspatialite to run the tests"
raise ImportError
import tempfile
# Convenience instances in case you may need them
QGISAPP, CANVAS, IFACE, PARENT = getQgisTestApp()
TEST_DATA_DIR = unitTestDataPath()
class TestQgsVirtualLayerProvider(TestCase, ProviderTestCase):
@classmethod
def setUpClass(cls):
"""Run before all tests"""
# Create the layer for the common provider tests
shp = os.path.join(TEST_DATA_DIR, 'provider/shapefile.shp')
d = QgsVirtualLayerDefinition()
d.addSource("vtab1", shp, "ogr")
d.setUid("pk")
cls.vl = QgsVectorLayer(d.toString(), u'test', u'virtual')
assert (cls.vl.isValid())
cls.provider = cls.vl.dataProvider()
@classmethod
def tearDownClass(cls):
"""Run after all tests"""
pass
def setUp(self):
"""Run before each test."""
self.testDataDir = unitTestDataPath()
print "****************************************************"
print "In method", self._testMethodName
print "****************************************************"
pass
def tearDown(self):
"""Run after each test."""
pass
def test_CsvNoGeometry(self):
l1 = QgsVectorLayer(os.path.join(self.testDataDir, "delimitedtext/test.csv") + "?type=csv&geomType=none&subsetIndex=no&watchFile=no", "test", "delimitedtext", False)
self.assertEqual(l1.isValid(), True)
QgsMapLayerRegistry.instance().addMapLayer(l1)
l2 = QgsVectorLayer("?layer_ref=" + l1.id(), "vtab", "virtual", False)
self.assertEqual(l2.isValid(), True)
QgsMapLayerRegistry.instance().removeMapLayer(l1.id())
def test_source_escaping(self):
# the source contains ':'
source = "file:///" + os.path.join(self.testDataDir, "delimitedtext/test.csv") + "?type=csv&geomType=none&subsetIndex=no&watchFile=no"
d = QgsVirtualLayerDefinition()
d.addSource("t", source, "delimitedtext")
l = QgsVectorLayer(d.toString(), "vtab", "virtual", False)
self.assertEqual(l.isValid(), True)
def test_source_escaping2(self):
def create_test_db(dbfile):
if os.path.exists(dbfile):
os.remove(dbfile)
con = sqlite3.connect(dbfile)
cur = con.cursor()
cur.execute("SELECT InitSpatialMetadata(1)")
cur.execute("CREATE TABLE test (id INTEGER, name TEXT)")
cur.execute("SELECT AddGeometryColumn('test', 'geometry', 4326, 'POINT', 'XY')")
sql = "INSERT INTO test (id, name, geometry) "
sql += "VALUES (1, 'toto',GeomFromText('POINT(0 0)',4326))"
cur.execute(sql)
con.close()
# the source contains ',' and single quotes
fn = os.path.join(tempfile.gettempdir(), "test,.db")
create_test_db(fn)
source = "dbname='%s' table=\"test\" (geometry) sql=" % fn
d = QgsVirtualLayerDefinition()
d.addSource("t", source, "spatialite")
l = QgsVectorLayer(d.toString(), "vtab", "virtual", False)
self.assertEqual(l.isValid(), True)
# the source contains ':' and single quotes
fn = os.path.join(tempfile.gettempdir(), "test:.db")
create_test_db(fn)
source = "dbname='%s' table=\"test\" (geometry) sql=" % fn
d = QgsVirtualLayerDefinition()
d.addSource("t", source, "spatialite")
l = QgsVectorLayer(d.toString(), "vtab", "virtual", False)
self.assertEqual(l.isValid(), True)
def test_DynamicGeometry(self):
l1 = QgsVectorLayer(os.path.join(self.testDataDir, "delimitedtext/testextpt.txt") + "?type=csv&delimiter=%7C&geomType=none&subsetIndex=no&watchFile=no", "test", "delimitedtext", False)
self.assertEqual(l1.isValid(), True)
QgsMapLayerRegistry.instance().addMapLayer(l1)
query = QUrl.toPercentEncoding("select *,makepoint(x,y) as geom from vtab1")
l2 = QgsVectorLayer("?layer_ref=%s&query=%s&geometry=geom:point:0&uid=id" % (l1.id(), query), "vtab", "virtual", False)
self.assertEqual(l2.isValid(), True)
QgsMapLayerRegistry.instance().removeMapLayer(l1)
def test_ShapefileWithGeometry(self):
l1 = QgsVectorLayer(os.path.join(self.testDataDir, "france_parts.shp"), "france_parts", "ogr", False)
self.assertEqual(l1.isValid(), True)
QgsMapLayerRegistry.instance().addMapLayer(l1)
# use a temporary file
l2 = QgsVectorLayer("?layer_ref=" + l1.id(), "vtab", "virtual", False)
self.assertEqual(l2.isValid(), True)
l2 = QgsVectorLayer("?layer_ref=%s:nn" % l1.id(), "vtab", "virtual", False)
self.assertEqual(l2.isValid(), True)
QgsMapLayerRegistry.instance().removeMapLayer(l1.id())
def test_Query(self):
l1 = QgsVectorLayer(os.path.join(self.testDataDir, "france_parts.shp"), "france_parts", "ogr", False)
self.assertEqual(l1.isValid(), True)
QgsMapLayerRegistry.instance().addMapLayer(l1)
ref_sum = sum(f.attributes()[0] for f in l1.getFeatures())
query = QUrl.toPercentEncoding("SELECT * FROM vtab1")
l2 = QgsVectorLayer("?layer_ref=%s&geometry=geometry:3:4326&query=%s&uid=OBJECTID" % (l1.id(), query), "vtab", "virtual", False)
self.assertEqual(l2.isValid(), True)
self.assertEqual(l2.dataProvider().geometryType(), 3)
ref_sum2 = sum(f.attributes()[0] for f in l2.getFeatures())
ref_sum3 = sum(f.id() for f in l2.getFeatures())
# check we have the same rows
self.assertEqual(ref_sum, ref_sum2)
# check the id is ok
self.assertEqual(ref_sum, ref_sum3)
# the same, without specifying the geometry column name
l2 = QgsVectorLayer("?layer_ref=%s&query=%s&uid=OBJECTID" % (l1.id(), query), "vtab", "virtual", False)
self.assertEqual(l2.isValid(), True)
self.assertEqual(l2.dataProvider().geometryType(), 3)
ref_sum2 = sum(f.attributes()[0] for f in l2.getFeatures())
ref_sum3 = sum(f.id() for f in l2.getFeatures())
# check we have the same rows
self.assertEqual(ref_sum, ref_sum2)
# check the id is ok
self.assertEqual(ref_sum, ref_sum3)
# with two geometry columns
query = QUrl.toPercentEncoding("SELECT *,geometry as geom FROM vtab1")
l2 = QgsVectorLayer("?layer_ref=%s&query=%s&uid=OBJECTID&geometry=geom:3:4326" % (l1.id(), query), "vtab", "virtual", False)
self.assertEqual(l2.isValid(), True)
self.assertEqual(l2.dataProvider().geometryType(), 3)
ref_sum2 = sum(f.attributes()[0] for f in l2.getFeatures())
ref_sum3 = sum(f.id() for f in l2.getFeatures())
# check we have the same rows
self.assertEqual(ref_sum, ref_sum2)
# check the id is ok
self.assertEqual(ref_sum, ref_sum3)
# with two geometry columns, but no geometry column specified (will take the first)
l2 = QgsVectorLayer("?layer_ref=%s&query=%s&uid=OBJECTID" % (l1.id(), query), "vtab", "virtual", False)
self.assertEqual(l2.isValid(), True)
self.assertEqual(l2.dataProvider().geometryType(), 3)
ref_sum2 = sum(f.attributes()[0] for f in l2.getFeatures())
ref_sum3 = sum(f.id() for f in l2.getFeatures())
# check we have the same rows
self.assertEqual(ref_sum, ref_sum2)
# check the id is ok
self.assertEqual(ref_sum, ref_sum3)
# the same, without geometry
query = QUrl.toPercentEncoding("SELECT * FROM ww")
l2 = QgsVectorLayer("?layer_ref=%s:ww&query=%s&uid=ObJeCtId&nogeometry" % (l1.id(), query), "vtab", "virtual", False)
self.assertEqual(l2.isValid(), True)
self.assertEqual(l2.dataProvider().geometryType(), 100) # NoGeometry
ref_sum2 = sum(f.attributes()[0] for f in l2.getFeatures())
ref_sum3 = sum(f.id() for f in l2.getFeatures())
self.assertEqual(ref_sum, ref_sum2)
self.assertEqual(ref_sum, ref_sum3)
# check that it fails when a query has a wrong geometry column
l2 = QgsVectorLayer("?layer_ref=%s&query=%s&geometry=geo" % (l1.id(), query), "vtab", "virtual", False)
self.assertEqual(l2.isValid(), False)
QgsMapLayerRegistry.instance().removeMapLayer(l1.id())
def test_QueryUrlEncoding(self):
l1 = QgsVectorLayer(os.path.join(self.testDataDir, "france_parts.shp"), "france_parts", "ogr", False)
self.assertEqual(l1.isValid(), True)
QgsMapLayerRegistry.instance().addMapLayer(l1)
query = str(QUrl.toPercentEncoding("SELECT * FROM vtab1"))
l2 = QgsVectorLayer("?layer_ref=%s&query=%s&uid=ObjectId&nogeometry" % (l1.id(), query), "vtab", "virtual", False)
self.assertEqual(l2.isValid(), True)
QgsMapLayerRegistry.instance().removeMapLayer(l1.id())
def test_QueryTableName(self):
l1 = QgsVectorLayer(os.path.join(self.testDataDir, "france_parts.shp"), "france_parts", "ogr", False)
self.assertEqual(l1.isValid(), True)
QgsMapLayerRegistry.instance().addMapLayer(l1)
query = str(QUrl.toPercentEncoding("SELECT * FROM vt"))
l2 = QgsVectorLayer("?layer_ref=%s:vt&query=%s&uid=ObJeCtId&nogeometry" % (l1.id(), query), "vtab", "virtual", False)
self.assertEqual(l2.isValid(), True)
self.assertEqual(l2.dataProvider().geometryType(), 100) # NoGeometry
QgsMapLayerRegistry.instance().removeMapLayer(l1.id())
def test_Join(self):
l1 = QgsVectorLayer(os.path.join(self.testDataDir, "points.shp"), "points", "ogr", False)
self.assertEqual(l1.isValid(), True)
QgsMapLayerRegistry.instance().addMapLayer(l1)
l2 = QgsVectorLayer(os.path.join(self.testDataDir, "points_relations.shp"), "points_relations", "ogr", False)
self.assertEqual(l2.isValid(), True)
QgsMapLayerRegistry.instance().addMapLayer(l2)
ref_sum = sum(f.attributes()[1] for f in l2.getFeatures())
# use a temporary file
query = QUrl.toPercentEncoding("select id,Pilots,vtab1.geometry from vtab1,vtab2 where intersects(vtab1.geometry,vtab2.geometry)")
l3 = QgsVectorLayer("?layer_ref=%s&layer_ref=%s&uid=id&query=%s&geometry=geometry:1:4326" % (l1.id(), l2.id(), query), "vtab", "virtual", False)
self.assertEqual(l3.isValid(), True)
self.assertEqual(l3.dataProvider().geometryType(), 1)
self.assertEqual(l3.dataProvider().fields().count(), 2)
ref_sum2 = sum(f.id() for f in l3.getFeatures())
self.assertEqual(ref_sum, ref_sum2)
QgsMapLayerRegistry.instance().removeMapLayer(l1)
QgsMapLayerRegistry.instance().removeMapLayer(l2)
def test_geometryTypes(self):
geo = [(1, "POINT", "(0 0)"),
(2, "LINESTRING", "(0 0,1 0)"),
(3, "POLYGON", "((0 0,1 0,1 1,0 0))"),
(4, "MULTIPOINT", "((1 1))"),
(5, "MULTILINESTRING", "((0 0,1 0),(0 1,1 1))"),
(6, "MULTIPOLYGON", "(((0 0,1 0,1 1,0 0)),((2 2,3 0,3 3,2 2)))")]
for wkb_type, wkt_type, wkt in geo:
l = QgsVectorLayer("%s?crs=epsg:4326" % wkt_type, "m1", "memory", False)
self.assertEqual(l.isValid(), True)
QgsMapLayerRegistry.instance().addMapLayer(l)
f1 = QgsFeature(1)
g = QgsGeometry.fromWkt(wkt_type + wkt)
self.assertEqual(g is None, False)
f1.setGeometry(g)
l.dataProvider().addFeatures([f1])
l2 = QgsVectorLayer("?layer_ref=%s" % l.id(), "vtab", "virtual", False)
self.assertEqual(l2.isValid(), True)
self.assertEqual(l2.dataProvider().featureCount(), 1)
self.assertEqual(l2.dataProvider().geometryType(), wkb_type)
QgsMapLayerRegistry.instance().removeMapLayer(l.id())
def test_embeddedLayer(self):
source = QUrl.toPercentEncoding(os.path.join(self.testDataDir, "france_parts.shp"))
l = QgsVectorLayer("?layer=ogr:%s" % source, "vtab", "virtual", False)
self.assertEqual(l.isValid(), True)
l = QgsVectorLayer("?layer=ogr:%s:nn" % source, "vtab", "virtual", False)
self.assertEqual(l.isValid(), True)
def test_filter_rect(self):
source = QUrl.toPercentEncoding(os.path.join(self.testDataDir, "france_parts.shp"))
query = QUrl.toPercentEncoding("select * from vtab where _search_frame_=BuildMbr(-2.10,49.38,-1.3,49.99,4326)")
l2 = QgsVectorLayer("?layer=ogr:%s:vtab&query=%s&uid=objectid" % (source, query), "vtab2", "virtual", False)
self.assertEqual(l2.isValid(), True)
self.assertEqual(l2.dataProvider().featureCount(), 1)
a = [fit.attributes()[4] for fit in l2.getFeatures()]
self.assertEqual(a, [u"Basse-Normandie"])
def test_recursiveLayer(self):
source = QUrl.toPercentEncoding(os.path.join(self.testDataDir, "france_parts.shp"))
l = QgsVectorLayer("?layer=ogr:%s" % source, "vtab", "virtual", False)
self.assertEqual(l.isValid(), True)
QgsMapLayerRegistry.instance().addMapLayer(l)
l2 = QgsVectorLayer("?layer_ref=" + l.id(), "vtab2", "virtual", False)
self.assertEqual(l2.isValid(), True)
QgsMapLayerRegistry.instance().removeMapLayer(l.id())
def test_no_geometry(self):
source = QUrl.toPercentEncoding(os.path.join(self.testDataDir, "france_parts.shp"))
l2 = QgsVectorLayer("?layer=ogr:%s:vtab&nogeometry" % source, "vtab2", "virtual", False)
self.assertEqual(l2.isValid(), True)
self.assertEqual(l2.dataProvider().geometryType(), 100) # NoGeometry
def test_reopen(self):
source = QUrl.toPercentEncoding(os.path.join(self.testDataDir, "france_parts.shp"))
tmp = os.path.join(tempfile.gettempdir(), "t.sqlite")
l = QgsVectorLayer("%s?layer=ogr:%s:vtab" % (tmp, source), "vtab2", "virtual", False)
self.assertEqual(l.isValid(), True)
l2 = QgsVectorLayer(tmp, "tt", "virtual", False)
self.assertEqual(l2.isValid(), True)
self.assertEqual(l2.dataProvider().geometryType(), 3)
self.assertEqual(l2.dataProvider().featureCount(), 4)
def test_reopen2(self):
source = QUrl.toPercentEncoding(os.path.join(self.testDataDir, "france_parts.shp"))
tmp = os.path.join(tempfile.gettempdir(), "t.sqlite")
l = QgsVectorLayer("%s?layer=ogr:%s:vtab&nogeometry" % (tmp, source), "vtab2", "virtual", False)
self.assertEqual(l.isValid(), True)
l2 = QgsVectorLayer(tmp, "tt", "virtual", False)
self.assertEqual(l2.isValid(), True)
self.assertEqual(l2.dataProvider().geometryType(), 100)
self.assertEqual(l2.dataProvider().featureCount(), 4)
def test_reopen3(self):
source = QUrl.toPercentEncoding(os.path.join(self.testDataDir, "france_parts.shp"))
tmp = os.path.join(tempfile.gettempdir(), "t.sqlite")
query = QUrl.toPercentEncoding("SELECT * FROM vtab")
l = QgsVectorLayer("%s?layer=ogr:%s:vtab&query=%s&uid=objectid&geometry=geometry:3:4326" % (tmp, source, query), "vtab2", "virtual", False)
self.assertEqual(l.isValid(), True)
l2 = QgsVectorLayer(tmp, "tt", "virtual", False)
self.assertEqual(l2.isValid(), True)
self.assertEqual(l2.dataProvider().geometryType(), 3)
self.assertEqual(l2.dataProvider().featureCount(), 4)
sumid = sum([f.id() for f in l2.getFeatures()])
self.assertEqual(sumid, 10659)
suma = sum([f.attributes()[1] for f in l2.getFeatures()])
self.assertEqual(suma, 3064.0)
def test_reopen4(self):
source = QUrl.toPercentEncoding(os.path.join(self.testDataDir, "france_parts.shp"))
tmp = os.path.join(tempfile.gettempdir(), "t.sqlite")
query = QUrl.toPercentEncoding("SELECT * FROM vtab")
l = QgsVectorLayer("%s?layer=ogr:%s:vtab&query=%s&uid=objectid&nogeometry" % (tmp, source, query), "vtab2", "virtual", False)
self.assertEqual(l.isValid(), True)
l2 = QgsVectorLayer(tmp, "tt", "virtual", False)
self.assertEqual(l2.isValid(), True)
self.assertEqual(l2.dataProvider().geometryType(), 100)
self.assertEqual(l2.dataProvider().featureCount(), 4)
sumid = sum([f.id() for f in l2.getFeatures()])
self.assertEqual(sumid, 10659)
suma = sum([f.attributes()[1] for f in l2.getFeatures()])
self.assertEqual(suma, 3064.0)
def test_refLayer(self):
l1 = QgsVectorLayer(os.path.join(self.testDataDir, "delimitedtext/test.csv") + "?type=csv&geomType=none&subsetIndex=no&watchFile=no", "test", "delimitedtext", False)
self.assertEqual(l1.isValid(), True)
QgsMapLayerRegistry.instance().addMapLayer(l1)
l2 = QgsVectorLayer("?layer_ref=" + l1.id(), "vtab", "virtual", False)
self.assertEqual(l2.isValid(), True)
# now delete the layer
QgsMapLayerRegistry.instance().removeMapLayer(l1.id())
# check that it does not crash
print sum([f.id() for f in l2.getFeatures()])
def test_refLayers(self):
l1 = QgsVectorLayer(os.path.join(self.testDataDir, "delimitedtext/test.csv") + "?type=csv&geomType=none&subsetIndex=no&watchFile=no", "test", "delimitedtext", False)
self.assertEqual(l1.isValid(), True)
QgsMapLayerRegistry.instance().addMapLayer(l1)
# cf qgis bug #12266
for i in range(10):
q = QUrl.toPercentEncoding("select * from t" + str(i))
l2 = QgsVectorLayer("?layer_ref=%s:t%d&query=%s&uid=id" % (l1.id(), i, q), "vtab", "virtual", False)
QgsMapLayerRegistry.instance().addMapLayer(l2)
self.assertEqual(l2.isValid(), True)
s = sum([f.id() for f in l2.dataProvider().getFeatures()])
self.assertEqual(sum([f.id() for f in l2.getFeatures()]), 21)
QgsMapLayerRegistry.instance().removeMapLayer(l2.id())
def test_refLayers2(self):
l1 = QgsVectorLayer(os.path.join(self.testDataDir, "delimitedtext/test.csv") + "?type=csv&geomType=none&subsetIndex=no&watchFile=no", "test", "delimitedtext", False)
self.assertEqual(l1.isValid(), True)
QgsMapLayerRegistry.instance().addMapLayer(l1)
# referenced layers cannot be stored !
tmp = os.path.join(tempfile.gettempdir(), "t.sqlite")
l2 = QgsVectorLayer("%s?layer_ref=%s" % (tmp, l1.id()), "tt", "virtual", False)
self.assertEqual(l2.isValid(), False)
self.assertEqual("Cannot store referenced layers" in l2.dataProvider().error().message(), True)
def test_sql(self):
l1 = QgsVectorLayer(os.path.join(self.testDataDir, "delimitedtext/test.csv") + "?type=csv&geomType=none&subsetIndex=no&watchFile=no", "test", "delimitedtext", False)
self.assertEqual(l1.isValid(), True)
QgsMapLayerRegistry.instance().addMapLayer(l1)
l3 = QgsVectorLayer("?query=SELECT * FROM test", "tt", "virtual")
self.assertEqual(l3.isValid(), True)
s = sum(f.id() for f in l3.getFeatures())
self.assertEqual(s, 15)
def test_sql2(self):
l2 = QgsVectorLayer(os.path.join(self.testDataDir, "france_parts.shp"), "france_parts", "ogr", False)
self.assertEqual(l2.isValid(), True)
QgsMapLayerRegistry.instance().addMapLayer(l2)
query = QUrl.toPercentEncoding("SELECT * FROM france_parts")
l4 = QgsVectorLayer("?query=%s" % query, "tt", "virtual")
self.assertEqual(l4.isValid(), True)
self.assertEqual(l4.dataProvider().geometryType(), 3)
self.assertEqual(l4.dataProvider().crs().postgisSrid(), 4326)
n = 0
r = QgsFeatureRequest(QgsRectangle(-1.677, 49.624, -0.816, 49.086))
for f in l4.getFeatures(r):
self.assertEqual(f.geometry() is not None, True)
self.assertEqual(f.attributes()[0], 2661)
n += 1
self.assertEqual(n, 1)
# use uid
query = QUrl.toPercentEncoding("SELECT * FROM france_parts")
l5 = QgsVectorLayer("?query=%s&geometry=geometry:polygon:4326&uid=ObjectId" % query, "tt", "virtual")
self.assertEqual(l5.isValid(), True)
idSum = sum(f.id() for f in l5.getFeatures())
self.assertEqual(idSum, 10659)
r = QgsFeatureRequest(2661)
idSum2 = sum(f.id() for f in l5.getFeatures(r))
self.assertEqual(idSum2, 2661)
r = QgsFeatureRequest()
r.setFilterFids([2661, 2664])
self.assertEqual(sum(f.id() for f in l5.getFeatures(r)), 2661 + 2664)
# test attribute subset
r = QgsFeatureRequest()
r.setFlags(QgsFeatureRequest.SubsetOfAttributes)
r.setSubsetOfAttributes([1])
s = [(f.id(), f.attributes()[1]) for f in l5.getFeatures(r)]
self.assertEqual(sum(map(lambda x: x[0], s)), 10659)
self.assertEqual(sum(map(lambda x: x[1], s)), 3064.0)
# test NoGeometry
# by request flag
r = QgsFeatureRequest()
r.setFlags(QgsFeatureRequest.NoGeometry)
self.assertEqual(all([f.geometry() is None for f in l5.getFeatures(r)]), True)
# test subset
self.assertEqual(l5.dataProvider().featureCount(), 4)
l5.setSubsetString("ObjectId = 2661")
idSum2 = sum(f.id() for f in l5.getFeatures(r))
self.assertEqual(idSum2, 2661)
self.assertEqual(l5.dataProvider().featureCount(), 1)
def test_sql3(self):
l2 = QgsVectorLayer(os.path.join(self.testDataDir, "france_parts.shp"), "france_parts", "ogr", False)
self.assertEqual(l2.isValid(), True)
QgsMapLayerRegistry.instance().addMapLayer(l2)
# unnamed column
query = QUrl.toPercentEncoding("SELECT 42")
l4 = QgsVectorLayer("?query=%s" % query, "tt", "virtual", False)
self.assertEqual(l4.isValid(), False)
self.assertEqual("Result column #1 has no name" in l4.dataProvider().error().message(), True)
def test_sql_field_types(self):
query = QUrl.toPercentEncoding("SELECT 42 as t, 'ok'||'ok' as t2, GeomFromText('') as t3, 3.14*2 as t4")
l4 = QgsVectorLayer("?query=%s" % query, "tt", "virtual", False)
self.assertEqual(l4.isValid(), True)
self.assertEqual(l4.dataProvider().fields().at(0).name(), "t")
self.assertEqual(l4.dataProvider().fields().at(0).type(), QVariant.Int)
self.assertEqual(l4.dataProvider().fields().at(1).name(), "t2")
self.assertEqual(l4.dataProvider().fields().at(1).type(), QVariant.String)
self.assertEqual(l4.dataProvider().fields().at(2).name(), "t3")
self.assertEqual(l4.dataProvider().fields().at(2).type(), QVariant.String)
self.assertEqual(l4.dataProvider().fields().at(3).name(), "t4")
self.assertEqual(l4.dataProvider().fields().at(3).type(), QVariant.Double)
# with type annotations
query = QUrl.toPercentEncoding("SELECT '42.0' as t /*:real*/, 3 as t2/*:text */, GeomFromText('') as t3 /*:multiPoInT:4326 */, 3.14*2 as t4/*:int*/")
l4 = QgsVectorLayer("?query=%s" % query, "tt", "virtual", False)
self.assertEqual(l4.isValid(), True)
self.assertEqual(l4.dataProvider().fields().at(0).name(), "t")
self.assertEqual(l4.dataProvider().fields().at(0).type(), QVariant.Double)
self.assertEqual(l4.dataProvider().fields().at(1).name(), "t2")
self.assertEqual(l4.dataProvider().fields().at(1).type(), QVariant.String)
self.assertEqual(l4.dataProvider().fields().at(2).name(), "t4")
self.assertEqual(l4.dataProvider().fields().at(2).type(), QVariant.Int)
self.assertEqual(l4.dataProvider().geometryType(), 4) # multipoint
# test value types (!= from declared column types)
for f in l4.getFeatures():
self.assertEqual(f.attributes()[0], "42.0")
self.assertEqual(f.attributes()[1], 3)
self.assertEqual(f.attributes()[2], 6.28)
# with type annotations and url options
query = QUrl.toPercentEncoding("SELECT 1 as id /*:int*/, geomfromtext('point(0 0)',4326) as geometry/*:point:4326*/")
l4 = QgsVectorLayer("?query=%s&geometry=geometry" % query, "tt", "virtual", False)
self.assertEqual(l4.isValid(), True)
self.assertEqual(l4.dataProvider().geometryType(), 1) # point
# with type annotations and url options (2)
query = QUrl.toPercentEncoding("SELECT 1 as id /*:int*/, 3.14 as f, geomfromtext('point(0 0)',4326) as geometry/*:point:4326*/")
l4 = QgsVectorLayer("?query=%s&geometry=geometry&field=id:text" % query, "tt", "virtual", False)
self.assertEqual(l4.isValid(), True)
self.assertEqual(l4.dataProvider().fields().at(0).name(), "id")
self.assertEqual(l4.dataProvider().fields().at(0).type(), QVariant.String)
self.assertEqual(l4.dataProvider().fields().at(1).name(), "f")
self.assertEqual(l4.dataProvider().fields().at(1).type(), QVariant.Double)
self.assertEqual(l4.dataProvider().geometryType(), 1) # point
def test_sql3b(self):
query = QUrl.toPercentEncoding("SELECT GeomFromText('POINT(0 0)') as geom")
l4 = QgsVectorLayer("?query=%s&geometry=geom" % query, "tt", "virtual", False)
self.assertEqual(l4.isValid(), True)
self.assertEqual(l4.dataProvider().geometryType(), 1)
# forced geometry type
query = QUrl.toPercentEncoding("SELECT GeomFromText('POINT(0 0)') as geom")
l4 = QgsVectorLayer("?query=%s&geometry=geom:point:0" % query, "tt", "virtual", False)
self.assertEqual(l4.isValid(), True)
self.assertEqual(l4.dataProvider().geometryType(), 1)
query = QUrl.toPercentEncoding("SELECT CastToPoint(GeomFromText('POINT(0 0)')) as geom")
l4 = QgsVectorLayer("?query=%s" % query, "tt", "virtual", False)
self.assertEqual(l4.isValid(), True)
self.assertEqual(l4.dataProvider().geometryType(), 1)
def test_sql4(self):
l2 = QgsVectorLayer(os.path.join(self.testDataDir, "france_parts.shp"), "france_parts", "ogr", False)
self.assertEqual(l2.isValid(), True)
QgsMapLayerRegistry.instance().addMapLayer(l2)
query = QUrl.toPercentEncoding("SELECT OBJECTId from france_parts")
l4 = QgsVectorLayer("?query=%s" % query, "tt", "virtual", False)
self.assertEqual(l4.isValid(), True)
s = sum(f.attributes()[0] for f in l4.getFeatures())
self.assertEqual(s, 10659)
def test_layer_name(self):
# test space and upper case
l2 = QgsVectorLayer(os.path.join(self.testDataDir, "france_parts.shp"), "FranCe parts", "ogr", False)
self.assertEqual(l2.isValid(), True)
QgsMapLayerRegistry.instance().addMapLayer(l2)
query = QUrl.toPercentEncoding('SELECT OBJECTId from "FranCe parts"')
l4 = QgsVectorLayer("?query=%s" % query, "tt", "virtual", False)
self.assertEqual(l4.isValid(), True)
s = sum(f.attributes()[0] for f in l4.getFeatures())
self.assertEqual(s, 10659)
def test_encoding(self):
# changes encoding on a shapefile (the only provider supporting setEncoding)
source = QUrl.toPercentEncoding(os.path.join(self.testDataDir, "shp_latin1.dbf"))
l = QgsVectorLayer("?layer=ogr:%s:fp:latin1" % source, "vtab", "virtual", False)
self.assertEqual(l.isValid(), True)
for f in l.getFeatures():
self.assertEqual(f.attributes()[1], u"accents éàè")
# use UTF-8 now
l = QgsVectorLayer("?layer=ogr:%s:fp:UTF-8" % source, "vtab", "virtual", False)
self.assertEqual(l.isValid(), True)
for f in l.getFeatures():
self.assertEqual(f.attributes()[1], u"accents \ufffd\ufffd\ufffd") # invalid unicode characters
def test_rowid(self):
source = QUrl.toPercentEncoding(os.path.join(self.testDataDir, "france_parts.shp"))
query = QUrl.toPercentEncoding("select rowid as uid, * from vtab limit 1 offset 3")
l = QgsVectorLayer("?layer=ogr:%s:vtab&query=%s" % (source, query), "vtab2", "virtual", False)
# the last line must have a fixed rowid (not an autoincrement)
for f in l.getFeatures():
lid = f.attributes()[0]
self.assertEqual(lid, 3)
def test_geometry_conversion(self):
query = QUrl.toPercentEncoding("select geomfromtext('multipoint((0 0),(1 1))') as geom")
l = QgsVectorLayer("?query=%s&geometry=geom:multipoint:0" % query, "tt", "virtual", False)
self.assertEqual(l.isValid(), True)
for f in l.getFeatures():
self.assertEqual(f.geometry().exportToWkt().lower().startswith("multipoint"), True)
self.assertEqual("),(" in f.geometry().exportToWkt(), True) # has two points
query = QUrl.toPercentEncoding("select geomfromtext('multipolygon(((0 0,1 0,1 1,0 1,0 0)),((0 1,1 1,1 2,0 2,0 1)))') as geom")
l = QgsVectorLayer("?query=%s&geometry=geom:multipolygon:0" % query, "tt", "virtual", False)
self.assertEqual(l.isValid(), True)
for f in l.getFeatures():
self.assertEqual(f.geometry().exportToWkt().lower().startswith("multipolygon"), True)
self.assertEqual(")),((" in f.geometry().exportToWkt(), True) # has two polygons
query = QUrl.toPercentEncoding("select geomfromtext('multilinestring((0 0,1 0,1 1,0 1,0 0),(0 1,1 1,1 2,0 2,0 1))') as geom")
l = QgsVectorLayer("?query=%s&geometry=geom:multilinestring:0" % query, "tt", "virtual", False)
self.assertEqual(l.isValid(), True)
for f in l.getFeatures():
self.assertEqual(f.geometry().exportToWkt().lower().startswith("multilinestring"), True)
self.assertEqual("),(" in f.geometry().exportToWkt(), True) # has two linestrings
def test_queryOnMemoryLayer(self):
ml = QgsVectorLayer("Point?srid=EPSG:4326&field=a:int", "mem", "memory")
self.assertEqual(ml.isValid(), True)
QgsMapLayerRegistry.instance().addMapLayer(ml)
ml.startEditing()
f1 = QgsFeature(ml.fields())
f1.setGeometry(QgsGeometry.fromWkt('POINT(0 0)'))
f2 = QgsFeature(ml.fields())
f2.setGeometry(QgsGeometry.fromWkt('POINT(1 1)'))
ml.addFeatures([f1, f2])
ml.commitChanges()
vl = QgsVectorLayer("?query=select * from mem", "vl", "virtual")
self.assertEqual(vl.isValid(), True)
self.assertEqual(ml.featureCount(), vl.featureCount())
# test access to pending features as well
ml.startEditing()
f3 = QgsFeature(ml.fields())
ml.addFeatures([f3])
self.assertEqual(ml.featureCount(), vl.featureCount())
if __name__ == '__main__':
unittest.main()

View File

@ -38,7 +38,7 @@ except:
# (changes which lower this threshold are welcomed though!)
ACCEPTABLE_MISSING_CLASSES = 69
ACCEPTABLE_MISSING_MEMBERS = 264
ACCEPTABLE_MISSING_MEMBERS = 245
class TestQgsSipCoverage(TestCase):
@ -70,6 +70,9 @@ class TestQgsSipCoverage(TestCase):
for m in parser.bindable_members:
if m[0] in bound_objects:
obj = bound_objects[m[0]]
if "::" in m[0] and m[0].split("::")[1] == m[1]:
# skip constructors of nested classes
continue
# try two different methods of checking for member existence
try:

View File

@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
"""QGIS Unit tests for QgsVirtualLayerDefinition
.. note:: 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.
"""
__author__ = 'Hugo Mercier'
__date__ = '10/12/2015'
__copyright__ = 'Copyright 2015, The QGIS Project'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
import qgis
from qgis.core import (QGis,
QgsField,
QgsWKBTypes,
QgsFields,
QgsVirtualLayerDefinition
)
from utilities import (TestCase, unittest)
from PyQt4.QtCore import QVariant
class TestQgsVirtualLayerDefinition(TestCase):
def test1(self):
d = QgsVirtualLayerDefinition()
self.assertEqual(d.toString(), "")
d.setFilePath("/file")
self.assertEqual(d.toString(), "/file")
self.assertEqual(QgsVirtualLayerDefinition.fromUrl(d.toUrl()).filePath(), "/file")
d.setFilePath("C:\\file")
self.assertEqual(d.toString(), "C:%5Cfile")
self.assertEqual(QgsVirtualLayerDefinition.fromUrl(d.toUrl()).filePath(), "C:\\file")
d.setQuery("SELECT * FROM mytable")
self.assertEqual(QgsVirtualLayerDefinition.fromUrl(d.toUrl()).query(), "SELECT * FROM mytable")
q = u"SELECT * FROM tableéé /*:int*/"
d.setQuery(q)
self.assertEqual(QgsVirtualLayerDefinition.fromUrl(d.toUrl()).query(), q)
s1 = u"file://foo&bar=okié"
d.addSource("name", s1, "provider", "utf8")
self.assertEqual(QgsVirtualLayerDefinition.fromUrl(d.toUrl()).sourceLayers()[0].source(), s1)
n1 = u"éé ok"
d.addSource(n1, s1, "provider")
self.assertEqual(QgsVirtualLayerDefinition.fromUrl(d.toUrl()).sourceLayers()[1].name(), n1)
d.addSource("ref1", "id0001")
self.assertEqual(QgsVirtualLayerDefinition.fromUrl(d.toUrl()).sourceLayers()[2].reference(), "id0001")
d.setGeometryField("geom")
self.assertEqual(QgsVirtualLayerDefinition.fromUrl(d.toUrl()).geometryField(), "geom")
d.setGeometryWkbType(QgsWKBTypes.Point)
self.assertEqual(QgsVirtualLayerDefinition.fromUrl(d.toUrl()).geometryWkbType(), QgsWKBTypes.Point)
f = QgsFields()
f.append(QgsField("a", QVariant.Int))
f.append(QgsField("f", QVariant.Double))
f.append(QgsField("s", QVariant.String))
d.setFields(f)
f2 = QgsVirtualLayerDefinition.fromUrl(d.toUrl()).fields()
self.assertEqual(f[0].name(), f2[0].name())
self.assertEqual(f[0].type(), f2[0].type())
self.assertEqual(f[1].name(), f2[1].name())
self.assertEqual(f[1].type(), f2[1].type())
self.assertEqual(f[2].name(), f2[2].name())
self.assertEqual(f[2].type(), f2[2].type())
if __name__ == '__main__':
unittest.main()

BIN
tests/testdata/shp_latin1.dbf vendored Normal file

Binary file not shown.