443 lines
15 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. *
* *
***************************************************************************/
"""
2016-09-21 18:24:26 +02:00
from builtins import str
from builtins import map
from builtins import range
2012-04-16 13:19:40 +02:00
# this will disable the dbplugin if the connector raise an ImportError
from .connector import PostGisDBConnector
from qgis.PyQt.QtCore import Qt, QRegExp, QCoreApplication
2016-04-22 10:38:48 +02:00
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtWidgets import QAction, QApplication, QMessageBox
from qgis.core import Qgis, QgsApplication, QgsSettings
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
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 QgsApplication.getThemeIcon("/mIconPostgis.svg")
@classmethod
def typeName(self):
return 'postgis'
2012-04-16 13:19:40 +02:00
@classmethod
def typeNameString(self):
return QCoreApplication.translate('db_manager', '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 = QgsSettings()
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?
2017-03-04 16:23:36 +01:00
raise InvalidDataException(self.tr('There is no defined database connection "{0}".').format(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)
try:
sslmode = settings.value("sslmode", QgsDataSourceUri.SslPrefer, type=int)
except TypeError:
sslmode = QgsDataSourceUri.SslPrefer
2012-04-16 13:19:40 +02:00
settings.endGroup()
2012-04-16 13:19:40 +02:00
2016-10-19 10:46:02 +02:00
if hasattr(authcfg, 'isNull') and authcfg.isNull():
authcfg = ''
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 info(self):
from .info_model import PGDatabaseInfo
return PGDatabaseInfo(self)
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 sqlResultModelAsync(self, sql, parent):
from .data_model import PGSqlResultModelAsync
return PGSqlResultModelAsync(self, sql, parent)
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
action = QAction(self.tr("Run &Refresh Materialized View"), self)
mainWindow.registerAction(action, self.tr("&Table"), self.runRefreshMaterializedViewSlot)
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."), Qgis.Info,
parent.iface.messageTimeout())
return
finally:
QApplication.setOverrideCursor(Qt.WaitCursor)
item.runVacuumAnalyze()
2012-04-16 13:19:40 +02:00
def runRefreshMaterializedViewSlot(self, item, action, parent):
QApplication.restoreOverrideCursor()
try:
if not isinstance(item, PGTable) or item._relationType != 'm':
parent.infoBar.pushMessage(self.tr("Select a materialized view for refresh."), Qgis.Info,
parent.iface.messageTimeout())
return
finally:
QApplication.setOverrideCursor(Qt.WaitCursor)
item.runRefreshMaterializedView()
def hasLowercaseFieldNamesOption(self):
return True
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 runRefreshMaterializedView(self):
self.aboutToChange.emit()
self.database().connector.runRefreshMaterializedView((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):
2016-09-21 18:24:26 +02:00
action = str(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]
2017-03-04 16:23:36 +01:00
msg = self.tr(u"Do you want to {0} rule {1}?").format(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
elif action.startswith("refreshmaterializedview/"):
if action == "refreshmaterializedview/run":
self.runRefreshMaterializedView()
return True
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()
service = (u'service=\'%s\'' % uri.service()) if uri.service() else ''
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 ''
if not dbname:
# GDAL postgisraster driver *requires* ad dbname
# See: https://trac.osgeo.org/gdal/ticket/6910
# TODO: cache this ?
connector = self.database().connector
r = connector._execute(None, "SELECT current_database()")
dbname = (u'dbname=\'%s\'' % connector._fetchone(r)[0])
connector._close_cursor(r)
# 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 %s mode=2 %s %s table=\'%s\'' % \
2017-05-30 10:33:02 +02:00
(service, 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]
2016-09-21 18:24:26 +02:00
self.columns = list(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
2016-09-21 18:24:26 +02:00
self.columns = list(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