2012-12-10 00:12:07 +01:00

356 lines
11 KiB
Python

# -*- coding: utf-8 -*-
"""
/***************************************************************************
Name : DB Manager
Description : Database manager plugin for QuantumGIS
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
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from ..plugin import ConnectionError, DBPlugin, Database, Schema, Table, VectorTable, RasterTable, TableField, TableConstraint, TableIndex, TableTrigger, TableRule
try:
from . import resources_rc
except ImportError:
pass
from ..html_elems import HtmlParagraph, HtmlList, HtmlTable
def classFactory():
return PostGisDBPlugin
class PostGisDBPlugin(DBPlugin):
@classmethod
def icon(self):
return QIcon(":/db_manager/postgis/icon")
@classmethod
def typeName(self):
return 'postgis'
@classmethod
def typeNameString(self):
return 'PostGIS'
@classmethod
def providerName(self):
return 'postgres'
@classmethod
def connectionSettingsKey(self):
return '/PostgreSQL/connections'
def databasesFactory(self, connection, uri):
return PGDatabase(connection, uri)
def connect(self, parent=None):
conn_name = self.connectionName()
settings = QSettings()
settings.beginGroup( u"/%s/%s" % (self.connectionSettingsKey(), conn_name) )
if not settings.contains( "database" ): # non-existent entry?
raise InvalidDataException( u'there is no defined database connection "%s".' % conn_name )
from qgis.core import QgsDataSourceURI
uri = QgsDataSourceURI()
settingsList = ["service", "host", "port", "database", "username", "password"]
service, host, port, database, username, password = map(lambda x: settings.value(x).toString(), settingsList)
# qgis1.5 use 'savePassword' instead of 'save' setting
savedPassword = settings.value("save", False).toBool() or settings.value("savePassword", False).toBool()
useEstimatedMetadata = settings.value("estimatedMetadata", False).toBool()
sslmode = settings.value("sslmode", QgsDataSourceURI.SSLprefer).toInt()[0]
settings.endGroup()
if not service.isEmpty():
uri.setConnection(service, database, username, password, sslmode)
else:
uri.setConnection(host, port, database, username, password, sslmode)
uri.setUseEstimatedMetadata(useEstimatedMetadata)
err = QString()
try:
return self.connectToUri(uri)
except ConnectionError, e:
err = QString( str(e) )
hasCredentialDlg = True
try:
from qgis.gui import QgsCredentials
except ImportError: # no credential dialog
hasCredentialDlg = False
# ask for valid credentials
max_attempts = 3
for i in range(max_attempts):
if hasCredentialDlg:
(ok, username, password) = QgsCredentials.instance().get(uri.connectionInfo(), username, password, err)
else:
(password, ok) = QInputDialog.getText(parent, u"Enter password", u'Enter password for connection "%s":' % conn_name, QLineEdit.Password)
if not ok:
return False
if not service.isEmpty():
uri.setConnection(service, database, username, password, sslmode)
else:
uri.setConnection(host, port, database, username, password, sslmode)
try:
self.connectToUri(uri)
except ConnectionError, e:
if i == max_attempts-1: # failed the last attempt
raise e
err = QString( str(e) )
continue
if hasCredentialDlg:
QgsCredentials.instance().put(uri.connectionInfo(), username, password)
return True
return False
class PGDatabase(Database):
def __init__(self, connection, uri):
Database.__init__(self, connection, uri)
def connectorsFactory(self, uri):
return PostGisDBConnector(uri)
def dataTablesFactory(self, row, db, schema=None):
return PGTable(row, db, schema)
def vectorTablesFactory(self, row, db, schema=None):
return PGVectorTable(row, db, schema)
def rasterTablesFactory(self, row, db, schema=None):
return PGRasterTable(row, db, schema)
def schemasFactory(self, row, db):
return PGSchema(row, db)
def sqlResultModel(self, sql, parent):
from .data_model import PGSqlResultModel
return PGSqlResultModel(self, sql, parent)
def registerDatabaseActions(self, mainWindow):
Database.registerDatabaseActions(self, mainWindow)
# add a separator
separator = QAction(self);
separator.setSeparator(True)
mainWindow.registerAction( separator, "&Table" )
action = QAction("Run &Vacuum Analyze", self)
mainWindow.registerAction( action, "&Table", self.runVacuumAnalyzeActionSlot )
def runVacuumAnalyzeActionSlot(self, item, action, parent):
QApplication.restoreOverrideCursor()
try:
if not isinstance(item, Table) or item.isView:
QMessageBox.information(parent, "Sorry", "Select a TABLE for vacuum analyze.")
return
finally:
QApplication.setOverrideCursor(Qt.WaitCursor)
item.runVacuumAnalyze()
class PGSchema(Schema):
def __init__(self, row, db):
Schema.__init__(self, db)
self.oid, self.name, self.owner, self.perms, self.comment = row
class PGTable(Table):
def __init__(self, row, db, schema=None):
Table.__init__(self, db, schema)
self.name, schema_name, self.isView, self.owner, self.estimatedRowCount, self.pages, self.comment = row
self.estimatedRowCount = int(self.estimatedRowCount)
def runVacuumAnalyze(self):
self.aboutToChange()
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, "Table rule", msg, QMessageBox.Yes|QMessageBox.No) == QMessageBox.No:
return False
finally:
QApplication.setOverrideCursor(Qt.WaitCursor)
if rule_action == "delete":
self.aboutToChange()
self.database().connector.deleteTableRule(rule_name, (self.schemaName(), self.name))
self.refreshRules()
return True
return Table.runAction(self, action)
def tableFieldsFactory(self, row, table):
return PGTableField(row, table)
def tableConstraintsFactory(self, row, table):
return PGTableConstraint(row, table)
def tableIndexesFactory(self, row, table):
return PGTableIndex(row, table)
def tableTriggersFactory(self, row, table):
return PGTableTrigger(row, table)
def tableRulesFactory(self, row, table):
return PGTableRule(row, table)
def info(self):
from .info_model import PGTableInfo
return PGTableInfo(self)
def tableDataModel(self, parent):
from .data_model import PGTableDataModel
return PGTableDataModel(self, parent)
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:]
def info(self):
from .info_model import PGVectorTableInfo
return PGVectorTableInfo(self)
def runAction(self, action):
if PGTable.runAction(self, action):
return True
return VectorTable.runAction(self, action)
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'
def info(self):
from .info_model import PGRasterTableInfo
return PGRasterTableInfo(self)
def gdalUri(self):
uri = self.database().uri()
schema = ( u'schema=%s' % self.schemaName() ) if self.schemaName() else ''
gdalUri = u'PG: dbname=%s host=%s user=%s password=%s port=%s mode=2 %s table=%s' % (uri.database(), uri.host(), uri.username(), uri.password(), uri.port(), schema, self.name)
return QString( gdalUri )
def mimeUri(self):
uri = u"raster:gdal:%s:%s" % (self.name, self.gdalUri())
return QString( uri )
def toMapLayer(self):
from qgis.core import QgsRasterLayer
rl = QgsRasterLayer(self.gdalUri(), self.name)
if rl.isValid():
rl.setContrastEnhancementAlgorithm("StretchToMinimumMaximum")
return rl
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 = QString(typeStr).trimmed()
regex = QRegExp( "\((.+)\)$" )
startpos = regex.indexIn( trimmedTypeStr )
if startpos >= 0:
self.modifier = regex.cap(1).trimmed()
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
class PGTableConstraint(TableConstraint):
def __init__(self, row, table):
TableConstraint.__init__(self, table)
self.name, constr_type, self.isDefferable, self.isDeffered, columns = row[:5]
self.columns = map(int, columns.split(' '))
self.type = TableConstraint.types[constr_type] # convert to enum
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]
class PGTableIndex(TableIndex):
def __init__(self, row, table):
TableIndex.__init__(self, table)
self.name, columns, self.isUnique = row
self.columns = map(int, columns.split(' '))
class PGTableTrigger(TableTrigger):
def __init__(self, row, table):
TableTrigger.__init__(self, table)
self.name, self.function, self.type, self.enabled = row
class PGTableRule(TableRule):
def __init__(self, row, table):
TableRule.__init__(self, table)
self.name, self.definition = row