# -*- 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. * * * ***************************************************************************/ """ from PyQt4.QtCore import * from PyQt4.QtGui import * from ..db_plugins import createDbPlugin from .html_elems import HtmlParagraph, HtmlTable class BaseError(Exception): """Base class for exceptions in the plugin.""" def __init__(self, e): msg = e if isinstance(e, (str,unicode,QString)) else e.message try: msg = unicode( msg ) except UnicodeDecodeError: msg = unicode( msg, 'utf-8' ) Exception.__init__(self, msg) def __unicode__(self): return self.message def __str__(self): return unicode(self).encode('utf-8') class InvalidDataException(BaseError): pass class ConnectionError(BaseError): pass class DbError(BaseError): def __init__(self, e, query=None): BaseError.__init__(self, e) self.query = unicode( query ) if query != None else None def __unicode__(self): if self.query == None: return BaseError.__unicode__(self) msg = u"Error:\n%s" % BaseError.__unicode__(self) if self.query: msg += u"\n\nQuery:\n%s" % self.query return msg class DBPlugin(QObject): 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 connectionName(self): return self.connName def database(self): return self.db def info(self): from .info_model import DatabaseInfo return DatabaseInfo(None) 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() ) @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 = [] settings = QSettings() 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 class DbItemObject(QObject): def __init__(self, parent=None): QObject.__init__(self, parent) def database(self): return None def refresh(self): self.emit( SIGNAL('changed') ) # refresh the item data reading them from the db def aboutToChange(self): self.emit( SIGNAL('aboutToChange') ) def info(self): pass def runAction(self): pass def registerActions(self, mainWindow): pass class Database(DbItemObject): def __init__(self, dbplugin, uri): DbItemObject.__init__(self, 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 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 toSqlLayer(self, sql, geomCol, uniqueCol, layerName="QueryLayer", layerType=None, avoidSelectById=False): from qgis.core import QgsMapLayer, QgsVectorLayer, QgsRasterLayer uri = self.uri() uri.setDataSource("", u"(%s\n)" % sql, geomCol, QString(), uniqueCol) if avoidSelectById: uri.disableSelectAtId( True ) provider = self.dbplugin().providerName() if layerType == QgsMapLayer.RasterLayer: return QgsRasterLayer(uri.uri(), layerName, provider) return QgsVectorLayer(uri.uri(), layerName, provider) def registerAllActions(self, mainWindow): self.registerDatabaseActions(mainWindow) self.registerSubPluginActions(mainWindow) def registerSubPluginActions(self, mainWindow): # load plugins! try: exec( u"from .%s.plugins import load" % self.dbplugin().typeName() ) except ImportError: pass else: load(self, mainWindow) def registerDatabaseActions(self, mainWindow): action = QAction("&Re-connect", self) mainWindow.registerAction( action, "&Database", self.reconnectActionSlot ) if self.schemas() != None: action = QAction("&Create schema", self) mainWindow.registerAction( action, "&Schema", self.createSchemaActionSlot ) action = QAction("&Delete (empty) schema", self) mainWindow.registerAction( action, "&Schema", self.deleteSchemaActionSlot ) action = QAction("Delete selected item", self) mainWindow.registerAction( action, None, self.deleteActionSlot ) action.setShortcuts(QKeySequence.Delete) action = QAction(QIcon(":/db_manager/actions/create_table"), "&Create table", self) mainWindow.registerAction( action, "&Table", self.createTableActionSlot ) action = QAction(QIcon(":/db_manager/actions/edit_table"), "&Edit table", self) mainWindow.registerAction( action, "&Table", self.editTableActionSlot ) action = QAction(QIcon(":/db_manager/actions/del_table"), "&Delete table/view", self) mainWindow.registerAction( action, "&Table", self.deleteTableActionSlot ) action = QAction("&Empty table", self) mainWindow.registerAction( action, "&Table", self.emptyTableActionSlot ) if self.schemas() != None: action = QAction("&Move to schema", self) action.setMenu( QMenu(mainWindow) ) invoke_callback = lambda: mainWindow.invokeCallback(self.prepareMenuMoveTableToSchemaActionSlot) QObject.connect( action.menu(), SIGNAL("aboutToShow()"), invoke_callback ) mainWindow.registerAction( action, "&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() QMessageBox.information(parent, "Sorry", "Cannot delete the selected item.") QApplication.setOverrideCursor(Qt.WaitCursor) def createSchemaActionSlot(self, item, action, parent): QApplication.restoreOverrideCursor() try: if not isinstance(item, (DBPlugin, Schema, Table)) or item.database() == None: QMessageBox.information(parent, "Sorry", "No database selected or you are not connected to it.") return (schema, ok) = QInputDialog.getText(parent, "New schema", "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): QMessageBox.information(parent, "Sorry", "Select an empty SCHEMA for deletion.") return res = QMessageBox.question(parent, "hey!", u"Really delete schema %s ?" % 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 != None: schemas = map(lambda x: self.schemasFactory(x, self), 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() == None: QMessageBox.information(parent, "Sorry", "No database selected or you are not connected to it.") 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: QMessageBox.information(parent, "Sorry", "Select a TABLE for editation.") 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): QMessageBox.information(parent, "Sorry", "Select a TABLE/VIEW for deletion.") return res = QMessageBox.question(parent, "hey!", u"Really delete table/view %s ?" % 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: QMessageBox.information(parent, "Sorry", "Select a TABLE to empty it.") return res = QMessageBox.question(parent, "hey!", u"Really delete all items from table %s ?" % 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 """ slot = lambda x: lambda: mainWindow.invokeCallback(self.moveTableToSchemaActionSlot, [x]) menu.clear() for schema in self.schemas(): action = menu.addAction(schema.name, slot(schema)) def moveTableToSchemaActionSlot(self, item, action, parent, new_schema): QApplication.restoreOverrideCursor() try: if not isinstance(item, Table): QMessageBox.information(parent, "Sorry", "Select a TABLE/VIEW.") 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): tables = self.connector.getTables(schema.name if schema else None) if tables != None: tables = map(lambda x: self.tablesFactory(x, self, schema), tables) return tables def createTable(self, table, fields, schema=None): field_defs = map( lambda x: x.definition(), fields ) pkeys = filter(lambda x: x.primaryKey, fields) pk_name = pkeys[0].name if len(pkeys) > 0 else None ret = self.connector.createTable( (schema, table), field_defs, pk_name) if ret != False: self.refresh() return ret def createVectorTable(self, table, fields, geom, schema=None): ret = self.createTable(table, fields, schema) if ret == False: return False try: createGeomCol = geom != None if createGeomCol: geomCol, geomType, geomSrid, geomDim = geom[:4] createSpatialIndex = geom[4] == True 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 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() ret = self.database().connector.deleteSchema(self.name) if ret != False: self.emit( SIGNAL('deleted') ) return ret def rename(self, new_name): self.aboutToChange() ret = self.database().connector.renameSchema(self.name, new_name) if ret != False: self.name = new_name self.refresh() return ret def info(self): from .info_model import SchemaInfo return SchemaInfo(self) class Table(DbItemObject): TableType, VectorType, RasterType = 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 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() if self.isView: ret = self.database().connector.deleteView( (self.schemaName(), self.name) ) else: ret = self.database().connector.deleteTable( (self.schemaName(), self.name) ) if ret != False: self.emit( SIGNAL('deleted') ) return ret def rename(self, new_name): self.aboutToChange() ret = self.database().connector.renameTable( (self.schemaName(), self.name), new_name) if ret != False: self.name = new_name self.refresh() return ret def empty(self): self.aboutToChange() ret = self.database().connector.emptyTable( (self.schemaName(), self.name) ) if ret != False: self.refreshRowCount() return ret def moveToSchema(self, schema): self.aboutToChange() if self.schema() == schema: return True ret = self.database().connector.moveTableToSchema( (self.schemaName(), self.name), schema.name ) if ret != 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 QString() uniqueCol = self.getValidQGisUniqueFields(True) if self.isView else None uri.setDataSource(schema, self.name, geomCol if geomCol else QString(), QString(), uniqueCol.name if uniqueCol else QString() ) return uri def mimeUri(self): layerType = "raster" if self.type == Table.RasterType else "vector" return u"%s:%s:%s:%s" % (layerType, self.database().dbplugin().providerName(), self.name, self.uri().uri()) def toMapLayer(self): from qgis.core import QgsVectorLayer, QgsRasterLayer provider = self.database().dbplugin().providerName() uri = self.uri().uri() if self.type == Table.RasterType: return QgsRasterLayer(uri, self.name, provider) return QgsVectorLayer(uri, self.name, provider) 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 = filter(lambda x: x.primaryKey, self.fields()) 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 != 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): return None def fields(self): if self._fields == None: fields = self.database().connector.getTableFields( (self.schemaName(), self.name) ) if fields != None: self._fields = map(lambda x: self.tableFieldsFactory(x, self), fields) return self._fields def refreshFields(self): self._fields = None # refresh table fields self.refresh() def addField(self, fld): self.aboutToChange() ret = self.database().connector.addTableColumn( (self.schemaName(), self.name), fld.definition()) if ret != False: self.refreshFields() return ret def deleteField(self, fld): self.aboutToChange() ret = self.database().connector.deleteTableColumn( (self.schemaName(), self.name), fld.name) if ret != False: self.refreshFields() return ret def addGeometryColumn(self, geomCol, geomType, srid, dim, createSpatialIndex=False): self.aboutToChange() ret = self.database().connector.addGeometryColumn( (self.schemaName(), self.name), geomCol, geomType, srid, dim) if ret == False: 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 == None: constraints = self.database().connector.getTableConstraints( (self.schemaName(), self.name) ) if constraints != None: self._constraints = map(lambda x: self.tableConstraintsFactory(x, self), constraints) return self._constraints def refreshConstraints(self): self._constraints = None # refresh table constraints self.refresh() def addConstraint(self, constr): self.aboutToChange() 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 != False: self.refreshConstraints() return ret def deleteConstraint(self, constr): self.aboutToChange() ret = self.database().connector.deleteTableConstraint( (self.schemaName(), self.name), constr.name) if ret != False: self.refreshConstraints() return ret def tableIndexesFactory(self): return None def indexes(self): if self._indexes == None: indexes = self.database().connector.getTableIndexes( (self.schemaName(), self.name) ) if indexes != None: self._indexes = map(lambda x: self.tableIndexesFactory(x, self), indexes) return self._indexes def refreshIndexes(self): self._indexes = None # refresh table indexes self.refresh() def addIndex(self, idx): self.aboutToChange() ret = self.database().connector.createTableIndex( (self.schemaName(), self.name), idx.name, idx.fields()[idx.columns[0]].name) if ret != False: self.refreshIndexes() return ret def deleteIndex(self, idx): self.aboutToChange() ret = self.database().connector.deleteTableIndex( (self.schemaName(), self.name), idx.name) if ret != False: self.refreshIndexes() return ret def tableTriggersFactory(self, row, table): return None def triggers(self): if self._triggers == None: triggers = self.database().connector.getTableTriggers( (self.schemaName(), self.name) ) if triggers != None: self._triggers = map(lambda x: self.tableTriggersFactory(x, self), 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 == None: rules = self.database().connector.getTableRules( (self.schemaName(), self.name) ) if rules != None: self._rules = map(lambda x: self.tableRulesFactory(x, self), rules) return self._rules def refreshRules(self): self._rules = None # refresh table rules self.refresh() def refreshRowCount(self): self.aboutToChange() prevRowCount = self.rowCount try: self.rowCount = self.database().connector.getTableRowCount( (self.schemaName(), self.name) ) self.rowCount = int(self.rowCount) if self.rowCount != None else None except DbError: self.rowCount = None if self.rowCount != prevRowCount: self.refresh() def runAction(self, action): action = unicode(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 = u"Do you want to %s all triggers?" % trigger_action QApplication.restoreOverrideCursor() try: if QMessageBox.question(None, "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() 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 = u"Do you want to %s trigger %s?" % (trigger_action, trigger_name) QApplication.restoreOverrideCursor() try: if QMessageBox.question(None, "Table trigger", msg, QMessageBox.Yes|QMessageBox.No) == QMessageBox.No: return False finally: QApplication.setOverrideCursor(Qt.WaitCursor) if trigger_action == "delete": self.aboutToChange() 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() self.database().connector.enableTableTrigger(trigger_name, enable, (self.schemaName(), self.name) ) self.refreshTriggers() return True return False 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 hasSpatialIndex(self, geom_column=None): geom_column = geom_column if geom_column != None else self.geomColumn fld = None for fld in self.fields(): if fld.name == geom_column: break if fld == 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() geom_column = geom_column if geom_column != None else self.geomColumn ret = self.database().connector.createSpatialIndex( (self.schemaName(), self.name), geom_column) if ret != False: self.refreshIndexes() return ret def deleteSpatialIndex(self, geom_column=None): self.aboutToChange() geom_column = geom_column if geom_column != None else self.geomColumn ret = self.database().connector.deleteSpatialIndex( (self.schemaName(), self.name), geom_column) if ret != 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 = unicode(action) if action.startswith( "spatialindex/" ): parts = action.split('/') spatialIndex_action = parts[1] msg = u"Do you want to %s spatial index for field %s?" % ( spatialIndex_action, self.geomColumn ) QApplication.restoreOverrideCursor() try: if QMessageBox.question(None, "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) 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 == None or self.modifier == -1: return u"%s" % self.dataType return u"%s (%s)" % (self.dataType, self.modifier) def default2String(self): if not self.hasDefault: return '' return self.default if self.default != 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 = u"%s %s %s" % (name, self.type2String(), not_null) if self.hasDefault: txt += u" DEFAULT %s" % self.default2String() return txt 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): self.table().aboutToChange() 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 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) if ret != False: self.table().refreshFields() return ret class TableConstraint(TableSubItemObject): """ class that represents a constraint of a table (relation) """ TypeCheck, TypeForeignKey, TypePrimaryKey, TypeUnique = range(4) types = { "c" : TypeCheck, "f" : TypeForeignKey, "p" : TypePrimaryKey, "u" : TypeUnique } onAction = { "a" : "NO ACTION", "r" : "RESTRICT", "c" : "CASCADE", "n" : "SET NULL", "d" : "SET DEFAULT" } matchTypes = { "u" : "UNSPECIFIED", "f" : "FULL", "p" : "PARTIAL" } def __init__(self, table): TableSubItemObject.__init__(self, table) self.name = self.type = self.columns = None def type2String(self): if self.type == TableConstraint.TypeCheck: return "Check" if self.type == TableConstraint.TypePrimaryKey: return "Primary key" if self.type == TableConstraint.TypeForeignKey: return "Foreign key" if self.type == TableConstraint.TypeUnique: return "Unique" return '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 = u'' 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