mirror of
				https://github.com/qgis/QGIS.git
				synced 2025-11-04 00:04:25 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			725 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			725 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# -*- 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 builtins import zip
 | 
						|
from builtins import next
 | 
						|
from builtins import str
 | 
						|
from hashlib import md5
 | 
						|
 | 
						|
import os
 | 
						|
 | 
						|
from qgis.PyQt.QtCore import Qt, pyqtSignal, QDir, QCoreApplication
 | 
						|
from qgis.PyQt.QtWidgets import (QDialog,
 | 
						|
                                 QWidget,
 | 
						|
                                 QAction,
 | 
						|
                                 QApplication,
 | 
						|
                                 QInputDialog,
 | 
						|
                                 QStyledItemDelegate,
 | 
						|
                                 QTableWidgetItem,
 | 
						|
                                 QFileDialog,
 | 
						|
                                 QMessageBox
 | 
						|
                                 )
 | 
						|
from qgis.PyQt.QtGui import (QKeySequence,
 | 
						|
                             QCursor,
 | 
						|
                             QClipboard,
 | 
						|
                             QIcon,
 | 
						|
                             QStandardItemModel,
 | 
						|
                             QStandardItem
 | 
						|
                             )
 | 
						|
from qgis.PyQt.Qsci import QsciAPIs, QsciScintilla
 | 
						|
 | 
						|
from qgis.core import (
 | 
						|
    QgsProject,
 | 
						|
    QgsApplication,
 | 
						|
    QgsTask,
 | 
						|
    QgsSettings,
 | 
						|
    QgsMapLayerType
 | 
						|
)
 | 
						|
from qgis.utils import OverrideCursor
 | 
						|
 | 
						|
from .db_plugins.plugin import BaseError
 | 
						|
from .db_plugins.postgis.plugin import PGDatabase
 | 
						|
from .dlg_db_error import DlgDbError
 | 
						|
from .dlg_query_builder import QueryBuilderDlg
 | 
						|
 | 
						|
try:
 | 
						|
    from qgis.gui import QgsCodeEditorSQL  # NOQA
 | 
						|
except:
 | 
						|
    from .sqledit import SqlEdit
 | 
						|
    from qgis import gui
 | 
						|
 | 
						|
    gui.QgsCodeEditorSQL = SqlEdit
 | 
						|
 | 
						|
from .ui.ui_DlgSqlWindow import Ui_DbManagerDlgSqlWindow as Ui_Dialog
 | 
						|
 | 
						|
import re
 | 
						|
 | 
						|
 | 
						|
def check_comments_in_sql(raw_sql_input):
 | 
						|
    lines = []
 | 
						|
    for line in raw_sql_input.splitlines():
 | 
						|
        if not line.strip().startswith('--'):
 | 
						|
            if '--' in line:
 | 
						|
                comments = re.finditer(r'--', line)
 | 
						|
                comment_positions = [
 | 
						|
                    match.start()
 | 
						|
                    for match in comments
 | 
						|
                ]
 | 
						|
                identifiers = re.finditer(r'"(?:[^"]|"")*"', line)
 | 
						|
                quotes = re.finditer(r"'(?:[^']|'')*'", line)
 | 
						|
                quote_positions = []
 | 
						|
                for match in identifiers:
 | 
						|
                    quote_positions.append((match.start(), match.end()))
 | 
						|
                for match in quotes:
 | 
						|
                    quote_positions.append((match.start(), match.end()))
 | 
						|
                unquoted_comments = comment_positions.copy()
 | 
						|
                for comment in comment_positions:
 | 
						|
                    for quote_position in quote_positions:
 | 
						|
                        if comment >= quote_position[0] and comment < quote_position[1]:
 | 
						|
                            unquoted_comments.remove(comment)
 | 
						|
                if len(unquoted_comments) > 0:
 | 
						|
                    lines.append(line[:unquoted_comments[0]])
 | 
						|
                else:
 | 
						|
                    lines.append(line)
 | 
						|
            else:
 | 
						|
                lines.append(line)
 | 
						|
    sql = ' '.join(lines)
 | 
						|
    return sql.strip()
 | 
						|
 | 
						|
 | 
						|
