Skip to content

Commit

Permalink
Merge pull request #2382 from SebDieBln/MultiColumnPK_DbManagerQuery
Browse files Browse the repository at this point in the history
[DB_Manager] allows to specify multiple columns as the primary for a query to PostGIS (follow a4124da)
  • Loading branch information
brushtyler committed Oct 22, 2015
2 parents 54e2a51 + 8e45da9 commit ef8f3d3
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 19 deletions.
115 changes: 99 additions & 16 deletions python/plugins/db_manager/dlg_sql_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@

from PyQt4.QtCore import Qt, QObject, QSettings, QByteArray, SIGNAL, pyqtSignal
from PyQt4.QtGui import QDialog, QWidget, QAction, QKeySequence, \
QDialogButtonBox, QApplication, QCursor, QMessageBox, QClipboard, QInputDialog, QIcon
QDialogButtonBox, QApplication, QCursor, QMessageBox, QClipboard, QInputDialog, QIcon, QStyledItemDelegate, QStandardItemModel, QStandardItem
from PyQt4.Qsci import QsciAPIs

from qgis.core import QgsProject

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

Expand All @@ -53,6 +54,7 @@ def __init__(self, iface, db, parent=None):
QWidget.__init__(self, parent)
self.iface = iface
self.db = db
self.allowMultiColumnPk = isinstance(db, PGDatabase) # at the moment only PostGIS allows a primary key to span multiple columns, spatialite doesn't
self.setupUi(self)
self.setWindowTitle(
u"%s - %s [%s]" % (self.windowTitle(), db.connection().connectionName(), db.connection().typeNameString()))
Expand Down Expand Up @@ -80,6 +82,16 @@ def __init__(self, iface, db, parent=None):

self.updatePresetsCombobox()

self.uniqueCombo.setItemDelegate(QStyledItemDelegate())
self.uniqueModel = QStandardItemModel(self.uniqueCombo)
self.uniqueCombo.setModel(self.uniqueModel)
if self.allowMultiColumnPk:
self.uniqueCombo.setEditable(True)
self.uniqueCombo.lineEdit().setReadOnly(True)
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
self.uniqueChanged

# hide the load query as layer if feature is not supported
self._loadAsLayerAvailable = self.db.connector.hasCustomQuerySupport()
self.loadAsLayerGroup.setVisible(self._loadAsLayerAvailable)
Expand Down Expand Up @@ -161,31 +173,43 @@ def executeSql(self):
if old_model:
old_model.deleteLater()

self.uniqueCombo.clear()
self.geomCombo.clear()
cols = []
quotedCols = []

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()))
cols = self.viewResult.model().columnNames()
for col in cols:
quotedCols.append(self.db.connector.quoteId(col))

except BaseError as e:
QApplication.restoreOverrideCursor()
DlgDbError.showError(e, self)
self.uniqueModel.clear()
self.geomCombo.clear()
return

cols = sorted(self.viewResult.model().columnNames())
self.uniqueCombo.addItems(cols)
self.geomCombo.addItems(cols)
self.setColumnCombos(cols, quotedCols)

self.update()
QApplication.restoreOverrideCursor()

def loadSqlLayer(self):
hasUniqueField = self.uniqueColumnCheck.checkState() == Qt.Checked
if hasUniqueField:
uniqueFieldName = self.uniqueCombo.currentText()
if self.allowMultiColumnPk:
checkedCols = []
for item in self.uniqueModel.findItems("*", Qt.MatchWildcard):
if item.checkState() == Qt.Checked:
checkedCols.append(item.data())
uniqueFieldName = ",".join(checkedCols)
elif self.uniqueCombo.currentIndex() >= 0:
uniqueFieldName = self.uniqueModel.item(self.uniqueCombo.currentIndex()).data()
else:
uniqueFieldName = None
else:
uniqueFieldName = None
hasGeomCol = self.hasGeometryCol.checkState() == Qt.Checked
Expand Down Expand Up @@ -236,8 +260,6 @@ def fillColumnCombos(self):
return

QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
self.uniqueCombo.clear()
self.geomCombo.clear()

# get a new alias
aliasIndex = 0
Expand All @@ -254,24 +276,34 @@ def fillColumnCombos(self):

# get all the columns
cols = []
quotedCols = []
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)
for col in cols:
quotedCols.append(connector.quoteId(col))

except BaseError as e:
QApplication.restoreOverrideCursor()
DlgDbError.showError(e, self)
self.uniqueModel.clear()
self.geomCombo.clear()
return

finally:
if c:
c.close()
del c

self.setColumnCombos(cols, quotedCols)

QApplication.restoreOverrideCursor()

def setColumnCombos(self, cols, quotedCols):
# get sensible default columns. do this before sorting in case there's hints in the column order (eg, id is more likely to be first)
try:
defaultGeomCol = next(col for col in cols if col in ['geom', 'geometry', 'the_geom', 'way'])
Expand All @@ -282,22 +314,59 @@ def fillColumnCombos(self):
except:
defaultUniqueCol = None

cols.sort()
self.uniqueCombo.addItems(cols)
colNames = zip(cols, quotedCols)
colNames.sort()
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
# set sensible default columns if the columns are not already set
try:
self.geomCombo.setCurrentIndex(cols.index(defaultGeomCol))
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)
try:
self.uniqueCombo.setCurrentIndex(cols.index(defaultUniqueCol))
pass
except:
pass

QApplication.restoreOverrideCursor()

def copySelectedResults(self):
if len(self.viewResult.selectedIndexes()) <= 0:
return
Expand Down Expand Up @@ -353,3 +422,17 @@ def _getSqlQuery(self):
if len(sql) == 0:
sql = self.editSql.text()
return 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.
checkedItems = []
for item in self.uniqueModel.findItems("*", Qt.MatchWildcard):
if item.checkState() == Qt.Checked:
checkedItems.append(item.text())
label = ", ".join(checkedItems)
if text != label:
self.uniqueCombo.setEditText(label)
15 changes: 12 additions & 3 deletions python/plugins/db_manager/ui/DlgSqlWindow.ui
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@
<item>
<widget class="QCheckBox" name="uniqueColumnCheck">
<property name="text">
<string>Column with unique
integer values</string>
<string>Column(s) with
unique values</string>
</property>
</widget>
</item>
Expand Down Expand Up @@ -144,7 +144,16 @@ columns</string>
<item>
<widget class="QWidget" name="layerTypeWidget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="margin">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
Expand Down

0 comments on commit ef8f3d3

Please sign in to comment.