dlg_query_builder.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. """
  2. /***************************************************************************
  3. Name : DB Manager
  4. Description : Database manager plugin for QGIS
  5. Date : March 2015
  6. copyright : (C) 2015 Hugo Mercier / Oslandia
  7. email : hugo dot mercier at oslandia dot com
  8. ***************************************************************************/
  9. /***************************************************************************
  10. * *
  11. * This program is free software; you can redistribute it and/or modify *
  12. * it under the terms of the GNU General Public License as published by *
  13. * the Free Software Foundation; either version 2 of the License, or *
  14. * (at your option) any later version. *
  15. * *
  16. ***************************************************************************/
  17. Query builder dialog, based on the QSpatialite plugin (GPLv2+) by Romain Riviere
  18. """
  19. from qgis.PyQt.QtCore import Qt, QObject, QEvent
  20. from qgis.PyQt.QtWidgets import QDialog, QMessageBox, QTextEdit
  21. from .ui.ui_DlgQueryBuilder import Ui_DbManagerQueryBuilderDlg as Ui_Dialog
  22. from .db_plugins.plugin import VectorTable
  23. class FocusEventFilter(QObject):
  24. def __init__(self, parent):
  25. QObject.__init__(self, parent)
  26. self.focus = ''
  27. def eventFilter(self, obj, event):
  28. if event.type() == QEvent.FocusIn:
  29. self.focus = obj.objectName()
  30. return QObject.eventFilter(self, obj, event)
  31. def insertWithSelection(widget, text):
  32. if widget.textCursor().hasSelection(): # user has selectedsomething...
  33. selection = widget.textCursor().selectedText()
  34. widget.insertPlainText(text + selection + ")")
  35. else:
  36. widget.insertPlainText(text)
  37. def insertWithSelectionOn(parent, objectname, text):
  38. """Insert the text in a QTextEdit given by its objectname"""
  39. w = parent.findChild(QTextEdit, objectname)
  40. insertWithSelection(w, text)
  41. class QueryBuilderDlg(QDialog):
  42. # object used to store parameters between invocations
  43. saveParameter = None
  44. def __init__(self, iface, db, parent=None, reset=False):
  45. QDialog.__init__(self, parent)
  46. self.iface = iface
  47. self.db = db
  48. self.query = ''
  49. self.ui = Ui_Dialog()
  50. self.ui.setupUi(self)
  51. self.ui.group.setMaximumHeight(self.ui.tab.sizeHint().height())
  52. self.ui.order.setMaximumHeight(self.ui.tab.sizeHint().height())
  53. self.evt = FocusEventFilter(self)
  54. self.ui.col.installEventFilter(self.evt)
  55. self.ui.where.installEventFilter(self.evt)
  56. self.ui.group.installEventFilter(self.evt)
  57. self.ui.order.installEventFilter(self.evt)
  58. d = self.db.connector.getQueryBuilderDictionary()
  59. # Application default parameters
  60. self.table = None
  61. self.col_col = []
  62. self.col_where = []
  63. self.coltables = []
  64. self.ui.extract.setChecked(True)
  65. # ComboBox default values
  66. self.ui.functions.insertItems(1, d['function'])
  67. self.ui.math.insertItems(1, d['math'])
  68. self.ui.aggregates.insertItems(1, d['aggregate'])
  69. self.ui.operators.insertItems(1, d['operator'])
  70. self.ui.stringfct.insertItems(1, d['string'])
  71. # self.ui.Rtree.insertItems(1,rtreecommand)
  72. # restore last query if needed
  73. if reset:
  74. QueryBuilderDlg.saveParameter = None
  75. if QueryBuilderDlg.saveParameter is not None:
  76. self.restoreLastQuery()
  77. # Show Tables
  78. self.show_tables()
  79. # Signal/slot
  80. self.ui.aggregates.currentIndexChanged.connect(self.add_aggregate)
  81. self.ui.stringfct.currentIndexChanged.connect(self.add_stringfct)
  82. self.ui.operators.currentIndexChanged.connect(self.add_operators)
  83. self.ui.functions.currentIndexChanged.connect(self.add_functions)
  84. self.ui.math.currentIndexChanged.connect(self.add_math)
  85. self.ui.tables.currentIndexChanged.connect(self.add_tables)
  86. self.ui.tables.currentIndexChanged.connect(self.list_cols)
  87. self.ui.columns.currentIndexChanged.connect(self.add_columns)
  88. self.ui.columns_2.currentIndexChanged.connect(self.list_values)
  89. self.ui.reset.clicked.connect(self.reset)
  90. self.ui.extract.stateChanged.connect(self.list_values)
  91. self.ui.values.doubleClicked.connect(self.query_item)
  92. self.ui.buttonBox.accepted.connect(self.validate)
  93. self.ui.checkBox.stateChanged.connect(self.show_tables)
  94. if self.db.explicitSpatialIndex():
  95. self.tablesGeo = [table for table in self.tables if isinstance(table, VectorTable)]
  96. tablesGeo = ['"%s"."%s"' % (table.name, table.geomColumn) for table in self.tablesGeo]
  97. self.ui.table_target.insertItems(1, tablesGeo)
  98. self.idxTables = [table for table in self.tablesGeo if table.hasSpatialIndex()]
  99. idxTables = ['"%s"."%s"' % (table.name, table.geomColumn) for table in self.idxTables]
  100. self.ui.table_idx.insertItems(1, idxTables)
  101. self.ui.usertree.clicked.connect(self.use_rtree)
  102. else:
  103. self.ui.toolBox.setItemEnabled(2, False)
  104. def update_table_list(self):
  105. self.tables = []
  106. add_sys_tables = self.ui.checkBox.isChecked()
  107. schemas = self.db.schemas()
  108. if schemas is None:
  109. self.tables = self.db.tables(None, add_sys_tables)
  110. else:
  111. for schema in schemas:
  112. self.tables += self.db.tables(schema, add_sys_tables)
  113. def show_tables(self):
  114. self.update_table_list()
  115. self.ui.tables.clear()
  116. self.ui.tables.insertItems(0, ["Tables"])
  117. self.ui.tables.insertItems(1, [t.name for t in self.tables])
  118. def add_aggregate(self):
  119. if self.ui.aggregates.currentIndex() <= 0:
  120. return
  121. ag = self.ui.aggregates.currentText()
  122. insertWithSelection(self.ui.col, ag)
  123. self.ui.aggregates.setCurrentIndex(0)
  124. def add_functions(self):
  125. if self.ui.functions.currentIndex() <= 0:
  126. return
  127. ag = self.ui.functions.currentText()
  128. insertWithSelectionOn(self, self.evt.focus, ag)
  129. self.ui.functions.setCurrentIndex(0)
  130. def add_stringfct(self):
  131. if self.ui.stringfct.currentIndex() <= 0:
  132. return
  133. ag = self.ui.stringfct.currentText()
  134. insertWithSelectionOn(self, self.evt.focus, ag)
  135. self.ui.stringfct.setCurrentIndex(0)
  136. def add_math(self):
  137. if self.ui.math.currentIndex() <= 0:
  138. return
  139. ag = self.ui.math.currentText()
  140. insertWithSelectionOn(self, self.evt.focus, ag)
  141. self.ui.math.setCurrentIndex(0)
  142. def add_operators(self):
  143. if self.ui.operators.currentIndex() <= 0:
  144. return
  145. ag = self.ui.operators.currentText()
  146. if self.evt.focus == "where": # in where section
  147. self.ui.where.insertPlainText(ag)
  148. else:
  149. self.ui.col.insertPlainText(ag)
  150. self.ui.operators.setCurrentIndex(0)
  151. def add_tables(self):
  152. if self.ui.tables.currentIndex() <= 0:
  153. return
  154. ag = self.ui.tables.currentText()
  155. # Retrieve Table Object from txt
  156. tableObj = [table for table in self.tables if table.name.upper() == ag.upper()]
  157. if len(tableObj) != 1:
  158. return # No object with this name
  159. self.table = tableObj[0]
  160. if (ag in self.coltables): # table already use
  161. response = QMessageBox.question(self, "Table already used", "Do you want to add table %s again?" % ag, QMessageBox.Yes | QMessageBox.No)
  162. if response == QMessageBox.No:
  163. return
  164. ag = self.table.quotedName()
  165. txt = self.ui.tab.text()
  166. if (txt is None) or (txt in ("", " ")):
  167. self.ui.tab.setText('%s' % ag)
  168. else:
  169. self.ui.tab.setText('%s, %s' % (txt, ag))
  170. self.ui.tables.setCurrentIndex(0)
  171. def add_columns(self):
  172. if self.ui.columns.currentIndex() <= 0:
  173. return
  174. ag = self.ui.columns.currentText()
  175. if self.evt.focus == "where": # in where section
  176. if ag in self.col_where: # column already called in where section
  177. response = QMessageBox.question(self, "Column already used in WHERE clause", "Do you want to add column %s again?" % ag, QMessageBox.Yes | QMessageBox.No)
  178. if response == QMessageBox.No:
  179. self.ui.columns.setCurrentIndex(0)
  180. return
  181. self.ui.where.insertPlainText(ag)
  182. self.col_where.append(ag)
  183. elif self.evt.focus == "col":
  184. if ag in self.col_col: # column already called in col section
  185. response = QMessageBox.question(self, "Column already used in COLUMNS section", "Do you want to add column %s again?" % ag, QMessageBox.Yes | QMessageBox.No)
  186. if response == QMessageBox.No:
  187. self.ui.columns.setCurrentIndex(0)
  188. return
  189. if len(self.ui.col.toPlainText().strip()) > 0:
  190. self.ui.col.insertPlainText(",\n" + ag)
  191. else:
  192. self.ui.col.insertPlainText(ag)
  193. self.col_col.append(ag)
  194. elif self.evt.focus == "group":
  195. if len(self.ui.group.toPlainText().strip()) > 0:
  196. self.ui.group.insertPlainText(", " + ag)
  197. else:
  198. self.ui.group.insertPlainText(ag)
  199. elif self.evt.focus == "order":
  200. if len(self.ui.order.toPlainText().strip()) > 0:
  201. self.ui.order.insertPlainText(", " + ag)
  202. else:
  203. self.ui.order.insertPlainText(ag)
  204. self.ui.columns.setCurrentIndex(0)
  205. def list_cols(self):
  206. table = self.table
  207. if (table is None):
  208. return
  209. if (table.name in self.coltables):
  210. return
  211. columns = ['"%s"."%s"' % (table.name, col.name) for col in table.fields()]
  212. # add special '*' column:
  213. columns = ['"%s".*' % table.name] + columns
  214. self.coltables.append(table.name) # table columns have been listed
  215. # first and second col combobox
  216. end = self.ui.columns.count()
  217. self.ui.columns.insertItems(end, columns)
  218. self.ui.columns_2.insertItems(end, columns)
  219. end = self.ui.columns.count()
  220. self.ui.columns.insertSeparator(end)
  221. self.ui.columns_2.insertSeparator(end)
  222. def list_values(self):
  223. if self.ui.columns_2.currentIndex() <= 0:
  224. return
  225. item = self.ui.columns_2.currentText()
  226. # recover column and table:
  227. column = item.split(".") # "table".'column'
  228. table = column[0]
  229. if column[1] == '*':
  230. return
  231. table = table[1:-1]
  232. qtable = [t for t in self.tables if t.name.lower() == table.lower()][0].quotedName()
  233. if self.ui.extract.isChecked():
  234. limit = 10
  235. else:
  236. limit = None
  237. model = self.db.columnUniqueValuesModel(item, qtable, limit)
  238. self.ui.values.setModel(model)
  239. def query_item(self, index):
  240. value = index.data(Qt.EditRole)
  241. if value is None:
  242. queryWord = 'NULL'
  243. elif isinstance(value, (int, float)):
  244. queryWord = str(value)
  245. else:
  246. queryWord = self.db.connector.quoteString(value)
  247. if queryWord.strip() != '':
  248. self.ui.where.insertPlainText(' ' + queryWord)
  249. self.ui.where.setFocus()
  250. def use_rtree(self):
  251. idx = self.ui.table_idx.currentText()
  252. if idx in (None, "", " ", "Table (with Spatial Index)"):
  253. return
  254. try:
  255. tab_idx = idx.split(".")[0][1:-1] # remove "
  256. col_idx = idx.split(".")[1][1:-1] # remove '
  257. except:
  258. QMessageBox.warning(self, "Use R-Tree", "All fields are necessary", QMessageBox.Cancel)
  259. tgt = self.ui.table_target.currentText()
  260. if tgt in (None, "", " ", "Table (Target)"):
  261. return
  262. tgt_tab = tgt.split('.')[0][1:-1]
  263. tgt_col = tgt.split('.')[1][1:-1]
  264. sql = ""
  265. if self.ui.where.toPlainText() not in (None, "", " "):
  266. sql += "\nAND"
  267. sql += self.db.spatialIndexClause(tab_idx, col_idx, tgt_tab, tgt_col)
  268. self.ui.where.insertPlainText(sql)
  269. def reset(self):
  270. # reset lists:
  271. self.ui.values.setModel(None)
  272. self.ui.columns_2.clear()
  273. self.ui.columns.insertItems(0, ["Columns"])
  274. self.ui.columns_2.insertItems(0, ["Columns"])
  275. self.coltables = []
  276. self.col_col = []
  277. self.col_where = []
  278. def validate(self):
  279. query_col = str(self.ui.col.toPlainText())
  280. query_table = str(self.ui.tab.text())
  281. query_where = str(self.ui.where.toPlainText())
  282. query_group = str(self.ui.group.toPlainText())
  283. query_order = str(self.ui.order.toPlainText())
  284. query = ""
  285. if query_col.strip() != '':
  286. query += "SELECT %s \nFROM %s" % (query_col, query_table)
  287. if query_where.strip() != '':
  288. query += "\nWHERE %s" % query_where
  289. if query_group.strip() != '':
  290. query += "\nGROUP BY %s" % query_group
  291. if query_order.strip() != '':
  292. query += "\nORDER BY %s" % query_order
  293. if query == '':
  294. return
  295. self.query = query
  296. saveParameter = {
  297. "coltables": self.coltables,
  298. "col_col": self.col_col,
  299. "col_where": self.col_where,
  300. "col": query_col,
  301. "tab": query_table,
  302. "where": query_where,
  303. "group": query_group,
  304. "order": query_order,
  305. }
  306. QueryBuilderDlg.saveParameter = saveParameter
  307. def restoreLastQuery(self):
  308. self.update_table_list()
  309. saveParameter = QueryBuilderDlg.saveParameter
  310. self.coltables = saveParameter["coltables"]
  311. self.col_col = saveParameter["col_col"]
  312. self.col_where = saveParameter["col_where"]
  313. self.ui.col.insertPlainText(saveParameter["col"])
  314. self.ui.tab.setText(saveParameter["tab"])
  315. self.ui.where.insertPlainText(saveParameter["where"])
  316. self.ui.order.setPlainText(saveParameter["order"])
  317. self.ui.group.setPlainText(saveParameter["group"])
  318. # list previous colist:
  319. for tablename in self.coltables:
  320. # Retrieve table object from table name:
  321. table = [table for table in self.tables if table.name.upper() == tablename.upper()]
  322. if len(table) != 1:
  323. break
  324. table = table[0]
  325. columns = ['"%s"."%s"' % (table.name, col.name) for col in table.fields()]
  326. # first and second col combobox
  327. end = self.ui.columns.count()
  328. self.ui.columns.insertItems(end, columns)
  329. self.ui.columns_2.insertItems(end, columns)
  330. end = self.ui.columns.count()
  331. self.ui.columns.insertSeparator(end)
  332. self.ui.columns_2.insertSeparator(end)