Skip to content

Commit bf7df6d

Browse files
committedMar 2, 2018
Add a cancel button for Postgis and Spatialite
1 parent 0081f78 commit bf7df6d

File tree

12 files changed

+346
-33
lines changed

12 files changed

+346
-33
lines changed
 

‎python/plugins/db_manager/db_plugins/connector.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ def __del__(self):
4242
def uri(self):
4343
return QgsDataSourceUri(self._uri.uri(False))
4444

45+
def cancel(self):
46+
pass
47+
4548
def publicUri(self):
4649
publicUri = QgsDataSourceUri.removePassword(self._uri.uri(False))
4750
return QgsDataSourceUri(publicUri)

‎python/plugins/db_manager/db_plugins/data_model.py

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,18 @@
2222
from builtins import str
2323
from builtins import range
2424

25-
from qgis.PyQt.QtCore import Qt, QTime, QRegExp, QAbstractTableModel
26-
from qgis.PyQt.QtGui import QFont, QStandardItemModel, QStandardItem
25+
from qgis.PyQt.QtCore import (Qt,
26+
QTime,
27+
QRegExp,
28+
QAbstractTableModel,
29+
pyqtSignal,
30+
QObject)
31+
from qgis.PyQt.QtGui import (QFont,
32+
QStandardItemModel,
33+
QStandardItem)
2734
from qgis.PyQt.QtWidgets import QApplication
2835

29-
from .plugin import DbError
36+
from .plugin import DbError, BaseError
3037

3138

3239
class BaseTableModel(QAbstractTableModel):
@@ -139,6 +146,33 @@ def rowCount(self, index=None):
139146
return self.table.rowCount if self.table.rowCount is not None and self.columnCount(index) > 0 else 0
140147

141148

149+
class SqlResultModelAsync(QObject):
150+
151+
done = pyqtSignal()
152+
153+
def __init__(self, db, sql, parent=None):
154+
QObject.__init__(self)
155+
self.db = db
156+
self.sql = sql
157+
self.parent = parent
158+
self.error = BaseError('')
159+
self.status = None
160+
self.model = None
161+
self.task = None
162+
163+
def cancel(self):
164+
if self.task:
165+
self.task.cancelQuery()
166+
167+
def modelDone(self):
168+
if self.task:
169+
self.status = self.task.status
170+
self.model = self.task.model
171+
self.error = self.task.error
172+
173+
self.done.emit()
174+
175+
142176
class SqlResultModel(BaseTableModel):
143177

144178
def __init__(self, db, sql, parent=None):

‎python/plugins/db_manager/db_plugins/plugin.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,11 @@ def sqlResultModel(self, sql, parent):
256256

257257
return SqlResultModel(self, sql, parent)
258258

259+
def sqlResultModelAsync(self, sql, parent):
260+
from .data_model import SqlResultModelAsync
261+
262+
return SqlResultModelAsync(self, sql, parent)
263+
259264
def columnUniqueValuesModel(self, col, table, limit=10):
260265
l = ""
261266
if limit is not None:

‎python/plugins/db_manager/db_plugins/postgis/connector.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,10 @@ def _checkRasterColumnsTable(self):
186186
self.has_raster_columns_access = self.getTablePrivileges('raster_columns')[0]
187187
return self.has_raster_columns
188188

189+
def cancel(self):
190+
if self.connection:
191+
self.connection.cancel()
192+
189193
def getInfo(self):
190194
c = self._execute(None, u"SELECT version()")
191195
res = self._fetchone(c)

