ExecuteSQL.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. """
  2. ***************************************************************************
  3. ExecuteSQL.py -- use virtual layers to execute SQL on any sources
  4. ---------------------
  5. Date : Jan 2016
  6. Copyright : (C) 2016 by Hugo Mercier
  7. Email : hugo dot mercier at oslandia dot com
  8. ***************************************************************************
  9. * *
  10. * This program is free software; you can redistribute it and/or modify *
  11. * it under the terms of the GNU General Public License as published by *
  12. * the Free Software Foundation; either version 2 of the License, or *
  13. * (at your option) any later version. *
  14. * *
  15. ***************************************************************************
  16. """
  17. __author__ = 'Hugo Mercier'
  18. __date__ = 'January 2016'
  19. __copyright__ = '(C) 2016, Hugo Mercier'
  20. from qgis.core import (Qgis,
  21. QgsVirtualLayerDefinition,
  22. QgsVectorLayer,
  23. QgsWkbTypes,
  24. QgsProcessingAlgorithm,
  25. QgsProcessingParameterMultipleLayers,
  26. QgsProcessingParameterDefinition,
  27. QgsExpression,
  28. QgsProcessingUtils,
  29. QgsProcessingParameterString,
  30. QgsProcessingParameterEnum,
  31. QgsProcessingParameterCrs,
  32. QgsProcessingParameterFeatureSink,
  33. QgsFeatureSink,
  34. QgsProcessingException,
  35. QgsVectorFileWriter,
  36. QgsProject)
  37. from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
  38. class ParameterExecuteSql(QgsProcessingParameterDefinition):
  39. def __init__(self, name='', description=''):
  40. super().__init__(name, description)
  41. self.setMetadata({
  42. 'widget_wrapper': 'processing.algs.qgis.ui.ExecuteSQLWidget.ExecuteSQLWidgetWrapper'
  43. })
  44. def type(self):
  45. return 'execute_sql'
  46. def clone(self):
  47. return ParameterExecuteSql(self.name(), self.description())
  48. class ExecuteSQL(QgisAlgorithm):
  49. """ This algorithm allows executing an SQL query on a set of input
  50. vector layers thanks to the virtual layer provider
  51. """
  52. INPUT_DATASOURCES = 'INPUT_DATASOURCES'
  53. INPUT_QUERY = 'INPUT_QUERY'
  54. INPUT_UID_FIELD = 'INPUT_UID_FIELD'
  55. INPUT_GEOMETRY_FIELD = 'INPUT_GEOMETRY_FIELD'
  56. INPUT_GEOMETRY_TYPE = 'INPUT_GEOMETRY_TYPE'
  57. INPUT_GEOMETRY_CRS = 'INPUT_GEOMETRY_CRS'
  58. OUTPUT = 'OUTPUT'
  59. def group(self):
  60. return self.tr('Vector general')
  61. def groupId(self):
  62. return 'vectorgeneral'
  63. def __init__(self):
  64. super().__init__()
  65. self.geometry_types = [
  66. (None, self.tr('Autodetect')),
  67. (Qgis.WkbType.NoGeometry, self.tr('No geometry')),
  68. (Qgis.WkbType.Point, self.tr('Point')),
  69. (Qgis.WkbType.LineString, self.tr('LineString')),
  70. (Qgis.WkbType.Polygon, self.tr('Polygon')),
  71. (Qgis.WkbType.MultiPoint, self.tr('MultiPoint')),
  72. (Qgis.WkbType.MultiLineString, self.tr('MultiLineString')),
  73. (Qgis.WkbType.MultiPolygon, self.tr('MultiPolygon'))]
  74. def flags(self):
  75. return super().flags() | QgsProcessingAlgorithm.FlagNoThreading
  76. def initAlgorithm(self, config=None):
  77. self.addParameter(QgsProcessingParameterMultipleLayers(name=self.INPUT_DATASOURCES,
  78. description=self.tr('Input data sources (called input1, .., inputN in the query)'),
  79. optional=True))
  80. self.addParameter(ParameterExecuteSql(name=self.INPUT_QUERY, description=self.tr('SQL query')))
  81. self.addParameter(QgsProcessingParameterString(name=self.INPUT_UID_FIELD,
  82. description=self.tr('Unique identifier field'), optional=True))
  83. self.addParameter(QgsProcessingParameterString(name=self.INPUT_GEOMETRY_FIELD,
  84. description=self.tr('Geometry field'), optional=True))
  85. self.addParameter(QgsProcessingParameterEnum(self.INPUT_GEOMETRY_TYPE,
  86. self.tr('Geometry type'),
  87. options=[t[1] for t in self.geometry_types],
  88. defaultValue=0))
  89. self.addParameter(QgsProcessingParameterCrs(self.INPUT_GEOMETRY_CRS,
  90. self.tr('CRS'), optional=True))
  91. self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('SQL Output')))
  92. def name(self):
  93. return 'executesql'
  94. def displayName(self):
  95. return self.tr('Execute SQL')
  96. def processAlgorithm(self, parameters, context, feedback):
  97. layers = self.parameterAsLayerList(parameters, self.INPUT_DATASOURCES, context)
  98. query = self.parameterAsString(parameters, self.INPUT_QUERY, context)
  99. uid_field = self.parameterAsString(parameters, self.INPUT_UID_FIELD, context)
  100. geometry_field = self.parameterAsString(parameters, self.INPUT_GEOMETRY_FIELD, context)
  101. geometry_type = self.geometry_types[
  102. self.parameterAsEnum(parameters, self.INPUT_GEOMETRY_TYPE, context)
  103. ][0]
  104. geometry_crs = self.parameterAsCrs(parameters, self.INPUT_GEOMETRY_CRS, context)
  105. df = QgsVirtualLayerDefinition()
  106. for layerIdx, layer in enumerate(layers):
  107. # Issue https://github.com/qgis/QGIS/issues/24041
  108. # When using this algorithm from the graphic modeler, it may try to
  109. # access (thanks the QgsVirtualLayerProvider) to memory layer that
  110. # belongs to temporary QgsMapLayerStore, not project.
  111. # So, we write them to disk is this is the case.
  112. if context.project() and not context.project().mapLayer(layer.id()):
  113. basename = "memorylayer." + QgsVectorFileWriter.supportedFormatExtensions()[0]
  114. tmp_path = QgsProcessingUtils.generateTempFilename(basename, context)
  115. QgsVectorFileWriter.writeAsVectorFormat(
  116. layer, tmp_path, layer.dataProvider().encoding())
  117. df.addSource(f'input{layerIdx + 1}', tmp_path, "ogr")
  118. else:
  119. df.addSource(f'input{layerIdx + 1}', layer.id())
  120. if query == '':
  121. raise QgsProcessingException(
  122. self.tr('Empty SQL. Please enter valid SQL expression and try again.'))
  123. localContext = self.createExpressionContext(parameters, context)
  124. expandedQuery = QgsExpression.replaceExpressionText(query, localContext)
  125. df.setQuery(expandedQuery)
  126. if uid_field:
  127. df.setUid(uid_field)
  128. if geometry_type == Qgis.WkbType.NoGeometry:
  129. df.setGeometryWkbType(Qgis.WkbType.NoGeometry)
  130. else:
  131. if geometry_field:
  132. df.setGeometryField(geometry_field)
  133. if geometry_type is not None:
  134. df.setGeometryWkbType(geometry_type)
  135. if geometry_crs.isValid():
  136. df.setGeometrySrid(geometry_crs.postgisSrid())
  137. vLayer = QgsVectorLayer(df.toString(), "temp_vlayer", "virtual")
  138. if not vLayer.isValid():
  139. raise QgsProcessingException(vLayer.dataProvider().error().message())
  140. if vLayer.wkbType() == QgsWkbTypes.Unknown:
  141. raise QgsProcessingException(self.tr("Cannot find geometry field"))
  142. (sink, dest_id) = self.parameterAsSink(parameters,
  143. self.OUTPUT,
  144. context,
  145. vLayer.fields(),
  146. vLayer.wkbType(),
  147. vLayer.crs())
  148. if sink is None:
  149. raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))
  150. features = vLayer.getFeatures()
  151. total = 100.0 / vLayer.featureCount() if vLayer.featureCount() else 0
  152. for current, inFeat in enumerate(features):
  153. if feedback.isCanceled():
  154. break
  155. sink.addFeature(inFeat, QgsFeatureSink.FastInsert)
  156. feedback.setProgress(int(current * total))
  157. return {self.OUTPUT: dest_id}