mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-14 00:07:35 -04:00
Add a cancel button for Postgis and Spatialite
This commit is contained in:
parent
0081f781d3
commit
bf7df6d2e3
@ -42,6 +42,9 @@ class DBConnector(object):
|
||||
def uri(self):
|
||||
return QgsDataSourceUri(self._uri.uri(False))
|
||||
|
||||
def cancel(self):
|
||||
pass
|
||||
|
||||
def publicUri(self):
|
||||
publicUri = QgsDataSourceUri.removePassword(self._uri.uri(False))
|
||||
return QgsDataSourceUri(publicUri)
|
||||
|
@ -22,11 +22,18 @@ email : brush.tyler@gmail.com
|
||||
from builtins import str
|
||||
from builtins import range
|
||||
|
||||
from qgis.PyQt.QtCore import Qt, QTime, QRegExp, QAbstractTableModel
|
||||
from qgis.PyQt.QtGui import QFont, QStandardItemModel, QStandardItem
|
||||
from qgis.PyQt.QtCore import (Qt,
|
||||
QTime,
|
||||
QRegExp,
|
||||
QAbstractTableModel,
|
||||
pyqtSignal,
|
||||
QObject)
|
||||
from qgis.PyQt.QtGui import (QFont,
|
||||
QStandardItemModel,
|
||||
QStandardItem)
|
||||
from qgis.PyQt.QtWidgets import QApplication
|
||||
|
||||
from .plugin import DbError
|
||||
from .plugin import DbError, BaseError
|
||||
|
||||
|
||||
class BaseTableModel(QAbstractTableModel):
|
||||
@ -139,6 +146,33 @@ class TableDataModel(BaseTableModel):
|
||||
return self.table.rowCount if self.table.rowCount is not None and self.columnCount(index) > 0 else 0
|
||||
|
||||
|
||||
class SqlResultModelAsync(QObject):
|
||||
|
||||
done = pyqtSignal()
|
||||
|
||||
def __init__(self, db, sql, parent=None):
|
||||
QObject.__init__(self)
|
||||
self.db = db
|
||||
self.sql = sql
|
||||
self.parent = parent
|
||||
self.error = BaseError('')
|
||||
self.status = None
|
||||
self.model = None
|
||||
self.task = None
|
||||
|
||||
def cancel(self):
|
||||
if self.task:
|
||||
self.task.cancelQuery()
|
||||
|
||||
def modelDone(self):
|
||||
if self.task:
|
||||
self.status = self.task.status
|
||||
self.model = self.task.model
|
||||
self.error = self.task.error
|
||||
|
||||
self.done.emit()
|
||||
|
||||
|
||||
class SqlResultModel(BaseTableModel):
|
||||
|
||||
def __init__(self, db, sql, parent=None):
|
||||
|
@ -256,6 +256,11 @@ class Database(DbItemObject):
|
||||
|
||||
return SqlResultModel(self, sql, parent)
|
||||
|
||||
def sqlResultModelAsync(self, sql, parent):
|
||||
from .data_model import SqlResultModelAsync
|
||||
|
||||
return SqlResultModelAsync(self, sql, parent)
|
||||
|
||||
def columnUniqueValuesModel(self, col, table, limit=10):
|
||||
l = ""
|
||||
if limit is not None:
|
||||
|
@ -186,6 +186,10 @@ class PostGisDBConnector(DBConnector):
|
||||
self.has_raster_columns_access = self.getTablePrivileges('raster_columns')[0]
|
||||
return self.has_raster_columns
|
||||
|
||||
def cancel(self):
|
||||
if self.connection:
|
||||
self.connection.cancel()
|
||||
|
||||
def getInfo(self):
|
||||
c = self._execute(None, u"SELECT version()")
|
||||
res = self._fetchone(c)
|
||||
|
@ -20,8 +20,9 @@ email : brush.tyler@gmail.com
|
||||
***************************************************************************/
|
||||
"""
|
||||
|
||||
|
||||
from ..data_model import TableDataModel, SqlResultModel
|
||||
from qgis.core import QgsTask
|
||||
from ..plugin import BaseError
|
||||
from ..data_model import TableDataModel, SqlResultModel, SqlResultModelAsync
|
||||
|
||||
|
||||
class PGTableDataModel(TableDataModel):
|
||||
@ -79,5 +80,41 @@ class PGTableDataModel(TableDataModel):
|
||||
self.fetchedFrom = row_start
|
||||
|
||||
|
||||
class PGSqlResultModelTask(QgsTask):
|
||||
|
||||
def __init__(self, db, sql, parent):
|
||||
QgsTask.__init__(self)
|
||||
self.db = db
|
||||
self.sql = sql
|
||||
self.parent = parent
|
||||
self.error = BaseError('')
|
||||
self.model = None
|
||||
|
||||
def run(self):
|
||||
|
||||
try:
|
||||
self.model = PGSqlResultModel(self.db, self.sql, self.parent)
|
||||
except BaseError as e:
|
||||
self.error = e
|
||||
QgsMessageLog.logMessage(e.msg)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def cancelQuery(self):
|
||||
self.db.connector.cancel()
|
||||
self.cancel()
|
||||
|
||||
|
||||
class PGSqlResultModelAsync(SqlResultModelAsync):
|
||||
|
||||
def __init__(self, db, sql, parent):
|
||||
SqlResultModelAsync.__init__(self, db, sql, parent)
|
||||
|
||||
self.task = PGSqlResultModelTask(db, sql, parent)
|
||||
self.task.taskCompleted.connect(self.modelDone)
|
||||
self.task.taskTerminated.connect(self.modelDone)
|
||||
|
||||
|
||||
class PGSqlResultModel(SqlResultModel):
|
||||
pass
|
||||
|
@ -134,6 +134,11 @@ class PGDatabase(Database):
|
||||
|
||||
return PGSqlResultModel(self, sql, parent)
|
||||
|
||||
def sqlResultModelAsync(self, sql, parent):
|
||||
from .data_model import PGSqlResultModelAsync
|
||||
|
||||
return PGSqlResultModelAsync(self, sql, parent)
|
||||
|
||||
def registerDatabaseActions(self, mainWindow):
|
||||
Database.registerDatabaseActions(self, mainWindow)
|
||||
|
||||
|
@ -59,6 +59,12 @@ class SpatiaLiteDBConnector(DBConnector):
|
||||
def _connectionInfo(self):
|
||||
return str(self.dbname)
|
||||
|
||||
def cancel(self):
|
||||
# https://www.sqlite.org/c3ref/interrupt.html
|
||||
# This function causes any pending database operation to abort and return at its earliest opportunity.
|
||||
if self.connection:
|
||||
self.connection.interrupt()
|
||||
|
||||
@classmethod
|
||||
def isValidDatabase(self, path):
|
||||
if not QFile.exists(path):
|
||||
|
@ -20,7 +20,10 @@ email : brush.tyler@gmail.com
|
||||
***************************************************************************/
|
||||
"""
|
||||
|
||||
from ..data_model import TableDataModel, SqlResultModel
|
||||
from qgis.core import QgsTask
|
||||
from ..plugin import BaseError
|
||||
from ..data_model import TableDataModel, SqlResultModel, SqlResultModelAsync
|
||||
from .plugin import SLDatabase
|
||||
|
||||
|
||||
class SLTableDataModel(TableDataModel):
|
||||
@ -60,5 +63,47 @@ class SLTableDataModel(TableDataModel):
|
||||
return self.fetchedCount
|
||||
|
||||
|
||||
class SLSqlResultModelTask(QgsTask):
|
||||
|
||||
def __init__(self, db, sql, parent):
|
||||
QgsTask.__init__(self)
|
||||
self.db = db
|
||||
self.sql = sql
|
||||
self.parent = parent
|
||||
self.error = BaseError('')
|
||||
self.model = None
|
||||
self.clone = None
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
self.clone = SLDatabase(None, self.db.connector.uri())
|
||||
|
||||
# import time
|
||||
# self.clone.connector.connection.create_function("sleep", 1, time.sleep)
|
||||
|
||||
self.model = SLSqlResultModel(self.clone, self.sql, None)
|
||||
except BaseError as e:
|
||||
self.error = e
|
||||
QgsMessageLog.logMessage(e.msg)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def cancelQuery(self):
|
||||
if self.clone:
|
||||
self.clone.connector.cancel()
|
||||
self.cancel()
|
||||
|
||||
|
||||
class SLSqlResultModelAsync(SqlResultModelAsync):
|
||||
|
||||
def __init__(self, db, sql, parent):
|
||||
SqlResultModelAsync.__init__(self, db, sql, parent)
|
||||
|
||||
self.task = SLSqlResultModelTask(db, sql, parent)
|
||||
self.task.taskCompleted.connect(self.modelDone)
|
||||
self.task.taskTerminated.connect(self.modelDone)
|
||||
|
||||
|
||||
class SLSqlResultModel(SqlResultModel):
|
||||
pass
|
||||
|
@ -130,6 +130,11 @@ class SLDatabase(Database):
|
||||
|
||||
return SLSqlResultModel(self, sql, parent)
|
||||
|
||||
def sqlResultModelAsync(self, sql, parent):
|
||||
from .data_model import SLSqlResultModelAsync
|
||||
|
||||
return SLSqlResultModelAsync(self, sql, parent)
|
||||
|
||||
def registerDatabaseActions(self, mainWindow):
|
||||
action = QAction(self.tr("Run &Vacuum"), self)
|
||||
mainWindow.registerAction(action, self.tr("&Database"), self.runVacuumActionSlot)
|
||||
|
66
python/plugins/db_manager/dlg_cancel_task_query.py
Normal file
66
python/plugins/db_manager/dlg_cancel_task_query.py
Normal file
@ -0,0 +1,66 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
/***************************************************************************
|
||||
Name : DB Manager
|
||||
Description : Database manager plugin for QGIS
|
||||
Date : January 15, 2018
|
||||
copyright : (C) 2018 by Paul Blottiere
|
||||
email : paul.blottiere@oslandia.com
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* *
|
||||
* 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 qgis.PyQt.QtWidgets import QDialog, QLabel, QHBoxLayout
|
||||
from qgis.PyQt.QtGui import QMovie
|
||||
from qgis.PyQt.QtCore import QSize, Qt, pyqtSignal
|
||||
|
||||
from qgis.core import QgsApplication
|
||||
|
||||
from .ui.ui_DlgCancelTaskQuery import Ui_DlgCancelTaskQuery as Ui_Dialog
|
||||
|
||||
|
||||
class DlgCancelTaskQuery(QDialog, Ui_Dialog):
|
||||
|
||||
canceled = pyqtSignal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QDialog.__init__(self, parent)
|
||||
self.setupUi(self)
|
||||
|
||||
gif = QgsApplication.iconPath("/mIconLoading.gif")
|
||||
self.mGif = QMovie(gif)
|
||||
self.mGif.setScaledSize(QSize(16, 16))
|
||||
|
||||
self.mMovie.setMovie(self.mGif)
|
||||
self.setWindowModality(Qt.ApplicationModal)
|
||||
|
||||
self.mCancelButton.clicked.connect(self.cancel)
|
||||
|
||||
self.cancelStatus = False
|
||||
|
||||
def cancel(self):
|
||||
self.mLabel.setText("Stopping SQL...")
|
||||
self.cancelStatus = True
|
||||
self.mCancelButton.setEnabled(False)
|
||||
self.canceled.emit()
|
||||
|
||||
def show(self):
|
||||
self.cancelStatus = False
|
||||
self.mGif.start()
|
||||
self.mCancelButton.setEnabled(True)
|
||||
self.mLabel.setText("Executing SQL...")
|
||||
super(QDialog, self).show()
|
||||
|
||||
def hide(self):
|
||||
self.cancelStatus = False
|
||||
self.mGif.stop()
|
||||
super(QDialog, self).hide()
|
@ -30,13 +30,14 @@ from qgis.PyQt.QtWidgets import QDialog, QWidget, QAction, QApplication, QInputD
|
||||
from qgis.PyQt.QtGui import QKeySequence, QCursor, QClipboard, QIcon, QStandardItemModel, QStandardItem
|
||||
from qgis.PyQt.Qsci import QsciAPIs
|
||||
|
||||
from qgis.core import QgsProject
|
||||
from qgis.core import QgsProject, QgsApplication, QgsTask
|
||||
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
|
||||
from .dlg_cancel_task_query import DlgCancelTaskQuery
|
||||
|
||||
try:
|
||||
from qgis.gui import QgsCodeEditorSQL # NOQA
|
||||
@ -59,6 +60,9 @@ class DlgSqlWindow(QWidget, Ui_Dialog):
|
||||
self.iface = iface
|
||||
self.db = db
|
||||
self.filter = ""
|
||||
self.modelAsync = None
|
||||
self.dlg_cancel_task = DlgCancelTaskQuery(self)
|
||||
self.dlg_cancel_task.canceled.connect(self.executeSqlCanceled)
|
||||
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)
|
||||
@ -177,40 +181,53 @@ class DlgSqlWindow(QWidget, Ui_Dialog):
|
||||
self.editSql.setFocus()
|
||||
self.filter = ""
|
||||
|
||||
def executeSqlCanceled(self):
|
||||
self.modelAsync.cancel()
|
||||
|
||||
def executeSqlCompleted(self):
|
||||
self.dlg_cancel_task.hide()
|
||||
|
||||
if self.modelAsync.task.status() == QgsTask.Complete:
|
||||
model = self.modelAsync.model
|
||||
cols = []
|
||||
quotedCols = []
|
||||
|
||||
self.viewResult.setModel(model)
|
||||
self.lblResult.setText(self.tr("{0} rows, {1:.1f} seconds").format(model.affectedRows(), model.secs()))
|
||||
cols = self.viewResult.model().columnNames()
|
||||
for col in cols:
|
||||
quotedCols.append(self.db.connector.quoteId(col))
|
||||
|
||||
self.setColumnCombos(cols, quotedCols)
|
||||
self.update()
|
||||
elif not self.dlg_cancel_task.cancelStatus:
|
||||
DlgDbError.showError(self.modelAsync.error, self)
|
||||
self.uniqueModel.clear()
|
||||
self.geomCombo.clear()
|
||||
pass
|
||||
|
||||
def executeSql(self):
|
||||
|
||||
sql = self._getSqlQuery()
|
||||
if sql == "":
|
||||
return
|
||||
|
||||
with OverrideCursor(Qt.WaitCursor):
|
||||
# delete the old model
|
||||
old_model = self.viewResult.model()
|
||||
self.viewResult.setModel(None)
|
||||
if old_model:
|
||||
old_model.deleteLater()
|
||||
# delete the old model
|
||||
old_model = self.viewResult.model()
|
||||
self.viewResult.setModel(None)
|
||||
if old_model:
|
||||
old_model.deleteLater()
|
||||
|
||||
cols = []
|
||||
quotedCols = []
|
||||
|
||||
try:
|
||||
# set the new model
|
||||
model = self.db.sqlResultModel(sql, self)
|
||||
self.viewResult.setModel(model)
|
||||
self.lblResult.setText(self.tr("{0} rows, {1:.1f} seconds").format(model.affectedRows(), model.secs()))
|
||||
cols = self.viewResult.model().columnNames()
|
||||
for col in cols:
|
||||
quotedCols.append(self.db.connector.quoteId(col))
|
||||
|
||||
except BaseError as e:
|
||||
DlgDbError.showError(e, self)
|
||||
self.uniqueModel.clear()
|
||||
self.geomCombo.clear()
|
||||
return
|
||||
|
||||
self.setColumnCombos(cols, quotedCols)
|
||||
|
||||
self.update()
|
||||
try:
|
||||
self.modelAsync = self.db.sqlResultModelAsync(sql, self)
|
||||
self.modelAsync.done.connect(self.executeSqlCompleted)
|
||||
self.dlg_cancel_task.show()
|
||||
QgsApplication.taskManager().addTask(self.modelAsync.task)
|
||||
except Exception as e:
|
||||
DlgDbError.showError(e, self)
|
||||
self.uniqueModel.clear()
|
||||
self.geomCombo.clear()
|
||||
return
|
||||
|
||||
def _getSqlLayer(self, _filter):
|
||||
hasUniqueField = self.uniqueColumnCheck.checkState() == Qt.Checked
|
||||
|
86
python/plugins/db_manager/ui/DlgCancelTaskQuery.ui
Normal file
86
python/plugins/db_manager/ui/DlgCancelTaskQuery.ui
Normal file
@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>DlgCancelTaskQuery</class>
|
||||
<widget class="QDialog" name="DlgCancelTaskQuery">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>178</width>
|
||||
<height>101</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="verticalLayoutWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>10</y>
|
||||
<width>168</width>
|
||||
<height>80</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="mLabel">
|
||||
<property name="text">
|
||||
<string>Executing SQL...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="mMovie">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="mCancelButton">
|
||||
<property name="text">
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
Loading…
x
Reference in New Issue
Block a user