‎python/plugins/db_manager/db_plugins/postgis/data_model.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@
2020
***************************************************************************/
2121
"""
2222

23-
24-
from ..data_model import TableDataModel, SqlResultModel
23+
from qgis.core import QgsTask
24+
from ..plugin import BaseError
25+
from ..data_model import TableDataModel, SqlResultModel, SqlResultModelAsync
2526

2627

2728
class PGTableDataModel(TableDataModel):
@@ -79,5 +80,41 @@ def fetchMoreData(self, row_start):
7980
self.fetchedFrom = row_start
8081

8182

83+
class PGSqlResultModelTask(QgsTask):
84+
85+
def __init__(self, db, sql, parent):
86+
QgsTask.__init__(self)
87+
self.db = db
88+
self.sql = sql
89+
self.parent = parent
90+
self.error = BaseError('')
91+
self.model = None
92+
93+
def run(self):
94+
95+
try:
96+
self.model = PGSqlResultModel(self.db, self.sql, self.parent)
97+
except BaseError as e:
98+
self.error = e
99+
QgsMessageLog.logMessage(e.msg)
100+
return False
101+
102+
return True
103+
104+
def cancelQuery(self):
105+
self.db.connector.cancel()
106+
self.cancel()
107+
108+
109+
class PGSqlResultModelAsync(SqlResultModelAsync):
110+
111+
def __init__(self, db, sql, parent):
112+
SqlResultModelAsync.__init__(self, db, sql, parent)
113+
114+
self.task = PGSqlResultModelTask(db, sql, parent)
115+
self.task.taskCompleted.connect(self.modelDone)
116+
self.task.taskTerminated.connect(self.modelDone)
117+
118+
82119
class PGSqlResultModel(SqlResultModel):
83120
pass

‎python/plugins/db_manager/db_plugins/postgis/plugin.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,11 @@ def sqlResultModel(self, sql, parent):
134134

135135
return PGSqlResultModel(self, sql, parent)
136136

137+
def sqlResultModelAsync(self, sql, parent):
138+
from .data_model import PGSqlResultModelAsync
139+
140+
return PGSqlResultModelAsync(self, sql, parent)
141+
137142
def registerDatabaseActions(self, mainWindow):
138143
Database.registerDatabaseActions(self, mainWindow)
139144

‎python/plugins/db_manager/db_plugins/spatialite/connector.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ def __init__(self, uri):
5959
def _connectionInfo(self):
6060
return str(self.dbname)
6161

62+
def cancel(self):
63+
# https://www.sqlite.org/c3ref/interrupt.html
64+
# This function causes any pending database operation to abort and return at its earliest opportunity.
65+
if self.connection:
66+
self.connection.interrupt()
67+
6268
@classmethod
6369
def isValidDatabase(self, path):
6470
if not QFile.exists(path):

‎python/plugins/db_manager/db_plugins/spatialite/data_model.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@
2020
***************************************************************************/
2121
"""
2222

23-
from ..data_model import TableDataModel, SqlResultModel
23+
from qgis.core import QgsTask
24+
from ..plugin import BaseError
25+
from ..data_model import TableDataModel, SqlResultModel, SqlResultModelAsync
26+
from .plugin import SLDatabase
2427

2528

2629
class SLTableDataModel(TableDataModel):
@@ -60,5 +63,47 @@ def rowCount(self, index=None):
6063
return self.fetchedCount
6164

6265

66+
class SLSqlResultModelTask(QgsTask):
67+
68+
def __init__(self, db, sql, parent):
69+
QgsTask.__init__(self)
70+
self.db = db
71+
self.sql = sql
72+
self.parent = parent
73+
self.error = BaseError('')
74+
self.model = None
75+
self.clone = None
76+
77+
def run(self):
78+
try:
79+
self.clone = SLDatabase(None, self.db.connector.uri())
80+
81+
# import time
82+
# self.clone.connector.connection.create_function("sleep", 1, time.sleep)
83+
84+
self.model = SLSqlResultModel(self.clone, self.sql, None)
85+
except BaseError as e:
86+
self.error = e
87+
QgsMessageLog.logMessage(e.msg)
88+
return False
89+
90+
return True
91+
92+
def cancelQuery(self):
93+
if self.clone:
94+
self.clone.connector.cancel()
95+
self.cancel()
96+
97+
98+
class SLSqlResultModelAsync(SqlResultModelAsync):
99+
100+
def __init__(self, db, sql, parent):
101+
SqlResultModelAsync.__init__(self, db, sql, parent)
102+
103+
self.task = SLSqlResultModelTask(db, sql, parent)
104+
self.task.taskCompleted.connect(self.modelDone)
105+
self.task.taskTerminated.connect(self.modelDone)
106+
107+
63108
class SLSqlResultModel(SqlResultModel):
64109
pass

‎python/plugins/db_manager/db_plugins/spatialite/plugin.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,11 @@ def sqlResultModel(self, sql, parent):
130130

131131
return SLSqlResultModel(self, sql, parent)
132132

