# -*- coding: utf-8 -*- """ /*************************************************************************** Name : DB Manager Description : Database manager plugin for QGIS Date : May 23, 2011 copyright : (C) 2011 by Giuseppe Sucameli email : brush.tyler@gmail.com The content of this file is based on - PG_Manager by Martin Dobias (GPLv2 license) ***************************************************************************/ /*************************************************************************** * * * 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 * from PyQt4.QtGui import * from qgis.core import * from .db_plugins.plugin import BaseError from .dlg_db_error import DlgDbError from .ui.ui_DlgSqlWindow import Ui_DbManagerDlgSqlWindow as Ui_Dialog from .highlighter import SqlHighlighter from .completer import SqlCompleter import re class DlgSqlWindow(QDialog, Ui_Dialog): def __init__(self, iface, db, parent=None): QDialog.__init__(self, parent) self.iface = iface self.db = db self.setupUi(self) self.setWindowTitle( u"%s - %s [%s]" % (self.windowTitle(), db.connection().connectionName(), db.connection().typeNameString()) ) self.defaultLayerName = 'QueryLayer' settings = QSettings() self.restoreGeometry(settings.value("/DB_Manager/sqlWindow/geometry", QByteArray(), type=QByteArray)) self.editSql.setAcceptRichText(False) self.editSql.setFocus() SqlCompleter(self.editSql, self.db) SqlHighlighter(self.editSql, self.db) # allow to copy results copyAction = QAction("copy", self) self.viewResult.addAction( copyAction ) copyAction.setShortcuts(QKeySequence.Copy) QObject.connect(copyAction, SIGNAL("triggered()"), self.copySelectedResults) self.connect(self.btnExecute, SIGNAL("clicked()"), self.executeSql) self.connect(self.btnClear, SIGNAL("clicked()"), self.clearSql) self.connect(self.buttonBox.button(QDialogButtonBox.Close), SIGNAL("clicked()"), self.close) self.connect(self.presetStore, SIGNAL("clicked()"), self.storePreset) self.connect(self.presetDelete, SIGNAL("clicked()"), self.deletePreset) self.connect(self.presetCombo, SIGNAL("activated(QString)"), self.loadPreset) self.connect(self.presetCombo, SIGNAL("activated(QString)"), self.presetName.setText) self.updatePresetsCombobox() # hide the load query as layer if feature is not supported self._loadAsLayerAvailable = self.db.connector.hasCustomQuerySupport() self.loadAsLayerGroup.setVisible( self._loadAsLayerAvailable ) if self._loadAsLayerAvailable: self.layerTypeWidget.hide() # show if load as raster is supported self.connect(self.loadLayerBtn, SIGNAL("clicked()"), self.loadSqlLayer) self.connect(self.getColumnsBtn, SIGNAL("clicked()"), self.fillColumnCombos) self.connect(self.loadAsLayerGroup, SIGNAL("toggled(bool)"), self.loadAsLayerToggled) self.loadAsLayerToggled(False) def updatePresetsCombobox(self): self.presetCombo.clear() names = [] entries = QgsProject.instance().subkeyList('DBManager','savedQueries') for entry in entries: name = QgsProject.instance().readEntry('DBManager','savedQueries/'+entry+'/name' )[0] names.append( name ) for name in sorted(names): self.presetCombo.addItem(name) self.presetCombo.setCurrentIndex(-1) def storePreset(self): query = self.editSql.toPlainText() name = self.presetName.text() QgsProject.instance().writeEntry('DBManager','savedQueries/q'+str(name.__hash__())+'/name', name ) QgsProject.instance().writeEntry('DBManager','savedQueries/q'+str(name.__hash__())+'/query', query ) index = self.presetCombo.findText(name) if index == -1: self.presetCombo.addItem(name) self.presetCombo.setCurrentIndex(self.presetCombo.count()-1) else: self.presetCombo.setCurrentIndex(index) def deletePreset(self): name = self.presetCombo.currentText() QgsProject.instance().removeEntry('DBManager','savedQueries/q'+str(name.__hash__()) ) self.presetCombo.removeItem( self.presetCombo.findText(name) ) self.presetCombo.setCurrentIndex(-1) def loadPreset(self, name): query = QgsProject.instance().readEntry('DBManager','savedQueries/q'+str(name.__hash__())+'/query' )[0] name = QgsProject.instance().readEntry('DBManager','savedQueries/q'+str(name.__hash__())+'/name' )[0] self.editSql.setText(query) def closeEvent(self, e): """ save window state """ settings = QSettings() settings.setValue("/DB_Manager/sqlWindow/geometry", self.saveGeometry()) QDialog.closeEvent(self, e) def loadAsLayerToggled(self, checked): self.loadAsLayerGroup.setChecked( checked ) self.loadAsLayerWidget.setVisible( checked ) def getSql(self): # If the selection obtained from an editor spans a line break, # the text will contain a Unicode U+2029 paragraph separator # character instead of a newline \n character # (see https://qt-project.org/doc/qt-4.8/qtextcursor.html#selectedText) sql = self.editSql.textCursor().selectedText().replace(unichr(0x2029), "\n") if sql == "": sql = self.editSql.toPlainText() # try to sanitize query sql = re.sub( ";\\s*$", "", sql ) return sql def clearSql(self): self.editSql.clear() def executeSql(self): sql = self.getSql() if sql == "": return QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) # delete the old model old_model = self.viewResult.model() self.viewResult.setModel(None) if old_model: old_model.deleteLater() self.uniqueCombo.clear() self.geomCombo.clear() try: # set the new model model = self.db.sqlResultModel( sql, self ) self.viewResult.setModel( model ) self.lblResult.setText(self.tr("%d rows, %.1f seconds") % (model.affectedRows(), model.secs())) except BaseError, e: QApplication.restoreOverrideCursor() DlgDbError.showError(e, self) return cols = self.viewResult.model().columnNames() cols.sort() self.uniqueCombo.addItems( cols ) self.geomCombo.addItems( cols ) self.update() QApplication.restoreOverrideCursor() def loadSqlLayer(self): uniqueFieldName = self.uniqueCombo.currentText() geomFieldName = self.geomCombo.currentText() if geomFieldName == "" or uniqueFieldName == "": QMessageBox.warning(self, self.tr( "Sorry" ), self.tr( "You must fill the required fields: \ngeometry column - column with unique integer values" ) ) return query = self.getSql() if query == "": return QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) from qgis.core import QgsMapLayer, QgsMapLayerRegistry layerType = QgsMapLayer.VectorLayer if self.vectorRadio.isChecked() else QgsMapLayer.RasterLayer # get a new layer name names = [] for layer in QgsMapLayerRegistry.instance().mapLayers().values(): names.append( layer.name() ) layerName = self.layerNameEdit.text() if layerName == "": layerName = self.defaultLayerName newLayerName = layerName index = 1 while newLayerName in names: index += 1 newLayerName = u"%s_%d" % (layerName, index) # create the layer layer = self.db.toSqlLayer(query, geomFieldName, uniqueFieldName, newLayerName, layerType, self.avoidSelectById.isChecked()) if layer.isValid(): QgsMapLayerRegistry.instance().addMapLayers([layer], True) QApplication.restoreOverrideCursor() def fillColumnCombos(self): query = self.getSql() if query == "": return QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) self.uniqueCombo.clear() self.geomCombo.clear() # get a new alias aliasIndex = 0 while True: alias = "_%s__%d" % ("subQuery", aliasIndex) escaped = re.compile('\\b("?)' + re.escape(alias) + '\\1\\b') if not escaped.search(query): break aliasIndex += 1 # get all the columns cols = [] connector = self.db.connector sql = u"SELECT * FROM (%s\n) AS %s LIMIT 0" % ( unicode(query), connector.quoteId(alias) ) c = None try: c = connector._execute(None, sql) cols = connector._get_cursor_columns(c) except BaseError as e: QApplication.restoreOverrideCursor() DlgDbError.showError(e, self) return finally: if c: c.close() del c cols.sort() self.uniqueCombo.addItems( cols ) self.geomCombo.addItems( cols ) QApplication.restoreOverrideCursor() def copySelectedResults(self): if len(self.viewResult.selectedIndexes()) <= 0: return model = self.viewResult.model() # convert to string using tab as separator text = model.headerToString( "\t" ) for idx in self.viewResult.selectionModel().selectedRows(): text += "\n" + model.rowToString( idx.row(), "\t" ) QApplication.clipboard().setText( text, QClipboard.Selection ) QApplication.clipboard().setText( text, QClipboard.Clipboard )