123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389 |
- """
- /***************************************************************************
- Name : DB Manager
- Description : Database manager plugin for QGIS
- 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. *
- * *
- ***************************************************************************/
- """
- from qgis.PyQt.QtCore import Qt, QObject, pyqtSignal, QByteArray
- from qgis.PyQt.QtWidgets import (
- QFormLayout,
- QComboBox,
- QCheckBox,
- QDialogButtonBox,
- QPushButton,
- QLabel,
- QApplication,
- QAction,
- QMenu,
- QInputDialog,
- QMessageBox,
- QDialog,
- QWidget
- )
- from qgis.PyQt.QtGui import QKeySequence
- from qgis.core import (
- Qgis,
- QgsApplication,
- QgsSettings,
- QgsMapLayerType,
- QgsWkbTypes,
- QgsProviderConnectionException,
- QgsProviderRegistry,
- QgsVectorLayer,
- QgsRasterLayer,
- QgsProject,
- QgsMessageLog,
- QgsCoordinateReferenceSystem
- )
- from qgis.gui import (
- QgsMessageBarItem,
- QgsProjectionSelectionWidget
- )
- from ..db_plugins import createDbPlugin
- class BaseError(Exception):
- """Base class for exceptions in the plugin."""
- def __init__(self, e):
- if isinstance(e, Exception):
- msg = e.args[0] if len(e.args) > 0 else ''
- else:
- msg = e
- if not isinstance(msg, str):
- msg = str(msg, 'utf-8', 'replace') # convert from utf8 and replace errors (if any)
- self.msg = msg
- Exception.__init__(self, msg)
- def __unicode__(self):
- return self.msg
- class InvalidDataException(BaseError):
- pass
- class ConnectionError(BaseError):
- pass
- class DbError(BaseError):
- def __init__(self, e, query=None):
- BaseError.__init__(self, e)
- self.query = str(query) if query is not None else None
- def __unicode__(self):
- if self.query is None:
- return BaseError.__unicode__(self)
- msg = QApplication.translate("DBManagerPlugin", "Error:\n{0}").format(BaseError.__unicode__(self))
- if self.query:
- msg += QApplication.translate("DBManagerPlugin", "\n\nQuery:\n{0}").format(self.query)
- return msg
- class DBPlugin(QObject):
- deleted = pyqtSignal()
- changed = pyqtSignal()
- aboutToChange = pyqtSignal()
- def __init__(self, conn_name, parent=None):
- QObject.__init__(self, parent)
- self.connName = conn_name
- self.db = None
- def __del__(self):
- pass # print "DBPlugin.__del__", self.connName
- def connectionIcon(self):
- return QgsApplication.getThemeIcon("/mIconDbSchema.svg")
- def connectionName(self):
- return self.connName
- def database(self):
- return self.db
- def info(self):
- from .info_model import DatabaseInfo
- return DatabaseInfo(None)
- def connect(self, parent=None):
- raise NotImplementedError('Needs to be implemented by subclasses')
- def connectToUri(self, uri):
- self.db = self.databasesFactory(self, uri)
- if self.db:
- return True
- return False
- def reconnect(self):
- if self.db is not None:
- uri = self.db.uri()
- self.db.deleteLater()
- self.db = None
- return self.connectToUri(uri)
- return self.connect(self.parent())
- def remove(self):
- # Try the new API first, fallback to legacy
- try:
- md = QgsProviderRegistry.instance().providerMetadata(self.providerName())
- md.deleteConnection(self.connectionName())
- except (AttributeError, QgsProviderConnectionException):
- settings = QgsSettings()
- settings.beginGroup("/%s/%s" % (self.connectionSettingsKey(), self.connectionName()))
- settings.remove("")
- self.deleted.emit()
- return True
- @classmethod
- def addConnection(self, conn_name, uri):
- raise NotImplementedError('Needs to be implemented by subclasses')
- @classmethod
- def icon(self):
- return None
- @classmethod
- def typeName(self):
- # return the db typename (e.g. 'postgis')
- pass
- @classmethod
- def typeNameString(self):
- # return the db typename string (e.g. 'PostGIS')
- pass
- @classmethod
- def providerName(self):
- # return the provider's name (e.g. 'postgres')
- pass
- @classmethod
- def connectionSettingsKey(self):
- # return the key used to store the connections in settings
- pass
- @classmethod
- def connections(self):
- # get the list of connections
- conn_list = []
- # First try with the new core API, if that fails, proceed with legacy code
- try:
- md = QgsProviderRegistry.instance().providerMetadata(self.providerName())
- for name in md.dbConnections(False).keys():
- conn_list.append(createDbPlugin(self.typeName(), name))
- except (AttributeError, QgsProviderConnectionException):
- settings = QgsSettings()
- settings.beginGroup(self.connectionSettingsKey())
- for name in settings.childGroups():
- conn_list.append(createDbPlugin(self.typeName(), name))
- settings.endGroup()
- return conn_list
- def databasesFactory(self, connection, uri):
- return None
- @classmethod
- def addConnectionActionSlot(self, item, action, parent):
- raise NotImplementedError('Needs to be implemented by subclasses')
- def removeActionSlot(self, item, action, parent):
- QApplication.restoreOverrideCursor()
- try:
- res = QMessageBox.question(parent, QApplication.translate("DBManagerPlugin", "DB Manager"),
- QApplication.translate("DBManagerPlugin",
- "Really remove connection to {0}?").format(item.connectionName()),
- QMessageBox.Yes | QMessageBox.No)
- if res != QMessageBox.Yes:
- return
- finally:
- QApplication.setOverrideCursor(Qt.WaitCursor)
- item.remove()
- class DbItemObject(QObject):
- changed = pyqtSignal()
- aboutToChange = pyqtSignal()
- deleted = pyqtSignal()
- def __init__(self, parent=None):
- super().__init__(parent)
- def database(self):
- return None
- def refresh(self):
- self.changed.emit() # refresh the item data reading them from the db
- def info(self):
- pass
- def runAction(self):
- pass
- def registerActions(self, mainWindow):
- pass
- class Database(DbItemObject):
- def __init__(self, dbplugin, uri):
- super().__init__(dbplugin)
- self.connector = self.connectorsFactory(uri)
- def connectorsFactory(self, uri):
- return None
- def __del__(self):
- self.connector = None
- pass # print "Database.__del__", self
- def connection(self):
- return self.parent()
- def dbplugin(self):
- return self.parent()
- def database(self):
- return self
- def uri(self):
- return self.connector.uri()
- def publicUri(self):
- return self.connector.publicUri()
- def delete(self):
- self.aboutToChange.emit()
- ret = self.connection().remove()
- if ret is not False:
- self.deleted.emit()
- return ret
- def info(self):
- from .info_model import DatabaseInfo
- return DatabaseInfo(self)
- def sqlResultModel(self, sql, parent):
- from .data_model import SqlResultModel
- return SqlResultModel(self, sql, parent)
- def sqlResultModelAsync(self, sql, parent):
- from .data_model import SqlResultModelAsync
- return SqlResultModelAsync(self, sql, parent)
- def columnUniqueValuesModel(self, col, table, limit=10):
- l = ""
- if limit is not None:
- l = "LIMIT %d" % limit
- return self.sqlResultModel("SELECT DISTINCT %s FROM %s %s" % (col, table, l), self)
- def uniqueIdFunction(self):
- """Return a SQL function used to generate a unique id for rows of a query"""
- # may be overloaded by derived classes
- return "row_number() over ()"
- def toSqlLayer(self, sql, geomCol, uniqueCol, layerName="QueryLayer", layerType=None, avoidSelectById=False, filter=""):
- if uniqueCol is None:
- if hasattr(self, 'uniqueIdFunction'):
- uniqueFct = self.uniqueIdFunction()
- if uniqueFct is not None:
- q = 1
- while "_subq_%d_" % q in sql:
- q += 1
- sql = "SELECT %s AS _uid_,* FROM (%s\n) AS _subq_%d_" % (uniqueFct, sql, q)
- uniqueCol = "_uid_"
- uri = self.uri()
- uri.setDataSource("", "(%s\n)" % sql, geomCol, filter, uniqueCol)
- if avoidSelectById:
- uri.disableSelectAtId(True)
- provider = self.dbplugin().providerName()
- if layerType == QgsMapLayerType.RasterLayer:
- return QgsRasterLayer(uri.uri(False), layerName, provider)
- return QgsVectorLayer(uri.uri(False), layerName, provider)
- def registerAllActions(self, mainWindow):
- self.registerDatabaseActions(mainWindow)
- self.registerSubPluginActions(mainWindow)
- def registerSubPluginActions(self, mainWindow):
- # load plugins!
- try:
- exec("from .%s.plugins import load" % self.dbplugin().typeName(), globals())
- except ImportError:
- pass
- else:
- load(self, mainWindow) # NOQA
- def registerDatabaseActions(self, mainWindow):
- action = QAction(QApplication.translate("DBManagerPlugin", "&Re-connect"), self)
- mainWindow.registerAction(action, QApplication.translate("DBManagerPlugin", "&Database"),
- self.reconnectActionSlot)
- if self.schemas() is not None:
- action = QAction(QApplication.translate("DBManagerPlugin", "&Create Schema…"), self)
- mainWindow.registerAction(action, QApplication.translate("DBManagerPlugin", "&Schema"),
- self.createSchemaActionSlot)
- action = QAction(QApplication.translate("DBManagerPlugin", "&Delete (Empty) Schema"), self)
- mainWindow.registerAction(action, QApplication.translate("DBManagerPlugin", "&Schema"),
- self.deleteSchemaActionSlot)
- action = QAction(QApplication.translate("DBManagerPlugin", "Delete Selected Item"), self)
- mainWindow.registerAction(action, None, self.deleteActionSlot)
- action.setShortcuts(QKeySequence.Delete)
- action = QAction(QgsApplication.getThemeIcon("/mActionCreateTable.svg"),
- QApplication.translate("DBManagerPlugin", "&Create Table…"), self)
- mainWindow.registerAction(action, QApplication.translate("DBManagerPlugin", "&Table"),
- self.createTableActionSlot)
- action = QAction(QgsApplication.getThemeIcon("/mActionEditTable.svg"),
- QApplication.translate("DBManagerPlugin", "&Edit Table…"), self)
- mainWindow.registerAction(action, QApplication.translate("DBManagerPlugin", "&Table"), self.editTableActionSlot)
- action = QAction(QgsApplication.getThemeIcon("/mActionDeleteTable.svg"),
- QApplication.translate("DBManagerPlugin", "&Delete Table/View…"), self)
- mainWindow.registerAction(action, QApplication.translate("DBManagerPlugin", "&Table"),
- self.deleteTableActionSlot)
- action = QAction(QApplication.translate("DBManagerPlugin", "&Empty Table…"), self)
- mainWindow.registerAction(action, QApplication.translate("DBManagerPlugin", "&Table"),
- self.emptyTableActionSlot)
- if self.schemas() is not None:
- action = QAction(QApplication.translate("DBManagerPlugin", "&Move to Schema"), self)
- action.setMenu(QMenu(mainWindow))
- def invoke_callback():
- return mainWindow.invokeCallback(self.prepareMenuMoveTableToSchemaActionSlot)
- action.menu().aboutToShow.connect(invoke_callback)
- mainWindow.registerAction(action, QApplication.translate("DBManagerPlugin", "&Table"))
- def reconnectActionSlot(self, item, action, parent):
- db = item.database()
- db.connection().reconnect()
- db.refresh()
- def deleteActionSlot(self, item, action, parent):
- if isinstance(item, Schema):
- self.deleteSchemaActionSlot(item, action, parent)
- elif isinstance(item, Table):
- self.deleteTableActionSlot(item, action, parent)
- else:
- QApplication.restoreOverrideCursor()
- parent.infoBar.pushMessage(QApplication.translate("DBManagerPlugin", "Cannot delete the selected item."),
- Qgis.Info, parent.iface.messageTimeout())
- QApplication.setOverrideCursor(Qt.WaitCursor)
- def createSchemaActionSlot(self, item, action, parent):
- QApplication.restoreOverrideCursor()
- try:
- if not isinstance(item, (DBPlugin, Schema, Table)) or item.database() is None:
- parent.infoBar.pushMessage(
- QApplication.translate("DBManagerPlugin", "No database selected or you are not connected to it."),
- Qgis.Info, parent.iface.messageTimeout())
- return
- (schema, ok) = QInputDialog.getText(parent, QApplication.translate("DBManagerPlugin", "New schema"),
- QApplication.translate("DBManagerPlugin", "Enter new schema name"))
- if not ok:
- return
- finally:
- QApplication.setOverrideCursor(Qt.WaitCursor)
- self.createSchema(schema)
- def deleteSchemaActionSlot(self, item, action, parent):
- QApplication.restoreOverrideCursor()
- try:
- if not isinstance(item, Schema):
- parent.infoBar.pushMessage(
- QApplication.translate("DBManagerPlugin", "Select an empty schema for deletion."),
- Qgis.Info, parent.iface.messageTimeout())
- return
- res = QMessageBox.question(parent, QApplication.translate("DBManagerPlugin", "DB Manager"),
- QApplication.translate("DBManagerPlugin",
- "Really delete schema {0}?").format(item.name),
- QMessageBox.Yes | QMessageBox.No)
- if res != QMessageBox.Yes:
- return
- finally:
- QApplication.setOverrideCursor(Qt.WaitCursor)
- item.delete()
- def schemasFactory(self, row, db):
- return None
- def schemas(self):
- schemas = self.connector.getSchemas()
- if schemas is not None:
- schemas = [self.schemasFactory(x, self) for x in schemas]
- return schemas
- def createSchema(self, name):
- self.connector.createSchema(name)
- self.refresh()
- def createTableActionSlot(self, item, action, parent):
- QApplication.restoreOverrideCursor()
- if not hasattr(item, 'database') or item.database() is None:
- parent.infoBar.pushMessage(
- QApplication.translate("DBManagerPlugin", "No database selected or you are not connected to it."),
- Qgis.Info, parent.iface.messageTimeout())
- return
- from ..dlg_create_table import DlgCreateTable
- DlgCreateTable(item, parent).exec_()
- QApplication.setOverrideCursor(Qt.WaitCursor)
- def editTableActionSlot(self, item, action, parent):
- QApplication.restoreOverrideCursor()
- try:
- if not isinstance(item, Table) or item.isView:
- parent.infoBar.pushMessage(QApplication.translate("DBManagerPlugin", "Select a table to edit."),
- Qgis.Info, parent.iface.messageTimeout())
- return
- if isinstance(item, RasterTable):
- parent.infoBar.pushMessage(QApplication.translate("DBManagerPlugin", "Editing of raster tables is not supported."),
- Qgis.Info, parent.iface.messageTimeout())
- return
- from ..dlg_table_properties import DlgTableProperties
- DlgTableProperties(item, parent).exec_()
- finally:
- QApplication.setOverrideCursor(Qt.WaitCursor)
- def deleteTableActionSlot(self, item, action, parent):
- QApplication.restoreOverrideCursor()
- try:
- if not isinstance(item, Table):
- parent.infoBar.pushMessage(
- QApplication.translate("DBManagerPlugin", "Select a table/view for deletion."),
- Qgis.Info, parent.iface.messageTimeout())
- return
- res = QMessageBox.question(parent, QApplication.translate("DBManagerPlugin", "DB Manager"),
- QApplication.translate("DBManagerPlugin",
- "Really delete table/view {0}?").format(item.name),
- QMessageBox.Yes | QMessageBox.No)
- if res != QMessageBox.Yes:
- return
- finally:
- QApplication.setOverrideCursor(Qt.WaitCursor)
- item.delete()
- def emptyTableActionSlot(self, item, action, parent):
- QApplication.restoreOverrideCursor()
- try:
- if not isinstance(item, Table) or item.isView:
- parent.infoBar.pushMessage(QApplication.translate("DBManagerPlugin", "Select a table to empty it."),
- Qgis.Info, parent.iface.messageTimeout())
- return
- res = QMessageBox.question(parent, QApplication.translate("DBManagerPlugin", "DB Manager"),
- QApplication.translate("DBManagerPlugin",
- "Really delete all items from table {0}?").format(item.name),
- QMessageBox.Yes | QMessageBox.No)
- if res != QMessageBox.Yes:
- return
- finally:
- QApplication.setOverrideCursor(Qt.WaitCursor)
- item.empty()
- def prepareMenuMoveTableToSchemaActionSlot(self, item, menu, mainWindow):
- """ populate menu with schemas """
- def slot(x):
- return lambda: mainWindow.invokeCallback(self.moveTableToSchemaActionSlot, x)
- menu.clear()
- for schema in self.schemas():
- menu.addAction(schema.name, slot(schema))
- def moveTableToSchemaActionSlot(self, item, action, parent, new_schema):
- QApplication.restoreOverrideCursor()
- try:
- if not isinstance(item, Table):
- parent.infoBar.pushMessage(QApplication.translate("DBManagerPlugin", "Select a table/view."),
- Qgis.Info, parent.iface.messageTimeout())
- return
- finally:
- QApplication.setOverrideCursor(Qt.WaitCursor)
- item.moveToSchema(new_schema)
- def tablesFactory(self, row, db, schema=None):
- typ, row = row[0], row[1:]
- if typ == Table.VectorType:
- return self.vectorTablesFactory(row, db, schema)
- elif typ == Table.RasterType:
- return self.rasterTablesFactory(row, db, schema)
- return self.dataTablesFactory(row, db, schema)
- def dataTablesFactory(self, row, db, schema=None):
- return None
- def vectorTablesFactory(self, row, db, schema=None):
- return None
- def rasterTablesFactory(self, row, db, schema=None):
- return None
- def tables(self, schema=None, sys_tables=False):
- tables = self.connector.getTables(schema.name if schema else None, sys_tables)
- if tables is not None:
- ret = [
- self.tablesFactory(t, self, schema)
- for t in tables
- ]
- return ret
- def createTable(self, table, fields, schema=None):
- field_defs = [x.definition() for x in fields]
- pkeys = [x for x in fields if x.primaryKey]
- pk_name = pkeys[0].name if len(pkeys) > 0 else None
- ret = self.connector.createTable((schema, table), field_defs, pk_name)
- if ret is not False:
- # Add comments if any, because definition does not include
- # the comment
- for f in fields:
- if f.comment:
- self.connector.updateTableColumn(
- (schema, table), f.name, comment=f.comment
- )
- self.refresh()
- return ret
- def createVectorTable(self, table, fields, geom, schema=None):
- ret = self.createTable(table, fields, schema)
- if not ret:
- return False
- try:
- createGeomCol = geom is not None
- if createGeomCol:
- geomCol, geomType, geomSrid, geomDim = geom[:4]
- createSpatialIndex = geom[4] if len(geom) > 4 else False
- self.connector.addGeometryColumn((schema, table), geomCol, geomType, geomSrid, geomDim)
- if createSpatialIndex:
- # commit data definition changes, otherwise index can't be built
- self.connector._commit()
- self.connector.createSpatialIndex((schema, table), geomCol)
- finally:
- self.refresh()
- return True
- def explicitSpatialIndex(self):
- return False
- def spatialIndexClause(self, src_table, src_column, dest_table, dest_table_column):
- return None
- def hasLowercaseFieldNamesOption(self):
- return False
- class Schema(DbItemObject):
- def __init__(self, db):
- DbItemObject.__init__(self, db)
- self.oid = self.name = self.owner = self.perms = None
- self.comment = None
- self.tableCount = 0
- def __del__(self):
- pass # print "Schema.__del__", self
- def database(self):
- return self.parent()
- def schema(self):
- return self
- def tables(self):
- return self.database().tables(self)
- def delete(self):
- self.aboutToChange.emit()
- ret = self.database().connector.deleteSchema(self.name)
- if ret is not False:
- self.deleted.emit()
- return ret
- def rename(self, new_name):
- self.aboutToChange.emit()
- ret = self.database().connector.renameSchema(self.name, new_name)
- if ret is not False:
- self.name = new_name
- # FIXME: refresh triggers
- self.refresh()
- return ret
- def info(self):
- from .info_model import SchemaInfo
- return SchemaInfo(self)
- class Table(DbItemObject):
- TableType, VectorType, RasterType = list(range(3))
- def __init__(self, db, schema=None, parent=None):
- DbItemObject.__init__(self, db)
- self._schema = schema
- if hasattr(self, 'type'):
- return
- self.type = Table.TableType
- self.name = self.isView = self.owner = self.pages = None
- self.comment = None
- self.rowCount = None
- self._fields = self._indexes = self._constraints = self._triggers = self._rules = None
- def __del__(self):
- pass # print "Table.__del__", self
- def canBeAddedToCanvas(self):
- return True
- def database(self):
- return self.parent()
- def schema(self):
- return self._schema
- def schemaName(self):
- return self.schema().name if self.schema() else None
- def quotedName(self):
- return self.database().connector.quoteId((self.schemaName(), self.name))
- def delete(self):
- self.aboutToChange.emit()
- if self.isView:
- ret = self.database().connector.deleteView((self.schemaName(), self.name))
- else:
- ret = self.database().connector.deleteTable((self.schemaName(), self.name))
- if ret is not False:
- self.deleted.emit()
- return ret
- def rename(self, new_name):
- self.aboutToChange.emit()
- ret = self.database().connector.renameTable((self.schemaName(), self.name), new_name)
- if ret is not False:
- self.name = new_name
- self._triggers = None
- self._rules = None
- self._constraints = None
- self.refresh()
- return ret
- def empty(self):
- self.aboutToChange.emit()
- ret = self.database().connector.emptyTable((self.schemaName(), self.name))
- if ret is not False:
- self.refreshRowCount()
- return ret
- def moveToSchema(self, schema):
- self.aboutToChange.emit()
- if self.schema() == schema:
- return True
- ret = self.database().connector.moveTableToSchema((self.schemaName(), self.name), schema.name)
- if ret is not False:
- self.schema().refresh()
- schema.refresh()
- return ret
- def info(self):
- from .info_model import TableInfo
- return TableInfo(self)
- def uri(self):
- uri = self.database().uri()
- schema = self.schemaName() if self.schemaName() else ''
- geomCol = self.geomColumn if self.type in [Table.VectorType, Table.RasterType] else ""
- uniqueCol = self.getValidQgisUniqueFields(True) if self.isView else None
- uri.setDataSource(schema, self.name, geomCol if geomCol else None, None, uniqueCol.name if uniqueCol else "")
- return uri
- def crs(self):
- """Returns the CRS of this table or an invalid CRS if this is not a spatial table
- This should be overwritten by any additional db plugins"""
- return QgsCoordinateReferenceSystem()
- def mimeUri(self):
- layerType = "raster" if self.type == Table.RasterType else "vector"
- return "%s:%s:%s:%s" % (layerType, self.database().dbplugin().providerName(), self.name, self.uri().uri(False))
- def toMapLayer(self, geometryType=None, crs=None):
- provider = self.database().dbplugin().providerName()
- dataSourceUri = self.uri()
- if geometryType:
- dataSourceUri.setWkbType(QgsWkbTypes.parseType(geometryType))
- if crs:
- dataSourceUri.setSrid(str(crs.postgisSrid()))
- uri = dataSourceUri.uri(False)
- if self.type == Table.RasterType:
- return QgsRasterLayer(uri, self.name, provider)
- return QgsVectorLayer(uri, self.name, provider)
- def geometryType(self):
- pass
- def getValidQgisUniqueFields(self, onlyOne=False):
- """ list of fields valid to load the table as layer in QGIS canvas.
- QGIS automatically search for a valid unique field, so it's
- needed only for queries and views """
- ret = []
- # add the pk
- pkcols = [x for x in self.fields() if x.primaryKey]
- if len(pkcols) == 1:
- ret.append(pkcols[0])
- # then add both oid, serial and int fields with an unique index
- indexes = self.indexes()
- if indexes is not None:
- for idx in indexes:
- if idx.isUnique and len(idx.columns) == 1:
- fld = idx.fields()[idx.columns[0]]
- if fld.dataType in ["oid", "serial", "int4", "int8"] and fld not in ret:
- ret.append(fld)
- # and finally append the other suitable fields
- for fld in self.fields():
- if fld.dataType in ["oid", "serial", "int4", "int8"] and fld not in ret:
- ret.append(fld)
- if onlyOne:
- return ret[0] if len(ret) > 0 else None
- return ret
- def tableDataModel(self, parent):
- pass
- def tableFieldsFactory(self, row, table):
- raise NotImplementedError('Needs to be implemented by subclasses')
- def fields(self):
- if self._fields is None:
- fields = self.database().connector.getTableFields((self.schemaName(), self.name))
- if fields is not None:
- self._fields = [self.tableFieldsFactory(x, self) for x in fields]
- return self._fields
- def refreshFields(self):
- self._fields = None # refresh table fields
- self.refresh()
- def addField(self, fld):
- self.aboutToChange.emit()
- ret = self.database().connector.addTableColumn((self.schemaName(), self.name), fld.definition())
- if ret is not False:
- self.refreshFields()
- return ret
- def deleteField(self, fld):
- self.aboutToChange.emit()
- ret = self.database().connector.deleteTableColumn((self.schemaName(), self.name), fld.name)
- if ret is not False:
- self.refreshFields()
- self.refreshConstraints()
- self.refreshIndexes()
- return ret
- def addGeometryColumn(self, geomCol, geomType, srid, dim, createSpatialIndex=False):
- self.aboutToChange.emit()
- ret = self.database().connector.addGeometryColumn((self.schemaName(), self.name), geomCol, geomType, srid, dim)
- if not ret:
- return False
- try:
- if createSpatialIndex:
- # commit data definition changes, otherwise index can't be built
- self.database().connector._commit()
- self.database().connector.createSpatialIndex((self.schemaName(), self.name), geomCol)
- finally:
- self.schema().refresh() if self.schema() else self.database().refresh() # another table was added
- return True
- def tableConstraintsFactory(self):
- return None
- def constraints(self):
- if self._constraints is None:
- constraints = self.database().connector.getTableConstraints((self.schemaName(), self.name))
- if constraints is not None:
- self._constraints = [self.tableConstraintsFactory(x, self) for x in constraints]
- return self._constraints
- def refreshConstraints(self):
- self._constraints = None # refresh table constraints
- self.refresh()
- def addConstraint(self, constr):
- self.aboutToChange.emit()
- if constr.type == TableConstraint.TypePrimaryKey:
- ret = self.database().connector.addTablePrimaryKey((self.schemaName(), self.name),
- constr.fields()[constr.columns[0]].name)
- elif constr.type == TableConstraint.TypeUnique:
- ret = self.database().connector.addTableUniqueConstraint((self.schemaName(), self.name),
- constr.fields()[constr.columns[0]].name)
- else:
- return False
- if ret is not False:
- self.refreshConstraints()
- return ret
- def deleteConstraint(self, constr):
- self.aboutToChange.emit()
- ret = self.database().connector.deleteTableConstraint((self.schemaName(), self.name), constr.name)
- if ret is not False:
- self.refreshConstraints()
- return ret
- def tableIndexesFactory(self):
- return None
- def indexes(self):
- if self._indexes is None:
- indexes = self.database().connector.getTableIndexes((self.schemaName(), self.name))
- if indexes is not None:
- self._indexes = [self.tableIndexesFactory(x, self) for x in indexes]
- return self._indexes
- def refreshIndexes(self):
- self._indexes = None # refresh table indexes
- self.refresh()
- def addIndex(self, idx):
- self.aboutToChange.emit()
- ret = self.database().connector.createTableIndex((self.schemaName(), self.name), idx.name,
- idx.fields()[idx.columns[0]].name)
- if ret is not False:
- self.refreshIndexes()
- return ret
- def deleteIndex(self, idx):
- self.aboutToChange.emit()
- ret = self.database().connector.deleteTableIndex((self.schemaName(), self.name), idx.name)
- if ret is not False:
- self.refreshIndexes()
- return ret
- def tableTriggersFactory(self, row, table):
- return None
- def triggers(self):
- if self._triggers is None:
- triggers = self.database().connector.getTableTriggers((self.schemaName(), self.name))
- if triggers is not None:
- self._triggers = [self.tableTriggersFactory(x, self) for x in triggers]
- return self._triggers
- def refreshTriggers(self):
- self._triggers = None # refresh table triggers
- self.refresh()
- def tableRulesFactory(self, row, table):
- return None
- def rules(self):
- if self._rules is None:
- rules = self.database().connector.getTableRules((self.schemaName(), self.name))
- if rules is not None:
- self._rules = [self.tableRulesFactory(x, self) for x in rules]
- return self._rules
- def refreshRules(self):
- self._rules = None # refresh table rules
- self.refresh()
- def refreshRowCount(self):
- self.aboutToChange.emit()
- prevRowCount = self.rowCount
- try:
- self.rowCount = self.database().connector.getTableRowCount((self.schemaName(), self.name))
- self.rowCount = int(self.rowCount) if self.rowCount is not None else None
- except DbError:
- self.rowCount = None
- if self.rowCount != prevRowCount:
- self.refresh()
- def runAction(self, action):
- action = str(action)
- if action.startswith("rows/"):
- if action == "rows/count":
- self.refreshRowCount()
- return True
- elif action.startswith("triggers/"):
- parts = action.split('/')
- trigger_action = parts[1]
- msg = QApplication.translate("DBManagerPlugin", "Do you want to {0} all triggers?").format(trigger_action)
- QApplication.restoreOverrideCursor()
- try:
- if QMessageBox.question(None, QApplication.translate("DBManagerPlugin", "Table triggers"), msg,
- QMessageBox.Yes | QMessageBox.No) == QMessageBox.No:
- return False
- finally:
- QApplication.setOverrideCursor(Qt.WaitCursor)
- if trigger_action == "enable" or trigger_action == "disable":
- enable = trigger_action == "enable"
- self.aboutToChange.emit()
- self.database().connector.enableAllTableTriggers(enable, (self.schemaName(), self.name))
- self.refreshTriggers()
- return True
- elif action.startswith("trigger/"):
- parts = action.split('/')
- trigger_name = parts[1]
- trigger_action = parts[2]
- msg = QApplication.translate("DBManagerPlugin", "Do you want to {0} trigger {1}?").format(
- trigger_action, trigger_name)
- QApplication.restoreOverrideCursor()
- try:
- if QMessageBox.question(None, QApplication.translate("DBManagerPlugin", "Table trigger"), msg,
- QMessageBox.Yes | QMessageBox.No) == QMessageBox.No:
- return False
- finally:
- QApplication.setOverrideCursor(Qt.WaitCursor)
- if trigger_action == "delete":
- self.aboutToChange.emit()
- self.database().connector.deleteTableTrigger(trigger_name, (self.schemaName(), self.name))
- self.refreshTriggers()
- return True
- elif trigger_action == "enable" or trigger_action == "disable":
- enable = trigger_action == "enable"
- self.aboutToChange.emit()
- self.database().connector.enableTableTrigger(trigger_name, enable, (self.schemaName(), self.name))
- self.refreshTriggers()
- return True
- return False
- def addExtraContextMenuEntries(self, menu):
- """Called whenever a context menu is shown for this table. Can be used to add additional actions to the menu."""
- pass
- class VectorTable(Table):
- def __init__(self, db, schema=None, parent=None):
- if not hasattr(self, 'type'): # check if the superclass constructor was called yet!
- Table.__init__(self, db, schema, parent)
- self.type = Table.VectorType
- self.geomColumn = self.geomType = self.geomDim = self.srid = None
- self.estimatedExtent = self.extent = None
- def info(self):
- from .info_model import VectorTableInfo
- return VectorTableInfo(self)
- def uri(self):
- uri = super().uri()
- for f in self.fields():
- if f.primaryKey:
- uri.setKeyColumn(f.name)
- break
- uri.setWkbType(QgsWkbTypes.parseType(self.geomType))
- return uri
- def hasSpatialIndex(self, geom_column=None):
- geom_column = geom_column if geom_column is not None else self.geomColumn
- fld = None
- for fld in self.fields():
- if fld.name == geom_column:
- break
- if fld is None:
- return False
- for idx in self.indexes():
- if fld.num in idx.columns:
- return True
- return False
- def createSpatialIndex(self, geom_column=None):
- self.aboutToChange.emit()
- geom_column = geom_column if geom_column is not None else self.geomColumn
- ret = self.database().connector.createSpatialIndex((self.schemaName(), self.name), geom_column)
- if ret is not False:
- self.refreshIndexes()
- return ret
- def deleteSpatialIndex(self, geom_column=None):
- self.aboutToChange.emit()
- geom_column = geom_column if geom_column is not None else self.geomColumn
- ret = self.database().connector.deleteSpatialIndex((self.schemaName(), self.name), geom_column)
- if ret is not False:
- self.refreshIndexes()
- return ret
- def refreshTableExtent(self):
- prevExtent = self.extent
- try:
- self.extent = self.database().connector.getTableExtent((self.schemaName(), self.name), self.geomColumn)
- except DbError:
- self.extent = None
- if self.extent != prevExtent:
- self.refresh()
- def refreshTableEstimatedExtent(self):
- prevEstimatedExtent = self.estimatedExtent
- try:
- self.estimatedExtent = self.database().connector.getTableEstimatedExtent((self.schemaName(), self.name),
- self.geomColumn)
- except DbError:
- self.estimatedExtent = None
- if self.estimatedExtent != prevEstimatedExtent:
- self.refresh()
- def runAction(self, action):
- action = str(action)
- if action.startswith("spatialindex/"):
- parts = action.split('/')
- spatialIndex_action = parts[1]
- msg = QApplication.translate("DBManagerPlugin", "Do you want to {0} spatial index for field {1}?").format(
- spatialIndex_action, self.geomColumn)
- QApplication.restoreOverrideCursor()
- try:
- if QMessageBox.question(None, QApplication.translate("DBManagerPlugin", "Spatial Index"), msg,
- QMessageBox.Yes | QMessageBox.No) == QMessageBox.No:
- return False
- finally:
- QApplication.setOverrideCursor(Qt.WaitCursor)
- if spatialIndex_action == "create":
- self.createSpatialIndex()
- return True
- elif spatialIndex_action == "delete":
- self.deleteSpatialIndex()
- return True
- if action.startswith("extent/"):
- if action == "extent/get":
- self.refreshTableExtent()
- return True
- if action == "extent/estimated/get":
- self.refreshTableEstimatedExtent()
- return True
- return Table.runAction(self, action)
- def addLayer(self, geometryType=None, crs=None):
- layer = self.toMapLayer(geometryType, crs)
- layers = QgsProject.instance().addMapLayers([layer])
- if len(layers) != 1:
- QgsMessageLog.logMessage(self.tr("{layer} is an invalid layer - not loaded").format(layer=layer.publicSource()))
- msgLabel = QLabel(self.tr("{layer} is an invalid layer and cannot be loaded. Please check the <a href=\"#messageLog\">message log</a> for further info.").format(layer=layer.publicSource()), self.mainWindow.infoBar)
- msgLabel.setWordWrap(True)
- msgLabel.linkActivated.connect(self.mainWindow.iface.mainWindow().findChild(QWidget, "MessageLog").show)
- msgLabel.linkActivated.connect(self.mainWindow.iface.mainWindow().raise_)
- self.mainWindow.infoBar.pushItem(QgsMessageBarItem(msgLabel, Qgis.Warning))
- def showAdvancedVectorDialog(self):
- dlg = QDialog()
- dlg.setObjectName('dbManagerAdvancedVectorDialog')
- settings = QgsSettings()
- dlg.restoreGeometry(settings.value("/DB_Manager/advancedAddDialog/geometry", QByteArray(), type=QByteArray))
- layout = QFormLayout()
- dlg.setLayout(layout)
- dlg.setWindowTitle(self.tr('Add Layer {}').format(self.name))
- geometryTypeComboBox = QComboBox()
- geometryTypeComboBox.addItem(self.tr('Point'), 'POINT')
- geometryTypeComboBox.addItem(self.tr('Line'), 'LINESTRING')
- geometryTypeComboBox.addItem(self.tr('Polygon'), 'POLYGON')
- layout.addRow(self.tr('Geometry Type'), geometryTypeComboBox)
- zCheckBox = QCheckBox(self.tr('With Z'))
- mCheckBox = QCheckBox(self.tr('With M'))
- layout.addRow(zCheckBox)
- layout.addRow(mCheckBox)
- crsSelector = QgsProjectionSelectionWidget()
- crsSelector.setCrs(self.crs())
- layout.addRow(self.tr('CRS'), crsSelector)
- def selectedGeometryType():
- geomType = geometryTypeComboBox.currentData()
- if zCheckBox.isChecked():
- geomType += 'Z'
- if mCheckBox.isChecked():
- geomType += 'M'
- return geomType
- def selectedCrs():
- return crsSelector.crs()
- addButton = QPushButton(self.tr('Load Layer'))
- addButton.clicked.connect(lambda: self.addLayer(selectedGeometryType(), selectedCrs()))
- btns = QDialogButtonBox(QDialogButtonBox.Cancel)
- btns.addButton(addButton, QDialogButtonBox.ActionRole)
- layout.addRow(btns)
- addButton.clicked.connect(dlg.accept)
- btns.accepted.connect(dlg.accept)
- btns.rejected.connect(dlg.reject)
- dlg.exec_()
- settings = QgsSettings()
- settings.setValue("/DB_Manager/advancedAddDialog/geometry", dlg.saveGeometry())
- def addExtraContextMenuEntries(self, menu):
- """Called whenever a context menu is shown for this table. Can be used to add additional actions to the menu."""
- if self.geomType == 'GEOMETRY':
- menu.addAction(QApplication.translate("DBManagerPlugin", "Add Layer (Advanced)…"), self.showAdvancedVectorDialog)
- class RasterTable(Table):
- def __init__(self, db, schema=None, parent=None):
- if not hasattr(self, 'type'): # check if the superclass constructor was called yet!
- Table.__init__(self, db, schema, parent)
- self.type = Table.RasterType
- self.geomColumn = self.geomType = self.pixelSizeX = self.pixelSizeY = self.pixelType = self.isExternal = self.srid = None
- self.extent = None
- def info(self):
- from .info_model import RasterTableInfo
- return RasterTableInfo(self)
- class TableSubItemObject(QObject):
- def __init__(self, table):
- QObject.__init__(self, table)
- def table(self):
- return self.parent()
- def database(self):
- return self.table().database() if self.table() else None
- class TableField(TableSubItemObject):
- def __init__(self, table):
- TableSubItemObject.__init__(self, table)
- self.num = self.name = self.dataType = self.modifier = self.notNull = self.default = self.hasDefault = self.primaryKey = None
- self.comment = None
- def type2String(self):
- if self.modifier is None or self.modifier == -1:
- return "%s" % self.dataType
- return "%s (%s)" % (self.dataType, self.modifier)
- def default2String(self):
- if not self.hasDefault:
- return ''
- return self.default if self.default is not None else "NULL"
- def definition(self):
- from .connector import DBConnector
- quoteIdFunc = self.database().connector.quoteId if self.database() else DBConnector.quoteId
- name = quoteIdFunc(self.name)
- not_null = "NOT NULL" if self.notNull else ""
- txt = "%s %s %s" % (name, self.type2String(), not_null)
- if self.hasDefault:
- txt += " DEFAULT %s" % self.default2String()
- return txt
- def getComment(self):
- """Returns the comment for a field"""
- return ''
- def delete(self):
- return self.table().deleteField(self)
- def rename(self, new_name):
- return self.update(new_name)
- def update(self, new_name, new_type_str=None, new_not_null=None, new_default_str=None, new_comment=None):
- self.table().aboutToChange.emit()
- if self.name == new_name:
- new_name = None
- if self.type2String() == new_type_str:
- new_type_str = None
- if self.notNull == new_not_null:
- new_not_null = None
- if self.default2String() == new_default_str:
- new_default_str = None
- if self.comment == new_comment:
- new_comment = None
- ret = self.table().database().connector.updateTableColumn((self.table().schemaName(), self.table().name),
- self.name, new_name, new_type_str,
- new_not_null, new_default_str, new_comment)
- if ret is not False:
- self.table().refreshFields()
- return ret
- class TableConstraint(TableSubItemObject):
- """ class that represents a constraint of a table (relation) """
- TypeCheck, TypeForeignKey, TypePrimaryKey, TypeUnique, TypeExclusion, TypeUnknown = list(range(6))
- types = {"c": TypeCheck, "f": TypeForeignKey, "p": TypePrimaryKey, "u": TypeUnique, "x": TypeExclusion}
- onAction = {"a": "NO ACTION", "r": "RESTRICT", "c": "CASCADE", "n": "SET NULL", "d": "SET DEFAULT"}
- matchTypes = {"u": "UNSPECIFIED", "f": "FULL", "p": "PARTIAL", "s": "SIMPLE"}
- def __init__(self, table):
- TableSubItemObject.__init__(self, table)
- self.name = self.type = self.columns = None
- def type2String(self):
- if self.type == TableConstraint.TypeCheck:
- return QApplication.translate("DBManagerPlugin", "Check")
- if self.type == TableConstraint.TypePrimaryKey:
- return QApplication.translate("DBManagerPlugin", "Primary key")
- if self.type == TableConstraint.TypeForeignKey:
- return QApplication.translate("DBManagerPlugin", "Foreign key")
- if self.type == TableConstraint.TypeUnique:
- return QApplication.translate("DBManagerPlugin", "Unique")
- if self.type == TableConstraint.TypeExclusion:
- return QApplication.translate("DBManagerPlugin", "Exclusion")
- return QApplication.translate("DBManagerPlugin", 'Unknown')
- def fields(self):
- def fieldFromNum(num, fields):
- """ return field specified by its number or None if doesn't exist """
- for fld in fields:
- if fld.num == num:
- return fld
- return None
- fields = self.table().fields()
- cols = {}
- for num in self.columns:
- cols[num] = fieldFromNum(num, fields)
- return cols
- def delete(self):
- return self.table().deleteConstraint(self)
- class TableIndex(TableSubItemObject):
- def __init__(self, table):
- TableSubItemObject.__init__(self, table)
- self.name = self.columns = self.isUnique = None
- def fields(self):
- def fieldFromNum(num, fields):
- """ return field specified by its number or None if doesn't exist """
- for fld in fields:
- if fld.num == num:
- return fld
- return None
- fields = self.table().fields()
- cols = {}
- for num in self.columns:
- cols[num] = fieldFromNum(num, fields)
- return cols
- def delete(self):
- return self.table().deleteIndex(self)
- class TableTrigger(TableSubItemObject):
- """ class that represents a trigger """
- # Bits within tgtype (pg_trigger.h)
- TypeRow = (1 << 0) # row or statement
- TypeBefore = (1 << 1) # before or after
- # events: one or more
- TypeInsert = (1 << 2)
- TypeDelete = (1 << 3)
- TypeUpdate = (1 << 4)
- TypeTruncate = (1 << 5)
- def __init__(self, table):
- TableSubItemObject.__init__(self, table)
- self.name = self.function = None
- def type2String(self):
- trig_type = ''
- trig_type += "Before " if self.type & TableTrigger.TypeBefore else "After "
- if self.type & TableTrigger.TypeInsert:
- trig_type += "INSERT "
- if self.type & TableTrigger.TypeUpdate:
- trig_type += "UPDATE "
- if self.type & TableTrigger.TypeDelete:
- trig_type += "DELETE "
- if self.type & TableTrigger.TypeTruncate:
- trig_type += "TRUNCATE "
- trig_type += "\n"
- trig_type += "for each "
- trig_type += "row" if self.type & TableTrigger.TypeRow else "statement"
- return trig_type
- class TableRule(TableSubItemObject):
- def __init__(self, table):
- TableSubItemObject.__init__(self, table)
- self.name = self.definition = None
|