133+
def sqlResultModelAsync(self, sql, parent):
134+
from .data_model import SLSqlResultModelAsync
135+
136+
return SLSqlResultModelAsync(self, sql, parent)
137+
133138
def registerDatabaseActions(self, mainWindow):
134139
action = QAction(self.tr("Run &Vacuum"), self)
135140
mainWindow.registerAction(action, self.tr("&Database"), self.runVacuumActionSlot)
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
/***************************************************************************
5+
Name : DB Manager
6+
Description : Database manager plugin for QGIS
7+
Date : January 15, 2018
8+
copyright : (C) 2018 by Paul Blottiere
9+
email : paul.blottiere@oslandia.com
10+
***************************************************************************/
11+
12+
/***************************************************************************
13+
* *
14+
* This program is free software; you can redistribute it and/or modify *
15+
* it under the terms of the GNU General Public License as published by *
16+
* the Free Software Foundation; either version 2 of the License, or *
17+
* (at your option) any later version. *
18+
* *
19+
***************************************************************************/
20+
"""
21+
22+
from qgis.PyQt.QtWidgets import QDialog, QLabel, QHBoxLayout
23+
from qgis.PyQt.QtGui import QMovie
24+
from qgis.PyQt.QtCore import QSize, Qt, pyqtSignal
25+
26+
from qgis.core import QgsApplication
27+
28+
from .ui.ui_DlgCancelTaskQuery import Ui_DlgCancelTaskQuery as Ui_Dialog
29+
30+
31+
class DlgCancelTaskQuery(QDialog, Ui_Dialog):
32+
33+
canceled = pyqtSignal()
34+
35+
def __init__(self, parent=None):
36+
QDialog.__init__(self, parent)
37+
self.setupUi(self)
38+
39+
gif = QgsApplication.iconPath("/mIconLoading.gif")
40+
self.mGif = QMovie(gif)
41+
self.mGif.setScaledSize(QSize(16, 16))
42+
43+
self.mMovie.setMovie(self.mGif)
44+
self.setWindowModality(Qt.ApplicationModal)
45+
46+
self.mCancelButton.clicked.connect(self.cancel)
47+
48+
self.cancelStatus = False
49+
50+
def cancel(self):
51+
self.mLabel.setText("Stopping SQL...")
52+
self.cancelStatus = True
53+
self.mCancelButton.setEnabled(False)
54+
self.canceled.emit()
55+
56+
def show(self):
57+
self.cancelStatus = False
58+
self.mGif.start()
59+
self.mCancelButton.setEnabled(True)
60+
self.mLabel.setText("Executing SQL...")
61+
super(QDialog, self).show()
62+
63+
def hide(self):
64+
self.cancelStatus = False
65+
self.mGif.stop()
66+
super(QDialog, self).hide()

‎python/plugins/db_manager/dlg_sql_window.py

Lines changed: 44 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,14 @@
3030
from qgis.PyQt.QtGui import QKeySequence, QCursor, QClipboard, QIcon, QStandardItemModel, QStandardItem
3131
from qgis.PyQt.Qsci import QsciAPIs
3232

33-
from qgis.core import QgsProject
33+
from qgis.core import QgsProject, QgsApplication, QgsTask
3434
from qgis.utils import OverrideCursor
3535

3636
from .db_plugins.plugin import BaseError
3737
from .db_plugins.postgis.plugin import PGDatabase
3838
from .dlg_db_error import DlgDbError
3939
from .dlg_query_builder import QueryBuilderDlg
40+
from .dlg_cancel_task_query import DlgCancelTaskQuery
4041

4142
try:
4243
from qgis.gui import QgsCodeEditorSQL # NOQA
@@ -59,6 +60,9 @@ def __init__(self, iface, db, parent=None):
5960
self.iface = iface
6061
self.db = db
6162
self.filter = ""
63+
self.modelAsync = None
64+
self.dlg_cancel_task = DlgCancelTaskQuery(self)
65+
self.dlg_cancel_task.canceled.connect(self.executeSqlCanceled)
6266
self.allowMultiColumnPk = isinstance(db, PGDatabase) # at the moment only PostgreSQL allows a primary key to span multiple columns, SpatiaLite doesn't
6367
self.aliasSubQuery = isinstance(db, PGDatabase) # only PostgreSQL requires subqueries to be aliases
6468
self.setupUi(self)
@@ -177,40 +181,53 @@ def clearSql(self):
177181
self.editSql.setFocus()
178182
self.filter = ""
179183

180-
def executeSql(self):
184+
def executeSqlCanceled(self):
185+
self.modelAsync.cancel()
181186

182-
sql = self._getSqlQuery()
183-
if sql == "":
184-
return
185-
186-
with OverrideCursor(Qt.WaitCursor):
187-
# delete the old model
188-
old_model = self.viewResult.model()
189-
self.viewResult.setModel(None)
190-
if old_model:
191-
old_model.deleteLater()
187+
def executeSqlCompleted(self):
188+
self.dlg_cancel_task.hide()
192189

190+
if self.modelAsync.task.status() == QgsTask.Complete:
191+
model = self.modelAsync.model
193192
cols = []
194193
quotedCols = []
195194

196-
try:
197-
# set the new model
198-
model = self.db.sqlResultModel(sql, self)
199-
self.viewResult.setModel(model)
200-
self.lblResult.setText(self.tr("{0} rows, {1:.1f} seconds").format(model.affectedRows(), model.secs()))
201-
cols = self.viewResult.model().columnNames()
202-
for col in cols:
203-
quotedCols.append(self.db.connector.quoteId(col))
204-
205-
except BaseError as e:
206-
DlgDbError.showError(e, self)
207-
self.uniqueModel.clear()
208-
self.geomCombo.clear()
209-
return
195+
self.viewResult.setModel(model)
196+
self.lblResult.setText(self.tr("{0} rows, {1:.1f} seconds").format(model.affectedRows(), model.secs()))
197+
cols = self.viewResult.model().columnNames()
198+
for col in cols:
199+
quotedCols.append(self.db.connector.quoteId(col))
210200

211201
self.setColumnCombos(cols, quotedCols)
212-
213202
self.update()
203+
elif not self.dlg_cancel_task.cancelStatus:
204+
DlgDbError.showError(self.modelAsync.error, self)
205+
self.uniqueModel.clear()
206+
self.geomCombo.clear()
207+
pass
208+
209+
def executeSql(self):
210+
211+
sql = self._getSqlQuery()
212+
if sql == "":
213+
return
214+
215+
# delete the old model
216+
old_model = self.viewResult.model()
217+
self.viewResult.setModel(None)
218+
if old_model:
219+
old_model.deleteLater()
220+
221+
try:
222+
self.modelAsync = self.db.sqlResultModelAsync(sql, self)
223+
self.modelAsync.done.connect(self.executeSqlCompleted)
224+
self.dlg_cancel_task.show()
225+
QgsApplication.taskManager().addTask(self.modelAsync.task)
226+
except Exception as e:
227+
DlgDbError.showError(e, self)
228+
self.uniqueModel.clear()
229+
self.geomCombo.clear()
230+
return
214231

215232
def _getSqlLayer(self, _filter):
216233
hasUniqueField = self.uniqueColumnCheck.checkState() == Qt.Checked
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<ui version="4.0">
3+
<class>DlgCancelTaskQuery</class>
4+
<widget class="QDialog" name="DlgCancelTaskQuery">
5+
<property name="geometry">
6+
<rect>
7+
<x>0</x>
8+
<y>0</y>
9+
<width>178</width>
10+
<height>101</height>
11+
</rect>
12+
</property>
13+
<property name="windowTitle">
14+
<string>Dialog</string>
15+
</property>
16+
<widget class="QWidget" name="verticalLayoutWidget">
17+
<property name="geometry">
18+
<rect>
19+
<x>10</x>
20+
<y>10</y>
21+
<width>168</width>
22+
<height>80</height>
23+
</rect>
24+
</property>
25+
<layout class="QVBoxLayout" name="verticalLayout">
26+
<item>
27+
<layout class="QHBoxLayout" name="horizontalLayout">
28+
<item>
29+
<widget class="QLabel" name="mLabel">
30+
<property name="text">
31+
<string>Executing SQL...</string>
32+
</property>
33+
</widget>
34+
</item>
35+
<item>
36+
<widget class="QLabel" name="mMovie">
37+
<property name="text">
38+
<string/>
39+
</property>
40+
</widget>
41+
</item>
42+
</layout>
43+
</item>
44+
<item>
45+
<layout class="QHBoxLayout" name="horizontalLayout_2">
46+
<item>
47+
<spacer name="horizontalSpacer">
48+
<property name="orientation">
49+
<enum>Qt::Horizontal</enum>
50+
</property>
51+
<property name="sizeHint" stdset="0">
52+
<size>
53+
<width>40</width>
54+
<height>20</height>
55+
</size>
56+
</property>
57+
</spacer>
58+
</item>
59+
<item>
60+
<widget class="QPushButton" name="mCancelButton">
61+
<property name="text">
62+
<string>Cancel</string>
63+
</property>
64+
</widget>
65+
</item>
66+
<item>
67+
<spacer name="horizontalSpacer_2">
68+
<property name="orientation">
69+
<enum>Qt::Horizontal</enum>
70+
</property>
71+
<property name="sizeHint" stdset="0">
72+
<size>
73+
<width>40</width>
74+
<height>20</height>
75+
</size>
76+
</property>
77+
</spacer>
78+
</item>
79+
</layout>
80+
</item>
81+
</layout>
82+
</widget>
83+
</widget>
84+
<resources/>
85+
<connections/>
86+
</ui>

0 commit comments

Comments
 (0)
Please sign in to comment.