387 lines
13 KiB
Python
Raw Normal View History

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
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
"""
# this will disable the dbplugin if the connector raise an ImportError
from .connector import PostGisDBConnector
2016-04-22 10:38:48 +02:00
from qgis.PyQt.QtCore import QSettings, Qt, QRegExp
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtWidgets import QAction, QApplication, QMessageBox
from qgis.gui import QgsMessageBar
2012-04-16 13:19:40 +02:00
from ..plugin import ConnectionError, InvalidDataException, DBPlugin, Database, Schema, Table, VectorTable, RasterTable, \
TableField, TableConstraint, TableIndex, TableTrigger, TableRule
import re
2012-04-16 13:19:40 +02:00
2016-03-21 04:51:10 +01:00
from . import resources_rc # NOQA
2012-04-16 13:19:40 +02:00
def classFactory():
return PostGisDBPlugin
2012-04-16 13:19:40 +02:00
class PostGisDBPlugin(DBPlugin):
@classmethod
def icon(self):
return QIcon(":/db_manager/postgis/icon")
@classmethod
def typeName(self):
return 'postgis'
2012-04-16 13:19:40 +02:00
@classmethod
def typeNameString(self):
return 'PostGIS'
2012-04-16 13:19:40 +02:00
@classmethod
def providerName(self):
return 'postgres'
2012-04-16 13:19:40 +02:00
@classmethod
def connectionSettingsKey(self):
return '/PostgreSQL/connections'
2012-04-16 13:19:40 +02:00
def databasesFactory(self, connection, uri):
return PGDatabase(connection, uri)
2012-04-16 13:19:40 +02:00
def connect(self, parent=None):
conn_name = self.connectionName()
settings = QSettings()
settings.beginGroup(u"/%s/%s" % (self.connectionSettingsKey(), conn_name))
2012-04-16 13:19:40 +02:00
if not settings.contains("database"): # non-existent entry?
raise InvalidDataException(self.tr('There is no defined database connection "%s".') % conn_name)
2012-04-16 13:19:40 +02:00
from qgis.core import QgsDataSourceURI
2012-04-16 13:19:40 +02:00
uri = QgsDataSourceURI()
2012-04-16 13:19:40 +02:00
settingsList = ["service", "host", "port", "database", "username", "password", "authcfg"]
2016-03-21 04:51:10 +01:00
service, host, port, database, username, password, authcfg = [settings.value(x, "", type=str) for x in settingsList]
2012-04-16 13:19:40 +02:00
useEstimatedMetadata = settings.value("estimatedMetadata", False, type=bool)
sslmode = settings.value("sslmode", QgsDataSourceURI.SSLprefer, type=int)
2012-04-16 13:19:40 +02:00
settings.endGroup()
2012-04-16 13:19:40 +02:00
if service:
uri.setConnection(service, database, username, password, sslmode, authcfg)
else:
uri.setConnection(host, port, database, username, password, sslmode, authcfg)
2012-04-16 13:19:40 +02:00
uri.setUseEstimatedMetadata(useEstimatedMetadata)
2012-12-10 00:12:07 +01:00
try:
return self.connectToUri(uri)
2016-03-21 04:51:10 +01:00
except ConnectionError:
return False
2012-04-16 13:19:40 +02:00
class PGDatabase(Database):
def __init__(self, connection, uri):
Database.__init__(self, connection, uri)
2012-04-16 13:19:40 +02:00
def connectorsFactory(self, uri):
return PostGisDBConnector(uri)
2012-04-16 13:19:40 +02:00
def dataTablesFactory(self, row, db, schema=None):
return PGTable(row, db, schema)
2012-04-16 13:19:40 +02:00
def vectorTablesFactory(self, row, db, schema=None):
return PGVectorTable(row, db, schema)
2012-04-16 13:19:40 +02:00
def rasterTablesFactory(self, row, db, schema=None):
return PGRasterTable(row, db, schema)
2012-04-16 13:19:40 +02:00
def schemasFactory(self, row, db):
return PGSchema(row, db)
2012-04-16 13:19:40 +02:00
def sqlResultModel(self, sql, parent):
from .data_model import PGSqlResultModel
2012-04-16 13:19:40 +02:00
return PGSqlResultModel(self, sql, parent)
2012-04-16 13:19:40 +02:00
def registerDatabaseActions(self, mainWindow):
Database.registerDatabaseActions(self, mainWindow)
2012-04-16 13:19:40 +02:00
# add a separator
separator = QAction(self)
separator.setSeparator(True)
mainWindow.registerAction(separator, self.tr("&Table"))
2012-04-16 13:19:40 +02:00
action = QAction(self.tr("Run &Vacuum Analyze"), self)
mainWindow.registerAction(action, self.tr("&Table"), self.runVacuumAnalyzeActionSlot)
2012-04-16 13:19:40 +02:00
def runVacuumAnalyzeActionSlot(self, item, action, parent):
QApplication.restoreOverrideCursor()
try:
if not isinstance(item, Table) or item.isView:
parent.infoBar.pushMessage(self.tr("Select a table for vacuum analyze."), QgsMessageBar.INFO,
parent.iface.messageTimeout())
return
finally:
QApplication.setOverrideCursor(Qt.WaitCursor)
item.runVacuumAnalyze()
2012-04-16 13:19:40 +02:00
class PGSchema(Schema):
def __init__(self, row, db):
Schema.__init__(self, db)
self.oid, self.name, self.owner, self.perms, self.comment = row
2012-04-16 13:19:40 +02:00
class PGTable(Table):
def __init__(self, row, db, schema=None):
Table.__init__(self, db, schema)
self.name, schema_name, self._relationType, self.owner, self.estimatedRowCount, self.pages, self.comment = row
self.isView = self._relationType in set(['v', 'm'])
self.estimatedRowCount = int(self.estimatedRowCount)
def runVacuumAnalyze(self):
2016-03-21 04:51:10 +01:00
self.aboutToChange.emit()
self.database().connector.runVacuumAnalyze((self.schemaName(), self.name))
# TODO: change only this item, not re-create all the tables in the schema/database
self.schema().refresh() if self.schema() else self.database().refresh()
def runAction(self, action):
action = unicode(action)
if action.startswith("vacuumanalyze/"):
if action == "vacuumanalyze/run":
self.runVacuumAnalyze()
return True
elif action.startswith("rule/"):
parts = action.split('/')
rule_name = parts[1]
rule_action = parts[2]
msg = u"Do you want to %s rule %s?" % (rule_action, rule_name)
QApplication.restoreOverrideCursor()
try:
if QMessageBox.question(None, self.tr("Table rule"), msg,
QMessageBox.Yes | QMessageBox.No) == QMessageBox.No:
return False
finally:
QApplication.setOverrideCursor(Qt.WaitCursor)
if rule_action == "delete":
2016-03-21 04:51:10 +01:00
self.aboutToChange.emit()
self.database().connector.deleteTableRule(rule_name, (self.schemaName(), self.name))
self.refreshRules()
return True
2012-04-16 13:19:40 +02:00
return Table.runAction(self, action)
2012-04-16 13:19:40 +02:00
def tableFieldsFactory(self, row, table):
return PGTableField(row, table)
2012-04-16 13:19:40 +02:00
def tableConstraintsFactory(self, row, table):
return PGTableConstraint(row, table)
2012-04-16 13:19:40 +02:00
def tableIndexesFactory(self, row, table):
return PGTableIndex(row, table)
2013-06-09 18:28:52 +02:00
def tableTriggersFactory(self, row, table):
return PGTableTrigger(row, table)
2013-06-09 18:28:52 +02:00
def tableRulesFactory(self, row, table):
return PGTableRule(row, table)
2012-04-16 13:19:40 +02:00
def info(self):
from .info_model import PGTableInfo
2012-04-16 13:19:40 +02:00
return PGTableInfo(self)
2012-04-16 13:19:40 +02:00
def tableDataModel(self, parent):
from .data_model import PGTableDataModel
2012-04-16 13:19:40 +02:00
return PGTableDataModel(self, parent)
2012-04-16 13:19:40 +02:00
def delete(self):
2016-03-21 04:51:10 +01:00
self.aboutToChange.emit()
if self.isView:
ret = self.database().connector.deleteView((self.schemaName(), self.name), self._relationType == 'm')
else:
ret = self.database().connector.deleteTable((self.schemaName(), self.name))
2016-03-21 04:51:10 +01:00
if not ret:
self.deleted.emit()
return ret
2012-04-16 13:19:40 +02:00
class PGVectorTable(PGTable, VectorTable):
def __init__(self, row, db, schema=None):
PGTable.__init__(self, row[:-4], db, schema)
VectorTable.__init__(self, db, schema)
self.geomColumn, self.geomType, self.geomDim, self.srid = row[-4:]
2012-04-16 13:19:40 +02:00
def info(self):
from .info_model import PGVectorTableInfo
2012-04-16 13:19:40 +02:00
return PGVectorTableInfo(self)
2012-04-16 13:19:40 +02:00
def runAction(self, action):
if PGTable.runAction(self, action):
return True
return VectorTable.runAction(self, action)
2012-04-16 13:19:40 +02:00
class PGRasterTable(PGTable, RasterTable):
def __init__(self, row, db, schema=None):
PGTable.__init__(self, row[:-6], db, schema)
RasterTable.__init__(self, db, schema)
self.geomColumn, self.pixelType, self.pixelSizeX, self.pixelSizeY, self.isExternal, self.srid = row[-6:]
self.geomType = 'RASTER'
2012-04-16 13:19:40 +02:00
def info(self):
from .info_model import PGRasterTableInfo
2012-04-16 13:19:40 +02:00
return PGRasterTableInfo(self)
def gdalUri(self, uri=None):
if not uri:
uri = self.database().uri()
schema = (u'schema=%s' % self.schemaName()) if self.schemaName() else ''
dbname = (u'dbname=%s' % uri.database()) if uri.database() else ''
host = (u'host=%s' % uri.host()) if uri.host() else ''
user = (u'user=%s' % uri.username()) if uri.username() else ''
passw = (u'password=%s' % uri.password()) if uri.password() else ''
port = (u'port=%s' % uri.port()) if uri.port() else ''
# Find first raster field
col = ''
for fld in self.fields():
if fld.dataType == "raster":
col = u'column=%s' % fld.name
break
gdalUri = u'PG: %s %s %s %s %s mode=2 %s %s table=%s' % \
(dbname, host, user, passw, port, schema, col, self.name)
return gdalUri
def mimeUri(self):
# QGIS has no provider for PGRasters, let's use GDAL
uri = u"raster:gdal:%s:%s" % (self.name, re.sub(":", "\:", self.gdalUri()))
return uri
def toMapLayer(self):
from qgis.core import QgsRasterLayer, QgsContrastEnhancement, QgsDataSourceURI, QgsCredentials
rl = QgsRasterLayer(self.gdalUri(), self.name)
if not rl.isValid():
err = rl.error().summary()
uri = QgsDataSourceURI(self.database().uri())
conninfo = uri.connectionInfo(False)
username = uri.username()
password = uri.password()
for i in range(3):
(ok, username, password) = QgsCredentials.instance().get(conninfo, username, password, err)
if ok:
uri.setUsername(username)
uri.setPassword(password)
rl = QgsRasterLayer(self.gdalUri(uri), self.name)
if rl.isValid():
break
if rl.isValid():
rl.setContrastEnhancement(QgsContrastEnhancement.StretchToMinimumMaximum)
return rl
2012-04-16 13:19:40 +02:00
class PGTableField(TableField):
def __init__(self, row, table):
TableField.__init__(self, table)
self.num, self.name, self.dataType, self.charMaxLen, self.modifier, self.notNull, self.hasDefault, self.default, typeStr = row
self.primaryKey = False
# get modifier (e.g. "precision,scale") from formatted type string
trimmedTypeStr = typeStr.strip()
regex = QRegExp("\((.+)\)$")
startpos = regex.indexIn(trimmedTypeStr)
if startpos >= 0:
self.modifier = regex.cap(1).strip()
else:
self.modifier = None
# find out whether fields are part of primary key
for con in self.table().constraints():
if con.type == TableConstraint.TypePrimaryKey and self.num in con.columns:
self.primaryKey = True
break
2012-04-16 13:19:40 +02:00
class PGTableConstraint(TableConstraint):
def __init__(self, row, table):
TableConstraint.__init__(self, table)
self.name, constr_type_str, self.isDefferable, self.isDeffered, columns = row[:5]
self.columns = map(int, columns.split(' '))
if constr_type_str in TableConstraint.types:
self.type = TableConstraint.types[constr_type_str]
else:
self.type = TableConstraint.TypeUnknown
if self.type == TableConstraint.TypeCheck:
self.checkSource = row[5]
elif self.type == TableConstraint.TypeForeignKey:
self.foreignTable = row[6]
self.foreignOnUpdate = TableConstraint.onAction[row[7]]
self.foreignOnDelete = TableConstraint.onAction[row[8]]
self.foreignMatchType = TableConstraint.matchTypes[row[9]]
self.foreignKeys = row[10]
2012-04-16 13:19:40 +02:00
class PGTableIndex(TableIndex):
def __init__(self, row, table):
TableIndex.__init__(self, table)
self.name, columns, self.isUnique = row
self.columns = map(int, columns.split(' '))
2012-04-16 13:19:40 +02:00
class PGTableTrigger(TableTrigger):
def __init__(self, row, table):
TableTrigger.__init__(self, table)
self.name, self.function, self.type, self.enabled = row
2012-04-16 13:19:40 +02:00
class PGTableRule(TableRule):
def __init__(self, row, table):
TableRule.__init__(self, table)
self.name, self.definition = row