class DlgSqlWindow(QWidget, Ui_Dialog):
 | 
						|
    nameChanged = pyqtSignal(str)
 | 
						|
    QUERY_HISTORY_LIMIT = 20
 | 
						|
    hasChanged = False
 | 
						|
 | 
						|
    def __init__(self, iface, db, parent=None):
 | 
						|
        QWidget.__init__(self, parent)
 | 
						|
        self.mainWindow = parent
 | 
						|
        self.iface = iface
 | 
						|
        self.db = db
 | 
						|
        self.dbType = db.connection().typeNameString()
 | 
						|
        self.connectionName = db.connection().connectionName()
 | 
						|
        self.filter = ""
 | 
						|
        self.modelAsync = None
 | 
						|
        self.allowMultiColumnPk = isinstance(db,
 | 
						|
                                             PGDatabase)  # at the moment only PostgreSQL allows a primary key to span multiple columns, SpatiaLite doesn't
 | 
						|
        self.aliasSubQuery = isinstance(db, PGDatabase)  # only PostgreSQL requires subqueries to be aliases
 | 
						|
        self.setupUi(self)
 | 
						|
        self.setWindowTitle(
 | 
						|
            self.tr(u"{0} - {1} [{2}]").format(self.windowTitle(), self.connectionName, self.dbType))
 | 
						|
 | 
						|
        self.defaultLayerName = self.tr('QueryLayer')
 | 
						|
 | 
						|
        if self.allowMultiColumnPk:
 | 
						|
            self.uniqueColumnCheck.setText(self.tr("Column(s) with unique values"))
 | 
						|
        else:
 | 
						|
            self.uniqueColumnCheck.setText(self.tr("Column with unique values"))
 | 
						|
 | 
						|
        self.editSql.setFocus()
 | 
						|
        self.editSql.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
 | 
						|
        self.editSql.setMarginVisible(True)
 | 
						|
        self.initCompleter()
 | 
						|
        self.editSql.textChanged.connect(lambda: self.setHasChanged(True))
 | 
						|
 | 
						|
        settings = QgsSettings()
 | 
						|
        self.history = settings.value('DB_Manager/queryHistory/' + self.dbType, {self.connectionName: []})
 | 
						|
        if self.connectionName not in self.history:
 | 
						|
            self.history[self.connectionName] = []
 | 
						|
 | 
						|
        self.queryHistoryWidget.setVisible(False)
 | 
						|
        self.queryHistoryTableWidget.verticalHeader().hide()
 | 
						|
        self.queryHistoryTableWidget.doubleClicked.connect(self.insertQueryInEditor)
 | 
						|
        self.populateQueryHistory()
 | 
						|
        self.btnQueryHistory.toggled.connect(self.showHideQueryHistory)
 | 
						|
 | 
						|
        self.btnCancel.setEnabled(False)
 | 
						|
        self.btnCancel.clicked.connect(self.executeSqlCanceled)
 | 
						|
        self.btnCancel.setShortcut(QKeySequence.Cancel)
 | 
						|
        self.progressBar.setEnabled(False)
 | 
						|
        self.progressBar.setRange(0, 100)
 | 
						|
        self.progressBar.setValue(0)
 | 
						|
        self.progressBar.setFormat("")
 | 
						|
        self.progressBar.setAlignment(Qt.AlignCenter)
 | 
						|
 | 
						|
        # allow copying results
 | 
						|
        copyAction = QAction("copy", self)
 | 
						|
        self.viewResult.addAction(copyAction)
 | 
						|
        copyAction.setShortcuts(QKeySequence.Copy)
 | 
						|
 | 
						|
        copyAction.triggered.connect(self.copySelectedResults)
 | 
						|
 | 
						|
        self.btnExecute.clicked.connect(self.executeSql)
 | 
						|
        self.btnSetFilter.clicked.connect(self.setFilter)
 | 
						|
        self.btnClear.clicked.connect(self.clearSql)
 | 
						|
 | 
						|
        self.presetStore.clicked.connect(self.storePreset)
 | 
						|
        self.presetSaveAsFile.clicked.connect(self.saveAsFilePreset)
 | 
						|
        self.presetLoadFile.clicked.connect(self.loadFilePreset)
 | 
						|
        self.presetDelete.clicked.connect(self.deletePreset)
 | 
						|
        self.presetCombo.activated[str].connect(self.loadPreset)
 | 
						|
        self.presetCombo.activated[str].connect(self.presetName.setText)
 | 
						|
 | 
						|
        self.updatePresetsCombobox()
 | 
						|
 | 
						|
        self.geomCombo.setEditable(True)
 | 
						|
        self.geomCombo.lineEdit().setReadOnly(True)
 | 
						|
 | 
						|
        self.uniqueCombo.setEditable(True)
 | 
						|
        self.uniqueCombo.lineEdit().setReadOnly(True)
 | 
						|
        self.uniqueModel = QStandardItemModel(self.uniqueCombo)
 | 
						|
        self.uniqueCombo.setModel(self.uniqueModel)
 | 
						|
        if self.allowMultiColumnPk:
 | 
						|
            self.uniqueCombo.setItemDelegate(QStyledItemDelegate())
 | 
						|
            self.uniqueModel.itemChanged.connect(self.uniqueChanged)  # react to the (un)checking of an item
 | 
						|
            self.uniqueCombo.lineEdit().textChanged.connect(
 | 
						|
                self.uniqueTextChanged)  # there are other events that change the displayed text and some of them can not be caught directly
 | 
						|
 | 
						|
        # 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.loadLayerBtn.clicked.connect(self.loadSqlLayer)
 | 
						|
            self.getColumnsBtn.clicked.connect(self.fillColumnCombos)
 | 
						|
            self.loadAsLayerGroup.toggled.connect(self.loadAsLayerToggled)
 | 
						|
            self.loadAsLayerToggled(False)
 | 
						|
 | 
						|
        self._createViewAvailable = self.db.connector.hasCreateSpatialViewSupport()
 | 
						|
        self.btnCreateView.setVisible(self._createViewAvailable)
 | 
						|
        if self._createViewAvailable:
 | 
						|
            self.btnCreateView.clicked.connect(self.createView)
 | 
						|
 | 
						|
        self.queryBuilderFirst = True
 | 
						|
        self.queryBuilderBtn.setIcon(QIcon(":/db_manager/icons/sql.gif"))
 | 
						|
        self.queryBuilderBtn.clicked.connect(self.displayQueryBuilder)
 | 
						|
 | 
						|
        self.presetName.textChanged.connect(self.nameChanged)
 | 
						|
 | 
						|
    def insertQueryInEditor(self, item):
 | 
						|
        sql = item.data(Qt.DisplayRole)
 | 
						|
        self.editSql.insertText(sql)
 | 
						|
 | 
						|
    def showHideQueryHistory(self, visible):
 | 
						|
        self.queryHistoryWidget.setVisible(visible)
 | 
						|
 | 
						|
    def populateQueryHistory(self):
 | 
						|
        self.queryHistoryTableWidget.clearContents()
 | 
						|
        self.queryHistoryTableWidget.setRowCount(0)
 | 
						|
        dictlist = self.history[self.connectionName]
 | 
						|
 | 
						|
        if not dictlist:
 | 
						|
            return
 | 
						|
 | 
						|
        for i in range(len(dictlist)):
 | 
						|
            self.queryHistoryTableWidget.insertRow(0)
 | 
						|
            queryItem = QTableWidgetItem(dictlist[i]['query'])
 | 
						|
            rowsItem = QTableWidgetItem(str(dictlist[i]['rows']))
 | 
						|
            durationItem = QTableWidgetItem(str(dictlist[i]['secs']))
 | 
						|
            self.queryHistoryTableWidget.setItem(0, 0, queryItem)
 | 
						|
            self.queryHistoryTableWidget.setItem(0, 1, rowsItem)
 | 
						|
            self.queryHistoryTableWidget.setItem(0, 2, durationItem)
 | 
						|
 | 
						|
        self.queryHistoryTableWidget.resizeColumnsToContents()
 | 
						|
        self.queryHistoryTableWidget.resizeRowsToContents()
 | 
						|
 | 
						|
    def writeQueryHistory(self, sql, affectedRows, secs):
 | 
						|
        if len(self.history[self.connectionName]) >= self.QUERY_HISTORY_LIMIT:
 | 
						|
            self.history[self.connectionName].pop(0)
 | 
						|
 | 
						|
        settings = QgsSettings()
 | 
						|
        self.history[self.connectionName].append({'query': sql,
 | 
						|
                                                  'rows': affectedRows,
 | 
						|
                                                  'secs': secs})
 | 
						|
        settings.setValue('DB_Manager/queryHistory/' + self.dbType, self.history)
 | 
						|
 | 
						|
        self.populateQueryHistory()
 | 
						|
 | 
						|
    def getQueryHash(self, name):
 | 
						|
        return 'q%s' % md5(name.encode('utf8')).hexdigest()
 | 
						|
 | 
						|
    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._getSqlQuery()
 | 
						|
        if query == "":
 | 
						|
            return
 | 
						|
        name = str(self.presetName.text())
 | 
						|
        QgsProject.instance().writeEntry('DBManager', 'savedQueries/' + self.getQueryHash(name) + '/name', name)
 | 
						|
        QgsProject.instance().writeEntry('DBManager', 'savedQueries/' + self.getQueryHash(name) + '/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 saveAsFilePreset(self):
 | 
						|
        settings = QgsSettings()
 | 
						|
        lastDir = settings.value('DB_Manager/lastDirSQLFIle', "")
 | 
						|
 | 
						|
        query = self.editSql.text()
 | 
						|
        if query == "":
 | 
						|
            return
 | 
						|
 | 
						|
        filename, _ = QFileDialog.getSaveFileName(
 | 
						|
            self,
 | 
						|
            self.tr('Save SQL Query'),
 | 
						|
            lastDir,
 | 
						|
            self.tr("SQL File (*.sql *.SQL)"))
 | 
						|
 | 
						|
        if filename:
 | 
						|
            if not filename.lower().endswith('.sql'):
 | 
						|
                filename += ".sql"
 | 
						|
 | 
						|
            with open(filename, 'w') as f:
 | 
						|
                f.write(query)
 | 
						|
                lastDir = os.path.dirname(filename)
 | 
						|
                settings.setValue('DB_Manager/lastDirSQLFile', lastDir)
 | 
						|
 | 
						|
    def loadFilePreset(self):
 | 
						|
        settings = QgsSettings()
 | 
						|
        lastDir = settings.value('DB_Manager/lastDirSQLFIle', "")
 | 
						|
 | 
						|
        filename, _ = QFileDialog.getOpenFileName(
 | 
						|
            self,
 | 
						|
            self.tr("Load SQL Query"),
 | 
						|
            lastDir,
 | 
						|
            self.tr("SQL File (*.sql *.SQL);;All Files (*)"))
 | 
						|
 | 
						|
        if filename:
 | 
						|
            with open(filename, 'r') as f:
 | 
						|
                self.editSql.clear()
 | 
						|
                for line in f:
 | 
						|
                    self.editSql.insertText(line)
 | 
						|
                lastDir = os.path.dirname(filename)
 | 
						|
                settings.setValue('DB_Manager/lastDirSQLFile', lastDir)
 | 
						|
 | 
						|
    def deletePreset(self):
 | 
						|
        name = self.presetCombo.currentText()
 | 
						|
        QgsProject.instance().removeEntry('DBManager', 'savedQueries/' + self.getQueryHash(name))
 | 
						|
        self.presetCombo.removeItem(self.presetCombo.findText(name))
 | 
						|
        self.presetCombo.setCurrentIndex(-1)
 | 
						|
 | 
						|
    def loadPreset(self, name):
 | 
						|
        query = QgsProject.instance().readEntry('DBManager', 'savedQueries/' + self.getQueryHash(name) + '/query')[0]
 | 
						|
        self.editSql.setText(query)
 | 
						|
 | 
						|
    def loadAsLayerToggled(self, checked):
 | 
						|
        self.loadAsLayerGroup.setChecked(checked)
 | 
						|
        self.loadAsLayerWidget.setVisible(checked)
 | 
						|
        if checked:
 | 
						|
            self.fillColumnCombos()
 | 
						|
 | 
						|
    def clearSql(self):
 | 
						|
        self.editSql.clear()
 | 
						|
        self.editSql.setFocus()
 | 
						|
        self.filter = ""
 | 
						|
        self.setHasChanged(True)
 | 
						|
 | 
						|
    def updateUiWhileSqlExecution(self, status):
 | 
						|
        if status:
 | 
						|
            for i in range(0, self.mainWindow.tabs.count()):
 | 
						|
                if i != self.mainWindow.tabs.currentIndex():
 | 
						|
                    self.mainWindow.tabs.setTabEnabled(i, False)
 | 
						|
 | 
						|
            self.mainWindow.menuBar.setEnabled(False)
 | 
						|
            self.mainWindow.toolBar.setEnabled(False)
 | 
						|
            self.mainWindow.tree.setEnabled(False)
 | 
						|
 | 
						|
            for w in self.findChildren(QWidget):
 | 
						|
                w.setEnabled(False)
 | 
						|
 | 
						|
            self.btnCancel.setEnabled(True)
 | 
						|
            self.progressBar.setEnabled(True)
 | 
						|
            self.progressBar.setRange(0, 0)
 | 
						|
        else:
 | 
						|
            for i in range(0, self.mainWindow.tabs.count()):
 | 
						|
                if i != self.mainWindow.tabs.currentIndex():
 | 
						|
                    self.mainWindow.tabs.setTabEnabled(i, True)
 | 
						|
 | 
						|
            self.mainWindow.refreshTabs()
 | 
						|
            self.mainWindow.menuBar.setEnabled(True)
 | 
						|
            self.mainWindow.toolBar.setEnabled(True)
 | 
						|
            self.mainWindow.tree.setEnabled(True)
 | 
						|
 | 
						|
            for w in self.findChildren(QWidget):
 | 
						|
                w.setEnabled(True)
 | 
						|
 | 
						|
            self.btnCancel.setEnabled(False)
 | 
						|
            self.progressBar.setRange(0, 100)
 | 
						|
            self.progressBar.setEnabled(False)
 | 
						|
 | 
						|
    def executeSqlCanceled(self):
 | 
						|
        self.btnCancel.setEnabled(False)
 | 
						|
        self.btnCancel.setText(QCoreApplication.translate("DlgSqlWindow", "Canceling…"))
 | 
						|
        self.modelAsync.cancel()
 | 
						|
 | 
						|
    def executeSqlCompleted(self):
 | 
						|
        self.updateUiWhileSqlExecution(False)
 | 
						|
 | 
						|
        with OverrideCursor(Qt.WaitCursor):
 | 
						|
            if self.modelAsync.task.status() == QgsTask.Complete:
 | 
						|
                model = self.modelAsync.model
 | 
						|
                self.showError(None)
 | 
						|
                self.viewResult.setModel(model)
 | 
						|
                self.lblResult.setText(self.tr("{0} rows, {1:.3f} seconds").format(model.affectedRows(), model.secs()))
 | 
						|
                cols = self.viewResult.model().columnNames()
 | 
						|
                quotedCols = [
 | 
						|
                    self.db.connector.quoteId(col)
 | 
						|
                    for col in cols
 | 
						|
                ]
 | 
						|
 | 
						|
                self.setColumnCombos(cols, quotedCols)
 | 
						|
 | 
						|
                self.writeQueryHistory(self.modelAsync.task.sql, model.affectedRows(), model.secs())
 | 
						|
                self.update()
 | 
						|
            elif not self.modelAsync.canceled:
 | 
						|
                self.showError(self.modelAsync.error)
 | 
						|
 | 
						|
                self.uniqueModel.clear()
 | 
						|
                self.geomCombo.clear()
 | 
						|
 | 
						|
            self.btnCancel.setText(self.tr("Cancel"))
 | 
						|
 | 
						|
    def executeSql(self):
 | 
						|
        sql = self._getExecutableSqlQuery()
 | 
						|
        if sql == "":
 | 
						|
            return
 | 
						|
 | 
						|
        # delete the old model
 | 
						|
        old_model = self.viewResult.model()
 | 
						|
        self.viewResult.setModel(None)
 | 
						|
        if old_model:
 | 
						|
            old_model.deleteLater()
 | 
						|
 | 
						|
        try:
 | 
						|
            self.modelAsync = self.db.sqlResultModelAsync(sql, self)
 | 
						|
            self.modelAsync.done.connect(self.executeSqlCompleted)
 | 
						|
            self.updateUiWhileSqlExecution(True)
 | 
						|
            QgsApplication.taskManager().addTask(self.modelAsync.task)
 | 
						|
        except Exception as e:
 | 
						|
            self.showError(e)
 | 
						|
            self.uniqueModel.clear()
 | 
						|
            self.geomCombo.clear()
 | 
						|
            return
 | 
						|
 | 
						|
    def showError(self, error):
 | 
						|
        '''Shows the error or hides it if error is None'''
 | 
						|
        if error:
 | 
						|
            self.viewResult.setVisible(False)
 | 
						|
            self.errorText.setVisible(True)
 | 
						|
            self.errorText.setText(error.msg)
 | 
						|
            self.errorText.setWrapMode(QsciScintilla.WrapWord)
 | 
						|
        else:
 | 
						|
            self.viewResult.setVisible(True)
 | 
						|
            self.errorText.setVisible(False)
 | 
						|
 | 
						|
    def _getSqlLayer(self, _filter):
 | 
						|
        hasUniqueField = self.uniqueColumnCheck.checkState() == Qt.Checked
 | 
						|
        if hasUniqueField and self.allowMultiColumnPk:
 | 
						|
            uniqueFieldName = ",".join(
 | 
						|
                item.data()
 | 
						|
                for item in self.uniqueModel.findItems("*", Qt.MatchWildcard)
 | 
						|
                if item.checkState() == Qt.Checked
 | 
						|
            )
 | 
						|
        elif (
 | 
						|
            hasUniqueField
 | 
						|
            and not self.allowMultiColumnPk
 | 
						|
            and self.uniqueCombo.currentIndex() >= 0
 | 
						|
        ):
 | 
						|
            uniqueFieldName = self.uniqueModel.item(self.uniqueCombo.currentIndex()).data()
 | 
						|
        else:
 | 
						|
            uniqueFieldName = None
 | 
						|
        hasGeomCol = self.hasGeometryCol.checkState() == Qt.Checked
 | 
						|
        if hasGeomCol:
 | 
						|
            geomFieldName = self.geomCombo.currentText()
 | 
						|
        else:
 | 
						|
            geomFieldName = None
 | 
						|
 | 
						|
        query = self._getExecutableSqlQuery()
 | 
						|
        if query == "":
 | 
						|
            return None
 | 
						|
 | 
						|
        # remove a trailing ';' from query if present
 | 
						|
        if query.strip().endswith(';'):
 | 
						|
            query = query.strip()[:-1]
 | 
						|
 | 
						|
        layerType = QgsMapLayerType.VectorLayer if self.vectorRadio.isChecked() else QgsMapLayerType.RasterLayer
 | 
						|
 | 
						|
        # get a new layer name
 | 
						|
        names = []
 | 
						|
        for layer in list(QgsProject.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(), _filter)
 | 
						|
        if layer.isValid():
 | 
						|
            return layer
 | 
						|
        else:
 | 
						|
            e = BaseError(self.tr("There was an error creating the SQL layer, please check the logs for further information."))
 | 
						|
            DlgDbError.showError(e, self)
 | 
						|
            return None
 | 
						|
 | 
						|
    def loadSqlLayer(self):
 | 
						|
        with OverrideCursor(Qt.WaitCursor):
 | 
						|
            layer = self._getSqlLayer(self.filter)
 | 
						|
            if layer is None:
 | 
						|
                return
 | 
						|
 | 
						|
            QgsProject.instance().addMapLayers([layer], True)
 | 
						|
 | 
						|
    def fillColumnCombos(self):
 | 
						|
        query = self._getExecutableSqlQuery()
 | 
						|
        if query == "":
 | 
						|
            return
 | 
						|
 | 
						|
        with OverrideCursor(Qt.WaitCursor):
 | 
						|
            # remove a trailing ';' from query if present
 | 
						|
            if query.strip().endswith(';'):
 | 
						|
                query = query.strip()[:-1]
 | 
						|
 | 
						|
            # get all the columns
 | 
						|
            quotedCols = []
 | 
						|
            connector = self.db.connector
 | 
						|
            if self.aliasSubQuery:
 | 
						|
                # get a new alias
 | 
						|
                aliasIndex = 0
 | 
						|
                while True:
 | 
						|
                    alias = "_subQuery__%d" % aliasIndex
 | 
						|
                    escaped = re.compile('\\b("?)' + re.escape(alias) + '\\1\\b')
 | 
						|
                    if not escaped.search(query):
 | 
						|
                        break
 | 
						|
                    aliasIndex += 1
 | 
						|
 | 
						|
                sql = u"SELECT * FROM (%s\n) AS %s LIMIT 0" % (str(query), connector.quoteId(alias))
 | 
						|
            else:
 | 
						|
                sql = u"SELECT * FROM (%s\n) WHERE 1=0" % str(query)
 | 
						|
 | 
						|
            c = None
 | 
						|
            try:
 | 
						|
                c = connector._execute(None, sql)
 | 
						|
                cols = connector._get_cursor_columns(c)
 | 
						|
                for col in cols:
 | 
						|
                    quotedCols.append(connector.quoteId(col))
 | 
						|
 | 
						|
            except BaseError as e:
 | 
						|
                DlgDbError.showError(e, self)
 | 
						|
                self.uniqueModel.clear()
 | 
						|
                self.geomCombo.clear()
 | 
						|
                return
 | 
						|
 | 
						|
            finally:
 | 
						|
                if c:
 | 
						|
                    c.close()
 | 
						|
                    del c
 | 
						|
 | 
						|
            self.setColumnCombos(cols, quotedCols)
 | 
						|
 | 
						|
    def setColumnCombos(self, cols, quotedCols):
 | 
						|
        # 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)
 | 
						|
        try:
 | 
						|
            defaultGeomCol = next(col for col in cols if col in ['geom', 'geometry', 'the_geom', 'way'])
 | 
						|
        except:
 | 
						|
            defaultGeomCol = None
 | 
						|
        try:
 | 
						|
            defaultUniqueCol = [col for col in cols if 'id' in col][0]
 | 
						|
        except:
 | 
						|
            defaultUniqueCol = None
 | 
						|
 | 
						|
        colNames = sorted(zip(cols, quotedCols))
 | 
						|
        newItems = []
 | 
						|
        uniqueIsFilled = False
 | 
						|
        for (col, quotedCol) in colNames:
 | 
						|
            item = QStandardItem(col)
 | 
						|
            item.setData(quotedCol)
 | 
						|
            item.setEnabled(True)
 | 
						|
            item.setCheckable(self.allowMultiColumnPk)
 | 
						|
            item.setSelectable(not self.allowMultiColumnPk)
 | 
						|
            if self.allowMultiColumnPk:
 | 
						|
                matchingItems = self.uniqueModel.findItems(col)
 | 
						|
                if matchingItems:
 | 
						|
                    item.setCheckState(matchingItems[0].checkState())
 | 
						|
                    uniqueIsFilled = uniqueIsFilled or matchingItems[0].checkState() == Qt.Checked
 | 
						|
                else:
 | 
						|
                    item.setCheckState(Qt.Unchecked)
 | 
						|
            newItems.append(item)
 | 
						|
        if self.allowMultiColumnPk:
 | 
						|
            self.uniqueModel.clear()
 | 
						|
            self.uniqueModel.appendColumn(newItems)
 | 
						|
            self.uniqueChanged()
 | 
						|
        else:
 | 
						|
            previousUniqueColumn = self.uniqueCombo.currentText()
 | 
						|
            self.uniqueModel.clear()
 | 
						|
            self.uniqueModel.appendColumn(newItems)
 | 
						|
            if self.uniqueModel.findItems(previousUniqueColumn):
 | 
						|
                self.uniqueCombo.setEditText(previousUniqueColumn)
 | 
						|
                uniqueIsFilled = True
 | 
						|
 | 
						|
        oldGeometryColumn = self.geomCombo.currentText()
 | 
						|
        self.geomCombo.clear()
 | 
						|
        self.geomCombo.addItems(cols)
 | 
						|
        self.geomCombo.setCurrentIndex(self.geomCombo.findText(oldGeometryColumn, Qt.MatchExactly))
 | 
						|
 | 
						|
        # set sensible default columns if the columns are not already set
 | 
						|
        try:
 | 
						|
            if self.geomCombo.currentIndex() == -1:
 | 
						|
                self.geomCombo.setCurrentIndex(cols.index(defaultGeomCol))
 | 
						|
        except:
 | 
						|
            pass
 | 
						|
        items = self.uniqueModel.findItems(defaultUniqueCol)
 | 
						|
        if items and not uniqueIsFilled:
 | 
						|
            if self.allowMultiColumnPk:
 | 
						|
                items[0].setCheckState(Qt.Checked)
 | 
						|
            else:
 | 
						|
                self.uniqueCombo.setEditText(defaultUniqueCol)
 | 
						|
 | 
						|
    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)
 | 
						|
 | 
						|
    def initCompleter(self):
 | 
						|
        dictionary = None
 | 
						|
        if self.db:
 | 
						|
            dictionary = self.db.connector.getSqlDictionary()
 | 
						|
        if not dictionary:
 | 
						|
            # use the generic sql dictionary
 | 
						|
            from .sql_dictionary import getSqlDictionary
 | 
						|
 | 
						|
            dictionary = getSqlDictionary()
 | 
						|
 | 
						|
        wordlist = []
 | 
						|
        for value in dictionary.values():
 | 
						|
            wordlist += value  # concat lists
 | 
						|
        wordlist = list(set(wordlist))  # remove duplicates
 | 
						|
 | 
						|
        api = QsciAPIs(self.editSql.lexer())
 | 
						|
        for word in wordlist:
 | 
						|
            api.add(word)
 | 
						|
 | 
						|
        api.prepare()
 | 
						|
        self.editSql.lexer().setAPIs(api)
 | 
						|
 | 
						|
    def displayQueryBuilder(self):
 | 
						|
        dlg = QueryBuilderDlg(self.iface, self.db, self, reset=self.queryBuilderFirst)
 | 
						|
        self.queryBuilderFirst = False
 | 
						|
        r = dlg.exec_()
 | 
						|
        if r == QDialog.Accepted:
 | 
						|
            self.editSql.setText(dlg.query)
 | 
						|
 | 
						|
    def createView(self):
 | 
						|
        name, ok = QInputDialog.getText(None, self.tr("View Name"), self.tr("View name"))
 | 
						|
        if ok:
 | 
						|
            try:
 | 
						|
                self.db.connector.createSpatialView(name, self._getExecutableSqlQuery())
 | 
						|
            except BaseError as e:
 | 
						|
                DlgDbError.showError(e, self)
 | 
						|
 | 
						|
    def _getSqlQuery(self):
 | 
						|
        sql = self.editSql.selectedText()
 | 
						|
        if len(sql) == 0:
 | 
						|
            sql = self.editSql.text()
 | 
						|
        return sql
 | 
						|
 | 
						|
    def _getExecutableSqlQuery(self):
 | 
						|
        sql = self._getSqlQuery().strip()
 | 
						|
 | 
						|
        uncommented_sql = check_comments_in_sql(sql)
 | 
						|
        uncommented_sql = uncommented_sql.rstrip(';')
 | 
						|
        return uncommented_sql
 | 
						|
 | 
						|
    def uniqueChanged(self):
 | 
						|
        # when an item is (un)checked, simply trigger an update of the combobox text
 | 
						|
        self.uniqueTextChanged(None)
 | 
						|
 | 
						|
    def uniqueTextChanged(self, text):
 | 
						|
        # Whenever there is new text displayed in the combobox, check if it is the correct one and if not, display the correct one.
 | 
						|
        label = ", ".join(
 | 
						|
            item.text()
 | 
						|
            for item in self.uniqueModel.findItems("*", Qt.MatchWildcard)
 | 
						|
            if item.checkState() == Qt.Checked
 | 
						|
        )
 | 
						|
        if text != label:
 | 
						|
            self.uniqueCombo.setEditText(label)
 | 
						|
 | 
						|
    def setFilter(self):
 | 
						|
        from qgis.gui import QgsQueryBuilder
 | 
						|
        layer = self._getSqlLayer("")
 | 
						|
        if not layer:
 | 
						|
            return
 | 
						|
 | 
						|
        dlg = QgsQueryBuilder(layer)
 | 
						|
        dlg.setSql(self.filter)
 | 
						|
        if dlg.exec_():
 | 
						|
            self.filter = dlg.sql()
 | 
						|
        layer.deleteLater()
 | 
						|
 | 
						|
    def setHasChanged(self, hasChanged):
 | 
						|
        self.hasChanged = hasChanged
 | 
						|
 | 
						|
    def close(self):
 | 
						|
        if self.hasChanged:
 | 
						|
            ret = QMessageBox.question(
 | 
						|
                self, self.tr('Unsaved Changes?'),
 | 
						|
                self.tr('There are unsaved changes. Do you want to keep them?'),
 | 
						|
                QMessageBox.Save | QMessageBox.Cancel | QMessageBox.Discard, QMessageBox.Cancel)
 | 
						|
 | 
						|
            if ret == QMessageBox.Save:
 | 
						|
                self.saveAsFilePreset()
 | 
						|
                return True
 | 
						|
            elif ret == QMessageBox.Discard:
 | 
						|
                return True
 | 
						|
            else:
 | 
						|
                return False
 | 
						|
        else:
 | 
						|
            return True
 |