mirror of
https://github.com/qgis/QGIS.git
synced 2025-06-19 00:02:48 -04:00
Merge pull request #2568 from mhugo/vlayers
Add support for virtual layers
This commit is contained in:
commit
60f246a350
@ -141,6 +141,7 @@
|
||||
%Include qgsvectorlayerfeatureiterator.sip
|
||||
%Include qgsvisibilitypresetcollection.sip
|
||||
%Include qgslayerdefinition.sip
|
||||
%Include qgsvirtuallayerdefinition.sip
|
||||
|
||||
%Include auth/qgsauthcertutils.sip
|
||||
%Include auth/qgsauthconfig.sip
|
||||
|
125
python/core/qgsvirtuallayerdefinition.sip
Normal file
125
python/core/qgsvirtuallayerdefinition.sip
Normal 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;
|
||||
};
|
||||
|
@ -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})
|
||||
|
@ -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})
|
||||
|
430
python/plugins/db_manager/db_plugins/vlayers/connector.py
Normal file
430
python/plugins/db_manager/db_plugins/vlayers/connector.py
Normal 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()
|
111
python/plugins/db_manager/db_plugins/vlayers/data_model.py
Normal file
111
python/plugins/db_manager/db_plugins/vlayers/data_model.py
Normal 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
|
46
python/plugins/db_manager/db_plugins/vlayers/info_model.py
Normal file
46
python/plugins/db_manager/db_plugins/vlayers/info_model.py
Normal 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
|
191
python/plugins/db_manager/db_plugins/vlayers/plugin.py
Normal file
191
python/plugins/db_manager/db_plugins/vlayers/plugin.py
Normal 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
|
@ -0,0 +1,5 @@
|
||||
<RCC>
|
||||
<qresource prefix="/db_manager/vlayers">
|
||||
<file alias="icon">vlayer.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
136
python/plugins/db_manager/db_plugins/vlayers/sql_dictionary.py
Normal file
136
python/plugins/db_manager/db_plugins/vlayers/sql_dictionary.py
Normal 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}
|
242
python/plugins/db_manager/db_plugins/vlayers/vlayer.svg
Normal file
242
python/plugins/db_manager/db_plugins/vlayers/vlayer.svg
Normal 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 |
@ -202,6 +202,7 @@ SET(QGIS_CORE_SRCS
|
||||
qgsvectorlayerundocommand.cpp
|
||||
qgsvectorsimplifymethod.cpp
|
||||
qgsvisibilitypresetcollection.cpp
|
||||
qgsvirtuallayerdefinition.cpp
|
||||
qgsxmlutils.cpp
|
||||
qgsslconnect.cpp
|
||||
qgslocalec.cpp
|
||||
|
250
src/core/qgsvirtuallayerdefinition.cpp
Normal file
250
src/core/qgsvirtuallayerdefinition.cpp
Normal 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;
|
||||
}
|
166
src/core/qgsvirtuallayerdefinition.h
Normal file
166
src/core/qgsvirtuallayerdefinition.h
Normal 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
|
@ -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)
|
||||
|
56
src/providers/virtual/CMakeLists.txt
Normal file
56
src/providers/virtual/CMakeLists.txt
Normal 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}
|
||||
)
|
25
src/providers/virtual/qgsslottofunction.h
Normal file
25
src/providers/virtual/qgsslottofunction.h
Normal 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
|
||||
|
220
src/providers/virtual/qgsvirtuallayerblob.cpp
Normal file
220
src/providers/virtual/qgsvirtuallayerblob.cpp
Normal 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 ) );
|
||||
}
|
71
src/providers/virtual/qgsvirtuallayerblob.h
Normal file
71
src/providers/virtual/qgsvirtuallayerblob.h
Normal 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
|
236
src/providers/virtual/qgsvirtuallayerfeatureiterator.cpp
Normal file
236
src/providers/virtual/qgsvirtuallayerfeatureiterator.cpp
Normal 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 ) );
|
||||
}
|
71
src/providers/virtual/qgsvirtuallayerfeatureiterator.h
Normal file
71
src/providers/virtual/qgsvirtuallayerfeatureiterator.h
Normal 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
|
636
src/providers/virtual/qgsvirtuallayerprovider.cpp
Normal file
636
src/providers/virtual/qgsvirtuallayerprovider.cpp
Normal 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()
|
||||
{
|
||||
}
|
146
src/providers/virtual/qgsvirtuallayerprovider.h
Normal file
146
src/providers/virtual/qgsvirtuallayerprovider.h
Normal 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
|
274
src/providers/virtual/qgsvirtuallayerqueryparser.cpp
Normal file
274
src/providers/virtual/qgsvirtuallayerqueryparser.cpp
Normal 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
|
70
src/providers/virtual/qgsvirtuallayerqueryparser.h
Normal file
70
src/providers/virtual/qgsvirtuallayerqueryparser.h
Normal 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
|
189
src/providers/virtual/qgsvirtuallayersqlitehelper.cpp
Normal file
189
src/providers/virtual/qgsvirtuallayersqlitehelper.cpp
Normal 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_; }
|
||||
|
||||
}
|
94
src/providers/virtual/qgsvirtuallayersqlitehelper.h
Normal file
94
src/providers/virtual/qgsvirtuallayersqlitehelper.h
Normal 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
|
682
src/providers/virtual/qgsvirtuallayersqlitemodule.cpp
Normal file
682
src/providers/virtual/qgsvirtuallayersqlitemodule.cpp
Normal 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;
|
||||
}
|
74
src/providers/virtual/qgsvirtuallayersqlitemodule.h
Normal file
74
src/providers/virtual/qgsvirtuallayersqlitemodule.h
Normal 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
|
@ -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)
|
||||
|
664
tests/src/python/test_provider_virtual.py
Normal file
664
tests/src/python/test_provider_virtual.py
Normal 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()
|
@ -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:
|
||||
|
78
tests/src/python/test_qgsvirtuallayerdefinition.py
Normal file
78
tests/src/python/test_qgsvirtuallayerdefinition.py
Normal 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
BIN
tests/testdata/shp_latin1.dbf
vendored
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user