dlg_sql_window.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719
  1. """
  2. /***************************************************************************
  3. Name : DB Manager
  4. Description : Database manager plugin for QGIS
  5. Date : May 23, 2011
  6. copyright : (C) 2011 by Giuseppe Sucameli
  7. email : brush.tyler@gmail.com
  8. The content of this file is based on
  9. - PG_Manager by Martin Dobias (GPLv2 license)
  10. ***************************************************************************/
  11. /***************************************************************************
  12. * *
  13. * This program is free software; you can redistribute it and/or modify *
  14. * it under the terms of the GNU General Public License as published by *
  15. * the Free Software Foundation; either version 2 of the License, or *
  16. * (at your option) any later version. *
  17. * *
  18. ***************************************************************************/
  19. """
  20. from hashlib import md5
  21. import os
  22. from qgis.PyQt.QtCore import Qt, pyqtSignal, QDir, QCoreApplication
  23. from qgis.PyQt.QtWidgets import (QDialog,
  24. QWidget,
  25. QAction,
  26. QApplication,
  27. QInputDialog,
  28. QStyledItemDelegate,
  29. QTableWidgetItem,
  30. QFileDialog,
  31. QMessageBox
  32. )
  33. from qgis.PyQt.QtGui import (QKeySequence,
  34. QCursor,
  35. QClipboard,
  36. QIcon,
  37. QStandardItemModel,
  38. QStandardItem
  39. )
  40. from qgis.PyQt.Qsci import QsciAPIs, QsciScintilla
  41. from qgis.core import (
  42. QgsProject,
  43. QgsApplication,
  44. QgsTask,
  45. QgsSettings,
  46. QgsMapLayerType
  47. )
  48. from qgis.utils import OverrideCursor
  49. from .db_plugins.plugin import BaseError
  50. from .db_plugins.postgis.plugin import PGDatabase
  51. from .dlg_db_error import DlgDbError
  52. from .dlg_query_builder import QueryBuilderDlg
  53. try:
  54. from qgis.gui import QgsCodeEditorSQL # NOQA
  55. except:
  56. from .sqledit import SqlEdit
  57. from qgis import gui
  58. gui.QgsCodeEditorSQL = SqlEdit
  59. from .ui.ui_DlgSqlWindow import Ui_DbManagerDlgSqlWindow as Ui_Dialog
  60. import re
  61. def check_comments_in_sql(raw_sql_input):
  62. lines = []
  63. for line in raw_sql_input.splitlines():
  64. if not line.strip().startswith('--'):
  65. if '--' in line:
  66. comments = re.finditer(r'--', line)
  67. comment_positions = [
  68. match.start()
  69. for match in comments
  70. ]
  71. identifiers = re.finditer(r'"(?:[^"]|"")*"', line)
  72. quotes = re.finditer(r"'(?:[^']|'')*'", line)
  73. quote_positions = []
  74. for match in identifiers:
  75. quote_positions.append((match.start(), match.end()))
  76. for match in quotes:
  77. quote_positions.append((match.start(), match.end()))
  78. unquoted_comments = comment_positions.copy()
  79. for comment in comment_positions:
  80. for quote_position in quote_positions:
  81. if comment >= quote_position[0] and comment < quote_position[1]:
  82. unquoted_comments.remove(comment)
  83. if len(unquoted_comments) > 0:
  84. lines.append(line[:unquoted_comments[0]])
  85. else:
  86. lines.append(line)
  87. else:
  88. lines.append(line)
  89. sql = ' '.join(lines)
  90. return sql.strip()
  91. class DlgSqlWindow(QWidget, Ui_Dialog):
  92. nameChanged = pyqtSignal(str)
  93. QUERY_HISTORY_LIMIT = 20
  94. hasChanged = False
  95. def __init__(self, iface, db, parent=None):
  96. QWidget.__init__(self, parent)
  97. self.mainWindow = parent
  98. self.iface = iface
  99. self.db = db
  100. self.dbType = db.connection().typeNameString()
  101. self.connectionName = db.connection().connectionName()
  102. self.filter = ""
  103. self.modelAsync = None
  104. self.allowMultiColumnPk = isinstance(db,
  105. PGDatabase) # at the moment only PostgreSQL allows a primary key to span multiple columns, SpatiaLite doesn't
  106. self.aliasSubQuery = isinstance(db, PGDatabase) # only PostgreSQL requires subqueries to be aliases
  107. self.setupUi(self)
  108. self.setWindowTitle(
  109. self.tr("{0} - {1} [{2}]").format(self.windowTitle(), self.connectionName, self.dbType))
  110. self.defaultLayerName = self.tr('QueryLayer')
  111. if self.allowMultiColumnPk:
  112. self.uniqueColumnCheck.setText(self.tr("Column(s) with unique values"))
  113. else:
  114. self.uniqueColumnCheck.setText(self.tr("Column with unique values"))
  115. self.editSql.setFocus()
  116. self.editSql.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
  117. self.editSql.setLineNumbersVisible(True)
  118. self.initCompleter()
  119. self.editSql.textChanged.connect(lambda: self.setHasChanged(True))
  120. settings = QgsSettings()
  121. self.history = settings.value('DB_Manager/queryHistory/' + self.dbType, {self.connectionName: []})
  122. if self.connectionName not in self.history:
  123. self.history[self.connectionName] = []
  124. self.queryHistoryWidget.setVisible(False)
  125. self.queryHistoryTableWidget.verticalHeader().hide()
  126. self.queryHistoryTableWidget.doubleClicked.connect(self.insertQueryInEditor)
  127. self.populateQueryHistory()
  128. self.btnQueryHistory.toggled.connect(self.showHideQueryHistory)
  129. self.btnCancel.setEnabled(False)
  130. self.btnCancel.clicked.connect(self.executeSqlCanceled)
  131. self.btnCancel.setShortcut(QKeySequence.Cancel)
  132. self.progressBar.setEnabled(False)
  133. self.progressBar.setRange(0, 100)
  134. self.progressBar.setValue(0)
  135. self.progressBar.setFormat("")
  136. self.progressBar.setAlignment(Qt.AlignCenter)
  137. # allow copying results
  138. copyAction = QAction("copy", self)
  139. self.viewResult.addAction(copyAction)
  140. copyAction.setShortcuts(QKeySequence.Copy)
  141. copyAction.triggered.connect(self.copySelectedResults)
  142. self.btnExecute.clicked.connect(self.executeSql)
  143. self.btnSetFilter.clicked.connect(self.setFilter)
  144. self.btnClear.clicked.connect(self.clearSql)
  145. self.presetStore.clicked.connect(self.storePreset)
  146. self.presetSaveAsFile.clicked.connect(self.saveAsFilePreset)
  147. self.presetLoadFile.clicked.connect(self.loadFilePreset)
  148. self.presetDelete.clicked.connect(self.deletePreset)
  149. self.presetCombo.activated[str].connect(self.loadPreset)
  150. self.presetCombo.activated[str].connect(self.presetName.setText)
  151. self.updatePresetsCombobox()
  152. self.geomCombo.setEditable(True)
  153. self.geomCombo.lineEdit().setReadOnly(True)
  154. self.uniqueCombo.setEditable(True)
  155. self.uniqueCombo.lineEdit().setReadOnly(True)
  156. self.uniqueModel = QStandardItemModel(self.uniqueCombo)
  157. self.uniqueCombo.setModel(self.uniqueModel)
  158. if self.allowMultiColumnPk:
  159. self.uniqueCombo.setItemDelegate(QStyledItemDelegate())
  160. self.uniqueModel.itemChanged.connect(self.uniqueChanged) # react to the (un)checking of an item
  161. self.uniqueCombo.lineEdit().textChanged.connect(
  162. self.uniqueTextChanged) # there are other events that change the displayed text and some of them can not be caught directly
  163. # hide the load query as layer if feature is not supported
  164. self._loadAsLayerAvailable = self.db.connector.hasCustomQuerySupport()
  165. self.loadAsLayerGroup.setVisible(self._loadAsLayerAvailable)
  166. if self._loadAsLayerAvailable:
  167. self.layerTypeWidget.hide() # show if load as raster is supported
  168. self.loadLayerBtn.clicked.connect(self.loadSqlLayer)
  169. self.getColumnsBtn.clicked.connect(self.fillColumnCombos)
  170. self.loadAsLayerGroup.toggled.connect(self.loadAsLayerToggled)
  171. self.loadAsLayerToggled(False)
  172. self._createViewAvailable = self.db.connector.hasCreateSpatialViewSupport()
  173. self.btnCreateView.setVisible(self._createViewAvailable)
  174. if self._createViewAvailable:
  175. self.btnCreateView.clicked.connect(self.createView)
  176. self.queryBuilderFirst = True
  177. self.queryBuilderBtn.setIcon(QIcon(":/db_manager/icons/sql.gif"))
  178. self.queryBuilderBtn.clicked.connect(self.displayQueryBuilder)
  179. self.presetName.textChanged.connect(self.nameChanged)
  180. def insertQueryInEditor(self, item):
  181. sql = item.data(Qt.DisplayRole)
  182. self.editSql.insertText(sql)
  183. def showHideQueryHistory(self, visible):
  184. self.queryHistoryWidget.setVisible(visible)
  185. def populateQueryHistory(self):
  186. self.queryHistoryTableWidget.clearContents()
  187. self.queryHistoryTableWidget.setRowCount(0)
  188. dictlist = self.history[self.connectionName]
  189. if not dictlist:
  190. return
  191. for i in range(len(dictlist)):
  192. self.queryHistoryTableWidget.insertRow(0)
  193. queryItem = QTableWidgetItem(dictlist[i]['query'])
  194. rowsItem = QTableWidgetItem(str(dictlist[i]['rows']))
  195. durationItem = QTableWidgetItem(str(dictlist[i]['secs']))
  196. self.queryHistoryTableWidget.setItem(0, 0, queryItem)
  197. self.queryHistoryTableWidget.setItem(0, 1, rowsItem)
  198. self.queryHistoryTableWidget.setItem(0, 2, durationItem)
  199. self.queryHistoryTableWidget.resizeColumnsToContents()
  200. self.queryHistoryTableWidget.resizeRowsToContents()
  201. def writeQueryHistory(self, sql, affectedRows, secs):
  202. if len(self.history[self.connectionName]) >= self.QUERY_HISTORY_LIMIT:
  203. self.history[self.connectionName].pop(0)
  204. settings = QgsSettings()
  205. self.history[self.connectionName].append({'query': sql,
  206. 'rows': affectedRows,
  207. 'secs': secs})
  208. settings.setValue('DB_Manager/queryHistory/' + self.dbType, self.history)
  209. self.populateQueryHistory()
  210. def getQueryHash(self, name):
  211. return 'q%s' % md5(name.encode('utf8')).hexdigest()
  212. def updatePresetsCombobox(self):
  213. self.presetCombo.clear()
  214. names = []
  215. entries = QgsProject.instance().subkeyList('DBManager', 'savedQueries')
  216. for entry in entries:
  217. name = QgsProject.instance().readEntry('DBManager', 'savedQueries/' + entry + '/name')[0]
  218. names.append(name)
  219. for name in sorted(names):
  220. self.presetCombo.addItem(name)
  221. self.presetCombo.setCurrentIndex(-1)
  222. def storePreset(self):
  223. query = self._getSqlQuery()
  224. if query == "":
  225. return
  226. name = str(self.presetName.text())
  227. QgsProject.instance().writeEntry('DBManager', 'savedQueries/' + self.getQueryHash(name) + '/name', name)
  228. QgsProject.instance().writeEntry('DBManager', 'savedQueries/' + self.getQueryHash(name) + '/query', query)
  229. index = self.presetCombo.findText(name)
  230. if index == -1:
  231. self.presetCombo.addItem(name)
  232. self.presetCombo.setCurrentIndex(self.presetCombo.count() - 1)
  233. else:
  234. self.presetCombo.setCurrentIndex(index)
  235. def saveAsFilePreset(self):
  236. settings = QgsSettings()
  237. lastDir = settings.value('DB_Manager/lastDirSQLFIle', "")
  238. query = self.editSql.text()
  239. if query == "":
  240. return
  241. filename, _ = QFileDialog.getSaveFileName(
  242. self,
  243. self.tr('Save SQL Query'),
  244. lastDir,
  245. self.tr("SQL File (*.sql *.SQL)"))
  246. if filename:
  247. if not filename.lower().endswith('.sql'):
  248. filename += ".sql"
  249. with open(filename, 'w') as f:
  250. f.write(query)
  251. lastDir = os.path.dirname(filename)
  252. settings.setValue('DB_Manager/lastDirSQLFile', lastDir)
  253. def loadFilePreset(self):
  254. settings = QgsSettings()
  255. lastDir = settings.value('DB_Manager/lastDirSQLFIle', "")
  256. filename, _ = QFileDialog.getOpenFileName(
  257. self,
  258. self.tr("Load SQL Query"),
  259. lastDir,
  260. self.tr("SQL File (*.sql *.SQL);;All Files (*)"))
  261. if filename:
  262. with open(filename) as f:
  263. self.editSql.clear()
  264. for line in f:
  265. self.editSql.insertText(line)
  266. lastDir = os.path.dirname(filename)
  267. settings.setValue('DB_Manager/lastDirSQLFile', lastDir)
  268. def deletePreset(self):
  269. name = self.presetCombo.currentText()
  270. QgsProject.instance().removeEntry('DBManager', 'savedQueries/' + self.getQueryHash(name))
  271. self.presetCombo.removeItem(self.presetCombo.findText(name))
  272. self.presetCombo.setCurrentIndex(-1)
  273. def loadPreset(self, name):
  274. query = QgsProject.instance().readEntry('DBManager', 'savedQueries/' + self.getQueryHash(name) + '/query')[0]
  275. self.editSql.setText(query)
  276. def loadAsLayerToggled(self, checked):
  277. self.loadAsLayerGroup.setChecked(checked)
  278. self.loadAsLayerWidget.setVisible(checked)
  279. if checked:
  280. self.fillColumnCombos()
  281. def clearSql(self):
  282. self.editSql.clear()
  283. self.editSql.setFocus()
  284. self.filter = ""
  285. self.setHasChanged(True)
  286. def updateUiWhileSqlExecution(self, status):
  287. if status:
  288. for i in range(0, self.mainWindow.tabs.count()):
  289. if i != self.mainWindow.tabs.currentIndex():
  290. self.mainWindow.tabs.setTabEnabled(i, False)
  291. self.mainWindow.menuBar.setEnabled(False)
  292. self.mainWindow.toolBar.setEnabled(False)
  293. self.mainWindow.tree.setEnabled(False)
  294. for w in self.findChildren(QWidget):
  295. w.setEnabled(False)
  296. self.btnCancel.setEnabled(True)
  297. self.progressBar.setEnabled(True)
  298. self.progressBar.setRange(0, 0)
  299. else:
  300. for i in range(0, self.mainWindow.tabs.count()):
  301. if i != self.mainWindow.tabs.currentIndex():
  302. self.mainWindow.tabs.setTabEnabled(i, True)
  303. self.mainWindow.refreshTabs()
  304. self.mainWindow.menuBar.setEnabled(True)
  305. self.mainWindow.toolBar.setEnabled(True)
  306. self.mainWindow.tree.setEnabled(True)
  307. for w in self.findChildren(QWidget):
  308. w.setEnabled(True)
  309. self.btnCancel.setEnabled(False)
  310. self.progressBar.setRange(0, 100)
  311. self.progressBar.setEnabled(False)
  312. def executeSqlCanceled(self):
  313. self.btnCancel.setEnabled(False)
  314. self.btnCancel.setText(QCoreApplication.translate("DlgSqlWindow", "Canceling…"))
  315. self.modelAsync.cancel()
  316. def executeSqlCompleted(self):
  317. self.updateUiWhileSqlExecution(False)
  318. with OverrideCursor(Qt.WaitCursor):
  319. if self.modelAsync.task.status() == QgsTask.Complete:
  320. model = self.modelAsync.model
  321. self.showError(None)
  322. self.viewResult.setModel(model)
  323. self.lblResult.setText(self.tr("{0} rows, {1:.3f} seconds").format(model.affectedRows(), model.secs()))
  324. cols = self.viewResult.model().columnNames()
  325. quotedCols = [
  326. self.db.connector.quoteId(col)
  327. for col in cols
  328. ]
  329. self.setColumnCombos(cols, quotedCols)
  330. self.writeQueryHistory(self.modelAsync.task.sql, model.affectedRows(), model.secs())
  331. self.update()
  332. elif not self.modelAsync.canceled:
  333. self.showError(self.modelAsync.error)
  334. self.uniqueModel.clear()
  335. self.geomCombo.clear()
  336. self.btnCancel.setText(self.tr("Cancel"))
  337. def executeSql(self):
  338. sql = self._getExecutableSqlQuery()
  339. if sql == "":
  340. return
  341. # delete the old model
  342. old_model = self.viewResult.model()
  343. self.viewResult.setModel(None)
  344. if old_model:
  345. old_model.deleteLater()
  346. try:
  347. self.modelAsync = self.db.sqlResultModelAsync(sql, self)
  348. self.modelAsync.done.connect(self.executeSqlCompleted)
  349. self.updateUiWhileSqlExecution(True)
  350. QgsApplication.taskManager().addTask(self.modelAsync.task)
  351. except Exception as e:
  352. self.showError(e)
  353. self.uniqueModel.clear()
  354. self.geomCombo.clear()
  355. return
  356. def showError(self, error):
  357. '''Shows the error or hides it if error is None'''
  358. if error:
  359. self.viewResult.setVisible(False)
  360. self.errorText.setVisible(True)
  361. self.errorText.setText(error.msg)
  362. self.errorText.setWrapMode(QsciScintilla.WrapWord)
  363. else:
  364. self.viewResult.setVisible(True)
  365. self.errorText.setVisible(False)
  366. def _getSqlLayer(self, _filter):
  367. hasUniqueField = self.uniqueColumnCheck.checkState() == Qt.Checked
  368. if hasUniqueField and self.allowMultiColumnPk:
  369. uniqueFieldName = ",".join(
  370. item.data()
  371. for item in self.uniqueModel.findItems("*", Qt.MatchWildcard)
  372. if item.checkState() == Qt.Checked
  373. )
  374. elif (
  375. hasUniqueField
  376. and not self.allowMultiColumnPk
  377. and self.uniqueCombo.currentIndex() >= 0
  378. ):
  379. uniqueFieldName = self.uniqueModel.item(self.uniqueCombo.currentIndex()).data()
  380. else:
  381. uniqueFieldName = None
  382. hasGeomCol = self.hasGeometryCol.checkState() == Qt.Checked
  383. if hasGeomCol:
  384. geomFieldName = self.geomCombo.currentText()
  385. else:
  386. geomFieldName = None
  387. query = self._getExecutableSqlQuery()
  388. if query == "":
  389. return None
  390. # remove a trailing ';' from query if present
  391. if query.strip().endswith(';'):
  392. query = query.strip()[:-1]
  393. layerType = QgsMapLayerType.VectorLayer if self.vectorRadio.isChecked() else QgsMapLayerType.RasterLayer
  394. # get a new layer name
  395. names = []
  396. for layer in list(QgsProject.instance().mapLayers().values()):
  397. names.append(layer.name())
  398. layerName = self.layerNameEdit.text()
  399. if layerName == "":
  400. layerName = self.defaultLayerName
  401. newLayerName = layerName
  402. index = 1
  403. while newLayerName in names:
  404. index += 1
  405. newLayerName = "%s_%d" % (layerName, index)
  406. # create the layer
  407. layer = self.db.toSqlLayer(query, geomFieldName, uniqueFieldName, newLayerName, layerType,
  408. self.avoidSelectById.isChecked(), _filter)
  409. if layer.isValid():
  410. return layer
  411. else:
  412. e = BaseError(self.tr("There was an error creating the SQL layer, please check the logs for further information."))
  413. DlgDbError.showError(e, self)
  414. return None
  415. def loadSqlLayer(self):
  416. with OverrideCursor(Qt.WaitCursor):
  417. layer = self._getSqlLayer(self.filter)
  418. if layer is None:
  419. return
  420. QgsProject.instance().addMapLayers([layer], True)
  421. def fillColumnCombos(self):
  422. query = self._getExecutableSqlQuery()
  423. if query == "":
  424. return
  425. with OverrideCursor(Qt.WaitCursor):
  426. # remove a trailing ';' from query if present
  427. if query.strip().endswith(';'):
  428. query = query.strip()[:-1]
  429. # get all the columns
  430. quotedCols = []
  431. connector = self.db.connector
  432. if self.aliasSubQuery:
  433. # get a new alias
  434. aliasIndex = 0
  435. while True:
  436. alias = "_subQuery__%d" % aliasIndex
  437. escaped = re.compile('\\b("?)' + re.escape(alias) + '\\1\\b')
  438. if not escaped.search(query):
  439. break
  440. aliasIndex += 1
  441. sql = "SELECT * FROM (%s\n) AS %s LIMIT 0" % (str(query), connector.quoteId(alias))
  442. else:
  443. sql = "SELECT * FROM (%s\n) WHERE 1=0" % str(query)
  444. c = None
  445. try:
  446. c = connector._execute(None, sql)
  447. cols = connector._get_cursor_columns(c)
  448. for col in cols:
  449. quotedCols.append(connector.quoteId(col))
  450. except BaseError as e:
  451. DlgDbError.showError(e, self)
  452. self.uniqueModel.clear()
  453. self.geomCombo.clear()
  454. return
  455. finally:
  456. if c:
  457. c.close()
  458. del c
  459. self.setColumnCombos(cols, quotedCols)
  460. def setColumnCombos(self, cols, quotedCols):
  461. # get sensible default columns. do this before sorting in case there's hints in the column order (e.g., id is more likely to be first)
  462. try:
  463. defaultGeomCol = next(col for col in cols if col in ['geom', 'geometry', 'the_geom', 'way'])
  464. except:
  465. defaultGeomCol = None
  466. try:
  467. defaultUniqueCol = [col for col in cols if 'id' in col][0]
  468. except:
  469. defaultUniqueCol = None
  470. colNames = sorted(zip(cols, quotedCols))
  471. newItems = []
  472. uniqueIsFilled = False
  473. for (col, quotedCol) in colNames:
  474. item = QStandardItem(col)
  475. item.setData(quotedCol)
  476. item.setEnabled(True)
  477. item.setCheckable(self.allowMultiColumnPk)
  478. item.setSelectable(not self.allowMultiColumnPk)
  479. if self.allowMultiColumnPk:
  480. matchingItems = self.uniqueModel.findItems(col)
  481. if matchingItems:
  482. item.setCheckState(matchingItems[0].checkState())
  483. uniqueIsFilled = uniqueIsFilled or matchingItems[0].checkState() == Qt.Checked
  484. else:
  485. item.setCheckState(Qt.Unchecked)
  486. newItems.append(item)
  487. if self.allowMultiColumnPk:
  488. self.uniqueModel.clear()
  489. self.uniqueModel.appendColumn(newItems)
  490. self.uniqueChanged()
  491. else:
  492. previousUniqueColumn = self.uniqueCombo.currentText()
  493. self.uniqueModel.clear()
  494. self.uniqueModel.appendColumn(newItems)
  495. if self.uniqueModel.findItems(previousUniqueColumn):
  496. self.uniqueCombo.setEditText(previousUniqueColumn)
  497. uniqueIsFilled = True
  498. oldGeometryColumn = self.geomCombo.currentText()
  499. self.geomCombo.clear()
  500. self.geomCombo.addItems(cols)
  501. self.geomCombo.setCurrentIndex(self.geomCombo.findText(oldGeometryColumn, Qt.MatchExactly))
  502. # set sensible default columns if the columns are not already set
  503. try:
  504. if self.geomCombo.currentIndex() == -1:
  505. self.geomCombo.setCurrentIndex(cols.index(defaultGeomCol))
  506. except:
  507. pass
  508. items = self.uniqueModel.findItems(defaultUniqueCol)
  509. if items and not uniqueIsFilled:
  510. if self.allowMultiColumnPk:
  511. items[0].setCheckState(Qt.Checked)
  512. else:
  513. self.uniqueCombo.setEditText(defaultUniqueCol)
  514. def copySelectedResults(self):
  515. if len(self.viewResult.selectedIndexes()) <= 0:
  516. return
  517. model = self.viewResult.model()
  518. # convert to string using tab as separator
  519. text = model.headerToString("\t")
  520. for idx in self.viewResult.selectionModel().selectedRows():
  521. text += "\n" + model.rowToString(idx.row(), "\t")
  522. QApplication.clipboard().setText(text, QClipboard.Selection)
  523. QApplication.clipboard().setText(text, QClipboard.Clipboard)
  524. def initCompleter(self):
  525. dictionary = None
  526. if self.db:
  527. dictionary = self.db.connector.getSqlDictionary()
  528. if not dictionary:
  529. # use the generic sql dictionary
  530. from .sql_dictionary import getSqlDictionary
  531. dictionary = getSqlDictionary()
  532. wordlist = []
  533. for value in dictionary.values():
  534. wordlist += value # concat lists
  535. wordlist = list(set(wordlist)) # remove duplicates
  536. api = QsciAPIs(self.editSql.lexer())
  537. for word in wordlist:
  538. api.add(word)
  539. api.prepare()
  540. self.editSql.lexer().setAPIs(api)
  541. def displayQueryBuilder(self):
  542. dlg = QueryBuilderDlg(self.iface, self.db, self, reset=self.queryBuilderFirst)
  543. self.queryBuilderFirst = False
  544. r = dlg.exec_()
  545. if r == QDialog.Accepted:
  546. self.editSql.setText(dlg.query)
  547. def createView(self):
  548. name, ok = QInputDialog.getText(None, self.tr("View Name"), self.tr("View name"))
  549. if ok:
  550. try:
  551. self.db.connector.createSpatialView(name, self._getExecutableSqlQuery())
  552. except BaseError as e:
  553. DlgDbError.showError(e, self)
  554. def _getSqlQuery(self):
  555. sql = self.editSql.selectedText()
  556. if len(sql) == 0:
  557. sql = self.editSql.text()
  558. return sql
  559. def _getExecutableSqlQuery(self):
  560. sql = self._getSqlQuery().strip()
  561. uncommented_sql = check_comments_in_sql(sql)
  562. uncommented_sql = uncommented_sql.rstrip(';')
  563. return uncommented_sql
  564. def uniqueChanged(self):
  565. # when an item is (un)checked, simply trigger an update of the combobox text
  566. self.uniqueTextChanged(None)
  567. def uniqueTextChanged(self, text):
  568. # Whenever there is new text displayed in the combobox, check if it is the correct one and if not, display the correct one.
  569. label = ", ".join(
  570. item.text()
  571. for item in self.uniqueModel.findItems("*", Qt.MatchWildcard)
  572. if item.checkState() == Qt.Checked
  573. )
  574. if text != label:
  575. self.uniqueCombo.setEditText(label)
  576. def setFilter(self):
  577. from qgis.gui import QgsQueryBuilder
  578. layer = self._getSqlLayer("")
  579. if not layer:
  580. return
  581. dlg = QgsQueryBuilder(layer)
  582. dlg.setSql(self.filter)
  583. if dlg.exec_():
  584. self.filter = dlg.sql()
  585. layer.deleteLater()
  586. def setHasChanged(self, hasChanged):
  587. self.hasChanged = hasChanged
  588. def close(self):
  589. if self.hasChanged:
  590. ret = QMessageBox.question(
  591. self, self.tr('Unsaved Changes?'),
  592. self.tr('There are unsaved changes. Do you want to keep them?'),
  593. QMessageBox.Save | QMessageBox.Cancel | QMessageBox.Discard, QMessageBox.Cancel)
  594. if ret == QMessageBox.Save:
  595. self.saveAsFilePreset()
  596. return True
  597. elif ret == QMessageBox.Discard:
  598. return True
  599. else:
  600. return False
  601. else:
  602. return True