2012-04-16 13:19:40 +02:00
# -*- coding: utf-8 -*-
"""
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Name : DB Manager
2013-06-09 18:28:52 +02:00
Description : Database manager plugin for QGIS
2012-04-16 13:19:40 +02:00
Date : May 23 , 2011
copyright : ( C ) 2011 by Giuseppe Sucameli
email : brush . tyler @gmail.com
2012-12-10 00:12:07 +01:00
The content of this file is based on
2012-04-16 13:19:40 +02:00
- 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 . *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
"""
2015-08-17 18:10:59 +10:00
from PyQt4 . QtCore import Qt , QObject , QSettings , QByteArray , SIGNAL , pyqtSignal
from PyQt4 . QtGui import QDialog , QWidget , QAction , QKeySequence , \
2015-10-17 16:15:21 +02:00
QDialogButtonBox , QApplication , QCursor , QMessageBox , QClipboard , QInputDialog , QIcon , QStyledItemDelegate , QStandardItemModel , QStandardItem
fix python pep8 warnings and fix some revealed errors
pep8 --ignore=E111,E128,E201,E202,E203,E211,E221,E222,E225,E226,E227,E231,E241,E261,E265,E272,E302,E303,E501,E701 \
--exclude="ui_*.py,debian/*,python/ext-libs/*" \
.
2015-02-01 14:15:42 +01:00
from PyQt4 . Qsci import QsciAPIs
2015-02-02 18:45:27 +01:00
fix python pep8 warnings and fix some revealed errors
pep8 --ignore=E111,E128,E201,E202,E203,E211,E221,E222,E225,E226,E227,E231,E241,E261,E265,E272,E302,E303,E501,E701 \
--exclude="ui_*.py,debian/*,python/ext-libs/*" \
.
2015-02-01 14:15:42 +01:00
from qgis . core import QgsProject
2012-04-16 13:19:40 +02:00
from . db_plugins . plugin import BaseError
from . dlg_db_error import DlgDbError
2015-03-30 15:31:19 +02:00
from . dlg_query_builder import QueryBuilderDlg
2012-04-16 13:19:40 +02:00
2015-02-02 18:45:27 +01:00
try :
from qgis . gui import QgsCodeEditorSQL
except :
from . sqledit import SqlEdit
from qgis import gui
2015-03-21 10:41:58 +10:00
gui . QgsCodeEditorSQL = SqlEdit
2015-02-02 18:45:27 +01:00
2012-12-09 17:47:26 +01:00
from . ui . ui_DlgSqlWindow import Ui_DbManagerDlgSqlWindow as Ui_Dialog
2012-04-16 13:19:40 +02:00
2013-06-15 22:44:11 +02:00
import re
2012-04-16 13:19:40 +02:00
2015-08-17 18:10:59 +10:00
class DlgSqlWindow ( QWidget , Ui_Dialog ) :
nameChanged = pyqtSignal ( str )
2015-08-22 14:29:41 +02:00
2015-03-21 10:41:58 +10:00
def __init__ ( self , iface , db , parent = None ) :
2015-08-17 18:10:59 +10:00
QWidget . __init__ ( self , parent )
2015-03-21 10:41:58 +10:00
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 '
self . editSql . setFocus ( )
self . editSql . setVerticalScrollBarPolicy ( Qt . ScrollBarAsNeeded )
self . initCompleter ( )
# allow to copy results
copyAction = QAction ( " copy " , self )
self . viewResult . addAction ( copyAction )
copyAction . setShortcuts ( QKeySequence . Copy )
2015-08-17 18:10:59 +10:00
copyAction . triggered . connect ( self . copySelectedResults )
self . btnExecute . clicked . connect ( self . executeSql )
self . btnClear . clicked . connect ( self . clearSql )
self . presetStore . clicked . connect ( self . storePreset )
self . presetDelete . clicked . connect ( self . deletePreset )
self . presetCombo . activated [ str ] . connect ( self . loadPreset )
self . presetCombo . activated [ str ] . connect ( self . presetName . setText )
2015-03-21 10:41:58 +10:00
self . updatePresetsCombobox ( )
2015-10-17 16:15:21 +02:00
self . uniqueCombo . setItemDelegate ( QStyledItemDelegate ( ) )
self . uniqueModel = QStandardItemModel ( self . uniqueCombo )
self . uniqueCombo . setModel ( self . uniqueModel )
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
2015-03-21 10:41:58 +10:00
# 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
2015-08-17 18:10:59 +10:00
self . loadLayerBtn . clicked . connect ( self . loadSqlLayer )
self . getColumnsBtn . clicked . connect ( self . fillColumnCombos )
self . loadAsLayerGroup . toggled . connect ( self . loadAsLayerToggled )
2015-03-21 10:41:58 +10:00
self . loadAsLayerToggled ( False )
2015-04-01 17:49:29 +02:00
self . _createViewAvailable = self . db . connector . hasCreateSpatialViewSupport ( )
2015-08-22 14:29:41 +02:00
self . btnCreateView . setVisible ( self . _createViewAvailable )
2015-04-01 17:49:29 +02:00
if self . _createViewAvailable :
2015-08-17 18:10:59 +10:00
self . btnCreateView . clicked . connect ( self . createView )
2015-04-01 17:49:29 +02:00
2015-03-30 15:31:19 +02:00
self . queryBuilderFirst = True
2015-06-02 18:48:28 +02:00
self . queryBuilderBtn . setIcon ( QIcon ( " :/db_manager/icons/sql.gif " ) )
2015-08-17 18:10:59 +10:00
self . queryBuilderBtn . clicked . connect ( self . displayQueryBuilder )
self . presetName . textChanged . connect ( self . nameChanged )
2015-03-30 15:31:19 +02:00
2015-03-21 10:41:58 +10:00
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 ) :
2015-04-21 15:08:12 +02:00
query = self . _getSqlQuery ( )
2015-08-22 14:29:41 +02:00
if query == " " :
return
2015-03-21 10:41:58 +10:00
name = self . presetName . text ( )
2015-08-16 20:57:24 +02:00
QgsProject . instance ( ) . writeEntry ( ' DBManager ' , ' savedQueries/q ' + unicode ( name . __hash__ ( ) ) + ' /name ' , name )
QgsProject . instance ( ) . writeEntry ( ' DBManager ' , ' savedQueries/q ' + unicode ( name . __hash__ ( ) ) + ' /query ' , query )
2015-03-21 10:41:58 +10:00
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 ( )
2015-08-16 20:57:24 +02:00
QgsProject . instance ( ) . removeEntry ( ' DBManager ' , ' savedQueries/q ' + unicode ( name . __hash__ ( ) ) )
2015-03-21 10:41:58 +10:00
self . presetCombo . removeItem ( self . presetCombo . findText ( name ) )
self . presetCombo . setCurrentIndex ( - 1 )
def loadPreset ( self , name ) :
2015-08-16 20:57:24 +02:00
query = QgsProject . instance ( ) . readEntry ( ' DBManager ' , ' savedQueries/q ' + unicode ( name . __hash__ ( ) ) + ' /query ' ) [ 0 ]
name = QgsProject . instance ( ) . readEntry ( ' DBManager ' , ' savedQueries/q ' + unicode ( name . __hash__ ( ) ) + ' /name ' ) [ 0 ]
2015-03-21 10:41:58 +10:00
self . editSql . setText ( query )
def loadAsLayerToggled ( self , checked ) :
self . loadAsLayerGroup . setChecked ( checked )
self . loadAsLayerWidget . setVisible ( checked )
def clearSql ( self ) :
self . editSql . clear ( )
self . editSql . setFocus ( )
def executeSql ( self ) :
2015-04-21 15:08:12 +02:00
sql = self . _getSqlQuery ( )
2015-08-22 14:29:41 +02:00
if sql == " " :
return
2015-03-21 10:41:58 +10:00
QApplication . setOverrideCursor ( QCursor ( Qt . WaitCursor ) )
# delete the old model
old_model = self . viewResult . model ( )
self . viewResult . setModel ( None )
2015-08-22 14:29:41 +02:00
if old_model :
old_model . deleteLater ( )
2015-03-21 10:41:58 +10:00
2015-10-17 16:15:21 +02:00
cols = [ ]
quotedCols = [ ]
2015-03-21 10:41:58 +10:00
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 ( ) ) )
2015-10-17 16:15:21 +02:00
cols = self . viewResult . model ( ) . columnNames ( )
for col in cols :
quotedCols . append ( self . db . connector . quoteId ( col ) )
2015-03-21 10:41:58 +10:00
2015-08-22 14:29:41 +02:00
except BaseError as e :
2015-03-21 10:41:58 +10:00
QApplication . restoreOverrideCursor ( )
DlgDbError . showError ( e , self )
2015-10-17 16:15:21 +02:00
self . uniqueModel . clear ( )
2015-10-17 02:19:41 +02:00
self . geomCombo . clear ( )
2015-03-21 10:41:58 +10:00
return
2015-10-17 16:15:21 +02:00
self . setColumnCombos ( cols , quotedCols )
2015-03-21 10:41:58 +10:00
self . update ( )
QApplication . restoreOverrideCursor ( )
def loadSqlLayer ( self ) :
2015-03-27 14:32:41 +01:00
hasUniqueField = self . uniqueColumnCheck . checkState ( ) == Qt . Checked
if hasUniqueField :
2015-10-17 16:15:21 +02:00
checkedCols = [ ]
for item in self . uniqueModel . findItems ( " * " , Qt . MatchWildcard ) :
if item . checkState ( ) == Qt . Checked :
checkedCols . append ( item . data ( ) )
uniqueFieldName = " , " . join ( checkedCols )
2015-03-27 14:32:41 +01:00
else :
uniqueFieldName = None
2015-03-26 17:05:53 +01:00
hasGeomCol = self . hasGeometryCol . checkState ( ) == Qt . Checked
if hasGeomCol :
geomFieldName = self . geomCombo . currentText ( )
else :
geomFieldName = None
2015-03-21 10:41:58 +10:00
2015-04-21 15:08:12 +02:00
query = self . _getSqlQuery ( )
2015-08-22 14:29:41 +02:00
if query == " " :
return
2015-03-21 10:41:58 +10:00
# remove a trailing ';' from query if present
if query . strip ( ) . endswith ( ' ; ' ) :
query = query . strip ( ) [ : - 1 ]
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 ) :
2015-04-21 15:08:12 +02:00
query = self . _getSqlQuery ( )
2015-08-22 14:29:41 +02:00
if query == " " :
return
2015-03-21 10:41:58 +10:00
QApplication . setOverrideCursor ( QCursor ( Qt . WaitCursor ) )
# 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
# remove a trailing ';' from query if present
if query . strip ( ) . endswith ( ' ; ' ) :
query = query . strip ( ) [ : - 1 ]
# get all the columns
cols = [ ]
2015-10-17 16:15:21 +02:00
quotedCols = [ ]
2015-03-21 10:41:58 +10:00
connector = self . db . connector
2015-08-22 14:29:41 +02:00
sql = u " SELECT * FROM ( %s \n ) AS %s LIMIT 0 " % ( unicode ( query ) , connector . quoteId ( alias ) )
2015-03-21 10:41:58 +10:00
c = None
try :
c = connector . _execute ( None , sql )
cols = connector . _get_cursor_columns ( c )
2015-10-17 16:15:21 +02:00
for col in cols :
quotedCols . append ( connector . quoteId ( col ) )
2015-03-21 10:41:58 +10:00
except BaseError as e :
QApplication . restoreOverrideCursor ( )
DlgDbError . showError ( e , self )
2015-10-17 16:15:21 +02:00
self . uniqueModel . clear ( )
2015-10-17 02:19:41 +02:00
self . geomCombo . clear ( )
2015-03-21 10:41:58 +10:00
return
finally :
if c :
c . close ( )
del c
2015-10-17 16:15:21 +02:00
self . setColumnCombos ( cols , quotedCols )
2015-10-17 02:19:41 +02:00
QApplication . restoreOverrideCursor ( )
2015-10-17 16:15:21 +02:00
def setColumnCombos ( self , cols , quotedCols ) :
2015-03-21 10:41:58 +10:00
# 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 ' ] )
except :
defaultGeomCol = None
try :
defaultUniqueCol = [ col for col in cols if ' id ' in col ] [ 0 ]
except :
defaultUniqueCol = None
2015-10-17 16:15:21 +02:00
colNames = zip ( cols , quotedCols )
colNames . sort ( )
newItems = [ ]
2015-10-17 16:43:54 +02:00
uniqueIsFilled = False
2015-10-17 16:15:21 +02:00
for ( col , quotedCol ) in colNames :
item = QStandardItem ( col )
item . setData ( quotedCol )
item . setEnabled ( True )
item . setCheckable ( True )
item . setSelectable ( False )
2015-10-17 16:43:54 +02:00
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 )
2015-10-17 16:15:21 +02:00
newItems . append ( item )
self . uniqueModel . clear ( )
self . uniqueModel . appendColumn ( newItems )
self . uniqueChanged ( )
2015-10-17 16:43:54 +02:00
oldGeometryColumn = self . geomCombo . currentText ( )
2015-10-17 02:19:41 +02:00
self . geomCombo . clear ( )
2015-03-21 10:41:58 +10:00
self . geomCombo . addItems ( cols )
2015-10-17 16:43:54 +02:00
self . geomCombo . setCurrentIndex ( self . geomCombo . findText ( oldGeometryColumn , Qt . MatchExactly ) )
2015-03-21 10:41:58 +10:00
2015-10-17 16:43:54 +02:00
# set sensible default columns if the columns are not already set
2015-03-21 10:41:58 +10:00
try :
2015-10-17 16:43:54 +02:00
if self . geomCombo . currentIndex ( ) == - 1 :
self . geomCombo . setCurrentIndex ( cols . index ( defaultGeomCol ) )
2015-03-21 10:41:58 +10:00
except :
pass
try :
2015-10-17 16:15:21 +02:00
items = self . uniqueModel . findItems ( defaultUniqueCol )
2015-10-17 16:43:54 +02:00
if items and not uniqueIsFilled :
2015-10-17 16:15:21 +02:00
items [ 0 ] . setCheckState ( Qt . Checked )
2015-03-21 10:41:58 +10:00
except :
pass
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 name , value in dictionary . iteritems ( ) :
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 )
2015-03-30 15:31:19 +02:00
2015-08-22 14:29:41 +02:00
def displayQueryBuilder ( self ) :
dlg = QueryBuilderDlg ( self . iface , self . db , self , reset = self . queryBuilderFirst )
2015-03-30 15:31:19 +02:00
self . queryBuilderFirst = False
r = dlg . exec_ ( )
if r == QDialog . Accepted :
2015-08-22 14:29:41 +02:00
self . editSql . setText ( dlg . query )
2015-03-30 15:31:19 +02:00
2015-08-22 14:29:41 +02:00
def createView ( self ) :
2015-04-01 17:49:29 +02:00
name , ok = QInputDialog . getText ( None , " View name " , " View name " )
if ok :
try :
2015-08-22 14:29:41 +02:00
self . db . connector . createSpatialView ( name , self . _getSqlQuery ( ) )
2015-04-01 17:49:29 +02:00
except BaseError as e :
DlgDbError . showError ( e , self )
2015-04-21 15:08:12 +02:00
def _getSqlQuery ( self ) :
sql = self . editSql . selectedText ( )
if len ( sql ) == 0 :
sql = self . editSql . text ( )
return sql
2015-10-17 16:15:21 +02:00
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 )