plugin.py 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389
  1. """
  2. /***************************************************************************
  3. Name : DB Manager
  4. Description : Database manager plugin for QGIS
  5. Date : May 23, 2011
  6. copyright : (C) 2011 by Giuseppe Sucameli
  7. email : brush.tyler@gmail.com
  8. ***************************************************************************/
  9. /***************************************************************************
  10. * *
  11. * This program is free software; you can redistribute it and/or modify *
  12. * it under the terms of the GNU General Public License as published by *
  13. * the Free Software Foundation; either version 2 of the License, or *
  14. * (at your option) any later version. *
  15. * *
  16. ***************************************************************************/
  17. """
  18. from qgis.PyQt.QtCore import Qt, QObject, pyqtSignal, QByteArray
  19. from qgis.PyQt.QtWidgets import (
  20. QFormLayout,
  21. QComboBox,
  22. QCheckBox,
  23. QDialogButtonBox,
  24. QPushButton,
  25. QLabel,
  26. QApplication,
  27. QAction,
  28. QMenu,
  29. QInputDialog,
  30. QMessageBox,
  31. QDialog,
  32. QWidget
  33. )
  34. from qgis.PyQt.QtGui import QKeySequence
  35. from qgis.core import (
  36. Qgis,
  37. QgsApplication,
  38. QgsSettings,
  39. QgsMapLayerType,
  40. QgsWkbTypes,
  41. QgsProviderConnectionException,
  42. QgsProviderRegistry,
  43. QgsVectorLayer,
  44. QgsRasterLayer,
  45. QgsProject,
  46. QgsMessageLog,
  47. QgsCoordinateReferenceSystem
  48. )
  49. from qgis.gui import (
  50. QgsMessageBarItem,
  51. QgsProjectionSelectionWidget
  52. )
  53. from ..db_plugins import createDbPlugin
  54. class BaseError(Exception):
  55. """Base class for exceptions in the plugin."""
  56. def __init__(self, e):
  57. if isinstance(e, Exception):
  58. msg = e.args[0] if len(e.args) > 0 else ''
  59. else:
  60. msg = e
  61. if not isinstance(msg, str):
  62. msg = str(msg, 'utf-8', 'replace') # convert from utf8 and replace errors (if any)
  63. self.msg = msg
  64. Exception.__init__(self, msg)
  65. def __unicode__(self):
  66. return self.msg
  67. class InvalidDataException(BaseError):
  68. pass
  69. class ConnectionError(BaseError):
  70. pass
  71. class DbError(BaseError):
  72. def __init__(self, e, query=None):
  73. BaseError.__init__(self, e)
  74. self.query = str(query) if query is not None else None
  75. def __unicode__(self):
  76. if self.query is None:
  77. return BaseError.__unicode__(self)
  78. msg = QApplication.translate("DBManagerPlugin", "Error:\n{0}").format(BaseError.__unicode__(self))
  79. if self.query:
  80. msg += QApplication.translate("DBManagerPlugin", "\n\nQuery:\n{0}").format(self.query)
  81. return msg
  82. class DBPlugin(QObject):
  83. deleted = pyqtSignal()
  84. changed = pyqtSignal()
  85. aboutToChange = pyqtSignal()
  86. def __init__(self, conn_name, parent=None):
  87. QObject.__init__(self, parent)
  88. self.connName = conn_name
  89. self.db = None
  90. def __del__(self):
  91. pass # print "DBPlugin.__del__", self.connName
  92. def connectionIcon(self):
  93. return QgsApplication.getThemeIcon("/mIconDbSchema.svg")
  94. def connectionName(self):
  95. return self.connName
  96. def database(self):
  97. return self.db
  98. def info(self):
  99. from .info_model import DatabaseInfo
  100. return DatabaseInfo(None)
  101. def connect(self, parent=None):
  102. raise NotImplementedError('Needs to be implemented by subclasses')
  103. def connectToUri(self, uri):
  104. self.db = self.databasesFactory(self, uri)
  105. if self.db:
  106. return True
  107. return False
  108. def reconnect(self):
  109. if self.db is not None:
  110. uri = self.db.uri()
  111. self.db.deleteLater()
  112. self.db = None
  113. return self.connectToUri(uri)
  114. return self.connect(self.parent())
  115. def remove(self):
  116. # Try the new API first, fallback to legacy
  117. try:
  118. md = QgsProviderRegistry.instance().providerMetadata(self.providerName())
  119. md.deleteConnection(self.connectionName())
  120. except (AttributeError, QgsProviderConnectionException):
  121. settings = QgsSettings()
  122. settings.beginGroup("/%s/%s" % (self.connectionSettingsKey(), self.connectionName()))
  123. settings.remove("")
  124. self.deleted.emit()
  125. return True
  126. @classmethod
  127. def addConnection(self, conn_name, uri):
  128. raise NotImplementedError('Needs to be implemented by subclasses')
  129. @classmethod
  130. def icon(self):
  131. return None
  132. @classmethod
  133. def typeName(self):
  134. # return the db typename (e.g. 'postgis')
  135. pass
  136. @classmethod
  137. def typeNameString(self):
  138. # return the db typename string (e.g. 'PostGIS')
  139. pass
  140. @classmethod
  141. def providerName(self):
  142. # return the provider's name (e.g. 'postgres')
  143. pass
  144. @classmethod
  145. def connectionSettingsKey(self):
  146. # return the key used to store the connections in settings
  147. pass
  148. @classmethod
  149. def connections(self):
  150. # get the list of connections
  151. conn_list = []
  152. # First try with the new core API, if that fails, proceed with legacy code
  153. try:
  154. md = QgsProviderRegistry.instance().providerMetadata(self.providerName())
  155. for name in md.dbConnections(False).keys():
  156. conn_list.append(createDbPlugin(self.typeName(), name))
  157. except (AttributeError, QgsProviderConnectionException):
  158. settings = QgsSettings()
  159. settings.beginGroup(self.connectionSettingsKey())
  160. for name in settings.childGroups():
  161. conn_list.append(createDbPlugin(self.typeName(), name))
  162. settings.endGroup()
  163. return conn_list
  164. def databasesFactory(self, connection, uri):
  165. return None
  166. @classmethod
  167. def addConnectionActionSlot(self, item, action, parent):
  168. raise NotImplementedError('Needs to be implemented by subclasses')
  169. def removeActionSlot(self, item, action, parent):
  170. QApplication.restoreOverrideCursor()
  171. try:
  172. res = QMessageBox.question(parent, QApplication.translate("DBManagerPlugin", "DB Manager"),
  173. QApplication.translate("DBManagerPlugin",
  174. "Really remove connection to {0}?").format(item.connectionName()),
  175. QMessageBox.Yes | QMessageBox.No)
  176. if res != QMessageBox.Yes:
  177. return
  178. finally:
  179. QApplication.setOverrideCursor(Qt.WaitCursor)
  180. item.remove()
  181. class DbItemObject(QObject):
  182. changed = pyqtSignal()
  183. aboutToChange = pyqtSignal()
  184. deleted = pyqtSignal()
  185. def __init__(self, parent=None):
  186. super().__init__(parent)
  187. def database(self):
  188. return None
  189. def refresh(self):
  190. self.changed.emit() # refresh the item data reading them from the db
  191. def info(self):
  192. pass
  193. def runAction(self):
  194. pass
  195. def registerActions(self, mainWindow):
  196. pass
  197. class Database(DbItemObject):
  198. def __init__(self, dbplugin, uri):
  199. super().__init__(dbplugin)
  200. self.connector = self.connectorsFactory(uri)
  201. def connectorsFactory(self, uri):
  202. return None
  203. def __del__(self):
  204. self.connector = None
  205. pass # print "Database.__del__", self
  206. def connection(self):
  207. return self.parent()
  208. def dbplugin(self):
  209. return self.parent()
  210. def database(self):
  211. return self
  212. def uri(self):
  213. return self.connector.uri()
  214. def publicUri(self):
  215. return self.connector.publicUri()
  216. def delete(self):
  217. self.aboutToChange.emit()
  218. ret = self.connection().remove()
  219. if ret is not False:
  220. self.deleted.emit()
  221. return ret
  222. def info(self):
  223. from .info_model import DatabaseInfo
  224. return DatabaseInfo(self)
  225. def sqlResultModel(self, sql, parent):
  226. from .data_model import SqlResultModel
  227. return SqlResultModel(self, sql, parent)
  228. def sqlResultModelAsync(self, sql, parent):
  229. from .data_model import SqlResultModelAsync
  230. return SqlResultModelAsync(self, sql, parent)
  231. def columnUniqueValuesModel(self, col, table, limit=10):
  232. l = ""
  233. if limit is not None:
  234. l = "LIMIT %d" % limit
  235. return self.sqlResultModel("SELECT DISTINCT %s FROM %s %s" % (col, table, l), self)
  236. def uniqueIdFunction(self):
  237. """Return a SQL function used to generate a unique id for rows of a query"""
  238. # may be overloaded by derived classes
  239. return "row_number() over ()"
  240. def toSqlLayer(self, sql, geomCol, uniqueCol, layerName="QueryLayer", layerType=None, avoidSelectById=False, filter=""):
  241. if uniqueCol is None:
  242. if hasattr(self, 'uniqueIdFunction'):
  243. uniqueFct = self.uniqueIdFunction()
  244. if uniqueFct is not None:
  245. q = 1
  246. while "_subq_%d_" % q in sql:
  247. q += 1
  248. sql = "SELECT %s AS _uid_,* FROM (%s\n) AS _subq_%d_" % (uniqueFct, sql, q)
  249. uniqueCol = "_uid_"
  250. uri = self.uri()
  251. uri.setDataSource("", "(%s\n)" % sql, geomCol, filter, uniqueCol)
  252. if avoidSelectById:
  253. uri.disableSelectAtId(True)
  254. provider = self.dbplugin().providerName()
  255. if layerType == QgsMapLayerType.RasterLayer:
  256. return QgsRasterLayer(uri.uri(False), layerName, provider)
  257. return QgsVectorLayer(uri.uri(False), layerName, provider)
  258. def registerAllActions(self, mainWindow):
  259. self.registerDatabaseActions(mainWindow)
  260. self.registerSubPluginActions(mainWindow)
  261. def registerSubPluginActions(self, mainWindow):
  262. # load plugins!
  263. try:
  264. exec("from .%s.plugins import load" % self.dbplugin().typeName(), globals())
  265. except ImportError:
  266. pass
  267. else:
  268. load(self, mainWindow) # NOQA
  269. def registerDatabaseActions(self, mainWindow):
  270. action = QAction(QApplication.translate("DBManagerPlugin", "&Re-connect"), self)
  271. mainWindow.registerAction(action, QApplication.translate("DBManagerPlugin", "&Database"),
  272. self.reconnectActionSlot)
  273. if self.schemas() is not None:
  274. action = QAction(QApplication.translate("DBManagerPlugin", "&Create Schema…"), self)
  275. mainWindow.registerAction(action, QApplication.translate("DBManagerPlugin", "&Schema"),
  276. self.createSchemaActionSlot)
  277. action = QAction(QApplication.translate("DBManagerPlugin", "&Delete (Empty) Schema"), self)
  278. mainWindow.registerAction(action, QApplication.translate("DBManagerPlugin", "&Schema"),
  279. self.deleteSchemaActionSlot)
  280. action = QAction(QApplication.translate("DBManagerPlugin", "Delete Selected Item"), self)
  281. mainWindow.registerAction(action, None, self.deleteActionSlot)
  282. action.setShortcuts(QKeySequence.Delete)
  283. action = QAction(QgsApplication.getThemeIcon("/mActionCreateTable.svg"),
  284. QApplication.translate("DBManagerPlugin", "&Create Table…"), self)
  285. mainWindow.registerAction(action, QApplication.translate("DBManagerPlugin", "&Table"),
  286. self.createTableActionSlot)
  287. action = QAction(QgsApplication.getThemeIcon("/mActionEditTable.svg"),
  288. QApplication.translate("DBManagerPlugin", "&Edit Table…"), self)
  289. mainWindow.registerAction(action, QApplication.translate("DBManagerPlugin", "&Table"), self.editTableActionSlot)
  290. action = QAction(QgsApplication.getThemeIcon("/mActionDeleteTable.svg"),
  291. QApplication.translate("DBManagerPlugin", "&Delete Table/View…"), self)
  292. mainWindow.registerAction(action, QApplication.translate("DBManagerPlugin", "&Table"),
  293. self.deleteTableActionSlot)
  294. action = QAction(QApplication.translate("DBManagerPlugin", "&Empty Table…"), self)
  295. mainWindow.registerAction(action, QApplication.translate("DBManagerPlugin", "&Table"),
  296. self.emptyTableActionSlot)
  297. if self.schemas() is not None:
  298. action = QAction(QApplication.translate("DBManagerPlugin", "&Move to Schema"), self)
  299. action.setMenu(QMenu(mainWindow))
  300. def invoke_callback():
  301. return mainWindow.invokeCallback(self.prepareMenuMoveTableToSchemaActionSlot)
  302. action.menu().aboutToShow.connect(invoke_callback)
  303. mainWindow.registerAction(action, QApplication.translate("DBManagerPlugin", "&Table"))
  304. def reconnectActionSlot(self, item, action, parent):
  305. db = item.database()
  306. db.connection().reconnect()
  307. db.refresh()
  308. def deleteActionSlot(self, item, action, parent):
  309. if isinstance(item, Schema):
  310. self.deleteSchemaActionSlot(item, action, parent)
  311. elif isinstance(item, Table):
  312. self.deleteTableActionSlot(item, action, parent)
  313. else:
  314. QApplication.restoreOverrideCursor()
  315. parent.infoBar.pushMessage(QApplication.translate("DBManagerPlugin", "Cannot delete the selected item."),
  316. Qgis.Info, parent.iface.messageTimeout())
  317. QApplication.setOverrideCursor(Qt.WaitCursor)
  318. def createSchemaActionSlot(self, item, action, parent):
  319. QApplication.restoreOverrideCursor()
  320. try:
  321. if not isinstance(item, (DBPlugin, Schema, Table)) or item.database() is None:
  322. parent.infoBar.pushMessage(
  323. QApplication.translate("DBManagerPlugin", "No database selected or you are not connected to it."),
  324. Qgis.Info, parent.iface.messageTimeout())
  325. return
  326. (schema, ok) = QInputDialog.getText(parent, QApplication.translate("DBManagerPlugin", "New schema"),
  327. QApplication.translate("DBManagerPlugin", "Enter new schema name"))
  328. if not ok:
  329. return
  330. finally:
  331. QApplication.setOverrideCursor(Qt.WaitCursor)
  332. self.createSchema(schema)
  333. def deleteSchemaActionSlot(self, item, action, parent):
  334. QApplication.restoreOverrideCursor()
  335. try:
  336. if not isinstance(item, Schema):
  337. parent.infoBar.pushMessage(
  338. QApplication.translate("DBManagerPlugin", "Select an empty schema for deletion."),
  339. Qgis.Info, parent.iface.messageTimeout())
  340. return
  341. res = QMessageBox.question(parent, QApplication.translate("DBManagerPlugin", "DB Manager"),
  342. QApplication.translate("DBManagerPlugin",
  343. "Really delete schema {0}?").format(item.name),
  344. QMessageBox.Yes | QMessageBox.No)
  345. if res != QMessageBox.Yes:
  346. return
  347. finally:
  348. QApplication.setOverrideCursor(Qt.WaitCursor)
  349. item.delete()
  350. def schemasFactory(self, row, db):
  351. return None
  352. def schemas(self):
  353. schemas = self.connector.getSchemas()
  354. if schemas is not None:
  355. schemas = [self.schemasFactory(x, self) for x in schemas]
  356. return schemas
  357. def createSchema(self, name):
  358. self.connector.createSchema(name)
  359. self.refresh()
  360. def createTableActionSlot(self, item, action, parent):
  361. QApplication.restoreOverrideCursor()
  362. if not hasattr(item, 'database') or item.database() is None:
  363. parent.infoBar.pushMessage(
  364. QApplication.translate("DBManagerPlugin", "No database selected or you are not connected to it."),
  365. Qgis.Info, parent.iface.messageTimeout())
  366. return
  367. from ..dlg_create_table import DlgCreateTable
  368. DlgCreateTable(item, parent).exec_()
  369. QApplication.setOverrideCursor(Qt.WaitCursor)
  370. def editTableActionSlot(self, item, action, parent):
  371. QApplication.restoreOverrideCursor()
  372. try:
  373. if not isinstance(item, Table) or item.isView:
  374. parent.infoBar.pushMessage(QApplication.translate("DBManagerPlugin", "Select a table to edit."),
  375. Qgis.Info, parent.iface.messageTimeout())
  376. return
  377. if isinstance(item, RasterTable):
  378. parent.infoBar.pushMessage(QApplication.translate("DBManagerPlugin", "Editing of raster tables is not supported."),
  379. Qgis.Info, parent.iface.messageTimeout())
  380. return
  381. from ..dlg_table_properties import DlgTableProperties
  382. DlgTableProperties(item, parent).exec_()
  383. finally:
  384. QApplication.setOverrideCursor(Qt.WaitCursor)
  385. def deleteTableActionSlot(self, item, action, parent):
  386. QApplication.restoreOverrideCursor()
  387. try:
  388. if not isinstance(item, Table):
  389. parent.infoBar.pushMessage(
  390. QApplication.translate("DBManagerPlugin", "Select a table/view for deletion."),
  391. Qgis.Info, parent.iface.messageTimeout())
  392. return
  393. res = QMessageBox.question(parent, QApplication.translate("DBManagerPlugin", "DB Manager"),
  394. QApplication.translate("DBManagerPlugin",
  395. "Really delete table/view {0}?").format(item.name),
  396. QMessageBox.Yes | QMessageBox.No)
  397. if res != QMessageBox.Yes:
  398. return
  399. finally:
  400. QApplication.setOverrideCursor(Qt.WaitCursor)
  401. item.delete()
  402. def emptyTableActionSlot(self, item, action, parent):
  403. QApplication.restoreOverrideCursor()
  404. try:
  405. if not isinstance(item, Table) or item.isView:
  406. parent.infoBar.pushMessage(QApplication.translate("DBManagerPlugin", "Select a table to empty it."),
  407. Qgis.Info, parent.iface.messageTimeout())
  408. return
  409. res = QMessageBox.question(parent, QApplication.translate("DBManagerPlugin", "DB Manager"),
  410. QApplication.translate("DBManagerPlugin",
  411. "Really delete all items from table {0}?").format(item.name),
  412. QMessageBox.Yes | QMessageBox.No)
  413. if res != QMessageBox.Yes:
  414. return
  415. finally:
  416. QApplication.setOverrideCursor(Qt.WaitCursor)
  417. item.empty()
  418. def prepareMenuMoveTableToSchemaActionSlot(self, item, menu, mainWindow):
  419. """ populate menu with schemas """
  420. def slot(x):
  421. return lambda: mainWindow.invokeCallback(self.moveTableToSchemaActionSlot, x)
  422. menu.clear()
  423. for schema in self.schemas():
  424. menu.addAction(schema.name, slot(schema))
  425. def moveTableToSchemaActionSlot(self, item, action, parent, new_schema):
  426. QApplication.restoreOverrideCursor()
  427. try:
  428. if not isinstance(item, Table):
  429. parent.infoBar.pushMessage(QApplication.translate("DBManagerPlugin", "Select a table/view."),
  430. Qgis.Info, parent.iface.messageTimeout())
  431. return
  432. finally:
  433. QApplication.setOverrideCursor(Qt.WaitCursor)
  434. item.moveToSchema(new_schema)
  435. def tablesFactory(self, row, db, schema=None):
  436. typ, row = row[0], row[1:]
  437. if typ == Table.VectorType:
  438. return self.vectorTablesFactory(row, db, schema)
  439. elif typ == Table.RasterType:
  440. return self.rasterTablesFactory(row, db, schema)
  441. return self.dataTablesFactory(row, db, schema)
  442. def dataTablesFactory(self, row, db, schema=None):
  443. return None
  444. def vectorTablesFactory(self, row, db, schema=None):
  445. return None
  446. def rasterTablesFactory(self, row, db, schema=None):
  447. return None
  448. def tables(self, schema=None, sys_tables=False):
  449. tables = self.connector.getTables(schema.name if schema else None, sys_tables)
  450. if tables is not None:
  451. ret = [
  452. self.tablesFactory(t, self, schema)
  453. for t in tables
  454. ]
  455. return ret
  456. def createTable(self, table, fields, schema=None):
  457. field_defs = [x.definition() for x in fields]
  458. pkeys = [x for x in fields if x.primaryKey]
  459. pk_name = pkeys[0].name if len(pkeys) > 0 else None
  460. ret = self.connector.createTable((schema, table), field_defs, pk_name)
  461. if ret is not False:
  462. # Add comments if any, because definition does not include
  463. # the comment
  464. for f in fields:
  465. if f.comment:
  466. self.connector.updateTableColumn(
  467. (schema, table), f.name, comment=f.comment
  468. )
  469. self.refresh()
  470. return ret
  471. def createVectorTable(self, table, fields, geom, schema=None):
  472. ret = self.createTable(table, fields, schema)
  473. if not ret:
  474. return False
  475. try:
  476. createGeomCol = geom is not None
  477. if createGeomCol:
  478. geomCol, geomType, geomSrid, geomDim = geom[:4]
  479. createSpatialIndex = geom[4] if len(geom) > 4 else False
  480. self.connector.addGeometryColumn((schema, table), geomCol, geomType, geomSrid, geomDim)
  481. if createSpatialIndex:
  482. # commit data definition changes, otherwise index can't be built
  483. self.connector._commit()
  484. self.connector.createSpatialIndex((schema, table), geomCol)
  485. finally:
  486. self.refresh()
  487. return True
  488. def explicitSpatialIndex(self):
  489. return False
  490. def spatialIndexClause(self, src_table, src_column, dest_table, dest_table_column):
  491. return None
  492. def hasLowercaseFieldNamesOption(self):
  493. return False
  494. class Schema(DbItemObject):
  495. def __init__(self, db):
  496. DbItemObject.__init__(self, db)
  497. self.oid = self.name = self.owner = self.perms = None
  498. self.comment = None
  499. self.tableCount = 0
  500. def __del__(self):
  501. pass # print "Schema.__del__", self
  502. def database(self):
  503. return self.parent()
  504. def schema(self):
  505. return self
  506. def tables(self):
  507. return self.database().tables(self)
  508. def delete(self):
  509. self.aboutToChange.emit()
  510. ret = self.database().connector.deleteSchema(self.name)
  511. if ret is not False:
  512. self.deleted.emit()
  513. return ret
  514. def rename(self, new_name):
  515. self.aboutToChange.emit()
  516. ret = self.database().connector.renameSchema(self.name, new_name)
  517. if ret is not False:
  518. self.name = new_name
  519. # FIXME: refresh triggers
  520. self.refresh()
  521. return ret
  522. def info(self):
  523. from .info_model import SchemaInfo
  524. return SchemaInfo(self)
  525. class Table(DbItemObject):
  526. TableType, VectorType, RasterType = list(range(3))
  527. def __init__(self, db, schema=None, parent=None):
  528. DbItemObject.__init__(self, db)
  529. self._schema = schema
  530. if hasattr(self, 'type'):
  531. return
  532. self.type = Table.TableType
  533. self.name = self.isView = self.owner = self.pages = None
  534. self.comment = None
  535. self.rowCount = None
  536. self._fields = self._indexes = self._constraints = self._triggers = self._rules = None
  537. def __del__(self):
  538. pass # print "Table.__del__", self
  539. def canBeAddedToCanvas(self):
  540. return True
  541. def database(self):
  542. return self.parent()
  543. def schema(self):
  544. return self._schema
  545. def schemaName(self):
  546. return self.schema().name if self.schema() else None
  547. def quotedName(self):
  548. return self.database().connector.quoteId((self.schemaName(), self.name))
  549. def delete(self):
  550. self.aboutToChange.emit()
  551. if self.isView:
  552. ret = self.database().connector.deleteView((self.schemaName(), self.name))
  553. else:
  554. ret = self.database().connector.deleteTable((self.schemaName(), self.name))
  555. if ret is not False:
  556. self.deleted.emit()
  557. return ret
  558. def rename(self, new_name):
  559. self.aboutToChange.emit()
  560. ret = self.database().connector.renameTable((self.schemaName(), self.name), new_name)
  561. if ret is not False:
  562. self.name = new_name
  563. self._triggers = None
  564. self._rules = None
  565. self._constraints = None
  566. self.refresh()
  567. return ret
  568. def empty(self):
  569. self.aboutToChange.emit()
  570. ret = self.database().connector.emptyTable((self.schemaName(), self.name))
  571. if ret is not False:
  572. self.refreshRowCount()
  573. return ret
  574. def moveToSchema(self, schema):
  575. self.aboutToChange.emit()
  576. if self.schema() == schema:
  577. return True
  578. ret = self.database().connector.moveTableToSchema((self.schemaName(), self.name), schema.name)
  579. if ret is not False:
  580. self.schema().refresh()
  581. schema.refresh()
  582. return ret
  583. def info(self):
  584. from .info_model import TableInfo
  585. return TableInfo(self)
  586. def uri(self):
  587. uri = self.database().uri()
  588. schema = self.schemaName() if self.schemaName() else ''
  589. geomCol = self.geomColumn if self.type in [Table.VectorType, Table.RasterType] else ""
  590. uniqueCol = self.getValidQgisUniqueFields(True) if self.isView else None
  591. uri.setDataSource(schema, self.name, geomCol if geomCol else None, None, uniqueCol.name if uniqueCol else "")
  592. return uri
  593. def crs(self):
  594. """Returns the CRS of this table or an invalid CRS if this is not a spatial table
  595. This should be overwritten by any additional db plugins"""
  596. return QgsCoordinateReferenceSystem()
  597. def mimeUri(self):
  598. layerType = "raster" if self.type == Table.RasterType else "vector"
  599. return "%s:%s:%s:%s" % (layerType, self.database().dbplugin().providerName(), self.name, self.uri().uri(False))
  600. def toMapLayer(self, geometryType=None, crs=None):
  601. provider = self.database().dbplugin().providerName()
  602. dataSourceUri = self.uri()
  603. if geometryType:
  604. dataSourceUri.setWkbType(QgsWkbTypes.parseType(geometryType))
  605. if crs:
  606. dataSourceUri.setSrid(str(crs.postgisSrid()))
  607. uri = dataSourceUri.uri(False)
  608. if self.type == Table.RasterType:
  609. return QgsRasterLayer(uri, self.name, provider)
  610. return QgsVectorLayer(uri, self.name, provider)
  611. def geometryType(self):
  612. pass
  613. def getValidQgisUniqueFields(self, onlyOne=False):
  614. """ list of fields valid to load the table as layer in QGIS canvas.
  615. QGIS automatically search for a valid unique field, so it's
  616. needed only for queries and views """
  617. ret = []
  618. # add the pk
  619. pkcols = [x for x in self.fields() if x.primaryKey]
  620. if len(pkcols) == 1:
  621. ret.append(pkcols[0])
  622. # then add both oid, serial and int fields with an unique index
  623. indexes = self.indexes()
  624. if indexes is not None:
  625. for idx in indexes:
  626. if idx.isUnique and len(idx.columns) == 1:
  627. fld = idx.fields()[idx.columns[0]]
  628. if fld.dataType in ["oid", "serial", "int4", "int8"] and fld not in ret:
  629. ret.append(fld)
  630. # and finally append the other suitable fields
  631. for fld in self.fields():
  632. if fld.dataType in ["oid", "serial", "int4", "int8"] and fld not in ret:
  633. ret.append(fld)
  634. if onlyOne:
  635. return ret[0] if len(ret) > 0 else None
  636. return ret
  637. def tableDataModel(self, parent):
  638. pass
  639. def tableFieldsFactory(self, row, table):
  640. raise NotImplementedError('Needs to be implemented by subclasses')
  641. def fields(self):
  642. if self._fields is None:
  643. fields = self.database().connector.getTableFields((self.schemaName(), self.name))
  644. if fields is not None:
  645. self._fields = [self.tableFieldsFactory(x, self) for x in fields]
  646. return self._fields
  647. def refreshFields(self):
  648. self._fields = None # refresh table fields
  649. self.refresh()
  650. def addField(self, fld):
  651. self.aboutToChange.emit()
  652. ret = self.database().connector.addTableColumn((self.schemaName(), self.name), fld.definition())
  653. if ret is not False:
  654. self.refreshFields()
  655. return ret
  656. def deleteField(self, fld):
  657. self.aboutToChange.emit()
  658. ret = self.database().connector.deleteTableColumn((self.schemaName(), self.name), fld.name)
  659. if ret is not False:
  660. self.refreshFields()
  661. self.refreshConstraints()
  662. self.refreshIndexes()
  663. return ret
  664. def addGeometryColumn(self, geomCol, geomType, srid, dim, createSpatialIndex=False):
  665. self.aboutToChange.emit()
  666. ret = self.database().connector.addGeometryColumn((self.schemaName(), self.name), geomCol, geomType, srid, dim)
  667. if not ret:
  668. return False
  669. try:
  670. if createSpatialIndex:
  671. # commit data definition changes, otherwise index can't be built
  672. self.database().connector._commit()
  673. self.database().connector.createSpatialIndex((self.schemaName(), self.name), geomCol)
  674. finally:
  675. self.schema().refresh() if self.schema() else self.database().refresh() # another table was added
  676. return True
  677. def tableConstraintsFactory(self):
  678. return None
  679. def constraints(self):
  680. if self._constraints is None:
  681. constraints = self.database().connector.getTableConstraints((self.schemaName(), self.name))
  682. if constraints is not None:
  683. self._constraints = [self.tableConstraintsFactory(x, self) for x in constraints]
  684. return self._constraints
  685. def refreshConstraints(self):
  686. self._constraints = None # refresh table constraints
  687. self.refresh()
  688. def addConstraint(self, constr):
  689. self.aboutToChange.emit()
  690. if constr.type == TableConstraint.TypePrimaryKey:
  691. ret = self.database().connector.addTablePrimaryKey((self.schemaName(), self.name),
  692. constr.fields()[constr.columns[0]].name)
  693. elif constr.type == TableConstraint.TypeUnique:
  694. ret = self.database().connector.addTableUniqueConstraint((self.schemaName(), self.name),
  695. constr.fields()[constr.columns[0]].name)
  696. else:
  697. return False
  698. if ret is not False:
  699. self.refreshConstraints()
  700. return ret
  701. def deleteConstraint(self, constr):
  702. self.aboutToChange.emit()
  703. ret = self.database().connector.deleteTableConstraint((self.schemaName(), self.name), constr.name)
  704. if ret is not False:
  705. self.refreshConstraints()
  706. return ret
  707. def tableIndexesFactory(self):
  708. return None
  709. def indexes(self):
  710. if self._indexes is None:
  711. indexes = self.database().connector.getTableIndexes((self.schemaName(), self.name))
  712. if indexes is not None:
  713. self._indexes = [self.tableIndexesFactory(x, self) for x in indexes]
  714. return self._indexes
  715. def refreshIndexes(self):
  716. self._indexes = None # refresh table indexes
  717. self.refresh()
  718. def addIndex(self, idx):
  719. self.aboutToChange.emit()
  720. ret = self.database().connector.createTableIndex((self.schemaName(), self.name), idx.name,
  721. idx.fields()[idx.columns[0]].name)
  722. if ret is not False:
  723. self.refreshIndexes()
  724. return ret
  725. def deleteIndex(self, idx):
  726. self.aboutToChange.emit()
  727. ret = self.database().connector.deleteTableIndex((self.schemaName(), self.name), idx.name)
  728. if ret is not False:
  729. self.refreshIndexes()
  730. return ret
  731. def tableTriggersFactory(self, row, table):
  732. return None
  733. def triggers(self):
  734. if self._triggers is None:
  735. triggers = self.database().connector.getTableTriggers((self.schemaName(), self.name))
  736. if triggers is not None:
  737. self._triggers = [self.tableTriggersFactory(x, self) for x in triggers]
  738. return self._triggers
  739. def refreshTriggers(self):
  740. self._triggers = None # refresh table triggers
  741. self.refresh()
  742. def tableRulesFactory(self, row, table):
  743. return None
  744. def rules(self):
  745. if self._rules is None:
  746. rules = self.database().connector.getTableRules((self.schemaName(), self.name))
  747. if rules is not None:
  748. self._rules = [self.tableRulesFactory(x, self) for x in rules]
  749. return self._rules
  750. def refreshRules(self):
  751. self._rules = None # refresh table rules
  752. self.refresh()
  753. def refreshRowCount(self):
  754. self.aboutToChange.emit()
  755. prevRowCount = self.rowCount
  756. try:
  757. self.rowCount = self.database().connector.getTableRowCount((self.schemaName(), self.name))
  758. self.rowCount = int(self.rowCount) if self.rowCount is not None else None
  759. except DbError:
  760. self.rowCount = None
  761. if self.rowCount != prevRowCount:
  762. self.refresh()
  763. def runAction(self, action):
  764. action = str(action)
  765. if action.startswith("rows/"):
  766. if action == "rows/count":
  767. self.refreshRowCount()
  768. return True
  769. elif action.startswith("triggers/"):
  770. parts = action.split('/')
  771. trigger_action = parts[1]
  772. msg = QApplication.translate("DBManagerPlugin", "Do you want to {0} all triggers?").format(trigger_action)
  773. QApplication.restoreOverrideCursor()
  774. try:
  775. if QMessageBox.question(None, QApplication.translate("DBManagerPlugin", "Table triggers"), msg,
  776. QMessageBox.Yes | QMessageBox.No) == QMessageBox.No:
  777. return False
  778. finally:
  779. QApplication.setOverrideCursor(Qt.WaitCursor)
  780. if trigger_action == "enable" or trigger_action == "disable":
  781. enable = trigger_action == "enable"
  782. self.aboutToChange.emit()
  783. self.database().connector.enableAllTableTriggers(enable, (self.schemaName(), self.name))
  784. self.refreshTriggers()
  785. return True
  786. elif action.startswith("trigger/"):
  787. parts = action.split('/')
  788. trigger_name = parts[1]
  789. trigger_action = parts[2]
  790. msg = QApplication.translate("DBManagerPlugin", "Do you want to {0} trigger {1}?").format(
  791. trigger_action, trigger_name)
  792. QApplication.restoreOverrideCursor()
  793. try:
  794. if QMessageBox.question(None, QApplication.translate("DBManagerPlugin", "Table trigger"), msg,
  795. QMessageBox.Yes | QMessageBox.No) == QMessageBox.No:
  796. return False
  797. finally:
  798. QApplication.setOverrideCursor(Qt.WaitCursor)
  799. if trigger_action == "delete":
  800. self.aboutToChange.emit()
  801. self.database().connector.deleteTableTrigger(trigger_name, (self.schemaName(), self.name))
  802. self.refreshTriggers()
  803. return True
  804. elif trigger_action == "enable" or trigger_action == "disable":
  805. enable = trigger_action == "enable"
  806. self.aboutToChange.emit()
  807. self.database().connector.enableTableTrigger(trigger_name, enable, (self.schemaName(), self.name))
  808. self.refreshTriggers()
  809. return True
  810. return False
  811. def addExtraContextMenuEntries(self, menu):
  812. """Called whenever a context menu is shown for this table. Can be used to add additional actions to the menu."""
  813. pass
  814. class VectorTable(Table):
  815. def __init__(self, db, schema=None, parent=None):
  816. if not hasattr(self, 'type'): # check if the superclass constructor was called yet!
  817. Table.__init__(self, db, schema, parent)
  818. self.type = Table.VectorType
  819. self.geomColumn = self.geomType = self.geomDim = self.srid = None
  820. self.estimatedExtent = self.extent = None
  821. def info(self):
  822. from .info_model import VectorTableInfo
  823. return VectorTableInfo(self)
  824. def uri(self):
  825. uri = super().uri()
  826. for f in self.fields():
  827. if f.primaryKey:
  828. uri.setKeyColumn(f.name)
  829. break
  830. uri.setWkbType(QgsWkbTypes.parseType(self.geomType))
  831. return uri
  832. def hasSpatialIndex(self, geom_column=None):
  833. geom_column = geom_column if geom_column is not None else self.geomColumn
  834. fld = None
  835. for fld in self.fields():
  836. if fld.name == geom_column:
  837. break
  838. if fld is None:
  839. return False
  840. for idx in self.indexes():
  841. if fld.num in idx.columns:
  842. return True
  843. return False
  844. def createSpatialIndex(self, geom_column=None):
  845. self.aboutToChange.emit()
  846. geom_column = geom_column if geom_column is not None else self.geomColumn
  847. ret = self.database().connector.createSpatialIndex((self.schemaName(), self.name), geom_column)
  848. if ret is not False:
  849. self.refreshIndexes()
  850. return ret
  851. def deleteSpatialIndex(self, geom_column=None):
  852. self.aboutToChange.emit()
  853. geom_column = geom_column if geom_column is not None else self.geomColumn
  854. ret = self.database().connector.deleteSpatialIndex((self.schemaName(), self.name), geom_column)
  855. if ret is not False:
  856. self.refreshIndexes()
  857. return ret
  858. def refreshTableExtent(self):
  859. prevExtent = self.extent
  860. try:
  861. self.extent = self.database().connector.getTableExtent((self.schemaName(), self.name), self.geomColumn)
  862. except DbError:
  863. self.extent = None
  864. if self.extent != prevExtent:
  865. self.refresh()
  866. def refreshTableEstimatedExtent(self):
  867. prevEstimatedExtent = self.estimatedExtent
  868. try:
  869. self.estimatedExtent = self.database().connector.getTableEstimatedExtent((self.schemaName(), self.name),
  870. self.geomColumn)
  871. except DbError:
  872. self.estimatedExtent = None
  873. if self.estimatedExtent != prevEstimatedExtent:
  874. self.refresh()
  875. def runAction(self, action):
  876. action = str(action)
  877. if action.startswith("spatialindex/"):
  878. parts = action.split('/')
  879. spatialIndex_action = parts[1]
  880. msg = QApplication.translate("DBManagerPlugin", "Do you want to {0} spatial index for field {1}?").format(
  881. spatialIndex_action, self.geomColumn)
  882. QApplication.restoreOverrideCursor()
  883. try:
  884. if QMessageBox.question(None, QApplication.translate("DBManagerPlugin", "Spatial Index"), msg,
  885. QMessageBox.Yes | QMessageBox.No) == QMessageBox.No:
  886. return False
  887. finally:
  888. QApplication.setOverrideCursor(Qt.WaitCursor)
  889. if spatialIndex_action == "create":
  890. self.createSpatialIndex()
  891. return True
  892. elif spatialIndex_action == "delete":
  893. self.deleteSpatialIndex()
  894. return True
  895. if action.startswith("extent/"):
  896. if action == "extent/get":
  897. self.refreshTableExtent()
  898. return True
  899. if action == "extent/estimated/get":
  900. self.refreshTableEstimatedExtent()
  901. return True
  902. return Table.runAction(self, action)
  903. def addLayer(self, geometryType=None, crs=None):
  904. layer = self.toMapLayer(geometryType, crs)
  905. layers = QgsProject.instance().addMapLayers([layer])
  906. if len(layers) != 1:
  907. QgsMessageLog.logMessage(self.tr("{layer} is an invalid layer - not loaded").format(layer=layer.publicSource()))
  908. 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)
  909. msgLabel.setWordWrap(True)
  910. msgLabel.linkActivated.connect(self.mainWindow.iface.mainWindow().findChild(QWidget, "MessageLog").show)
  911. msgLabel.linkActivated.connect(self.mainWindow.iface.mainWindow().raise_)
  912. self.mainWindow.infoBar.pushItem(QgsMessageBarItem(msgLabel, Qgis.Warning))
  913. def showAdvancedVectorDialog(self):
  914. dlg = QDialog()
  915. dlg.setObjectName('dbManagerAdvancedVectorDialog')
  916. settings = QgsSettings()
  917. dlg.restoreGeometry(settings.value("/DB_Manager/advancedAddDialog/geometry", QByteArray(), type=QByteArray))
  918. layout = QFormLayout()
  919. dlg.setLayout(layout)
  920. dlg.setWindowTitle(self.tr('Add Layer {}').format(self.name))
  921. geometryTypeComboBox = QComboBox()
  922. geometryTypeComboBox.addItem(self.tr('Point'), 'POINT')
  923. geometryTypeComboBox.addItem(self.tr('Line'), 'LINESTRING')
  924. geometryTypeComboBox.addItem(self.tr('Polygon'), 'POLYGON')
  925. layout.addRow(self.tr('Geometry Type'), geometryTypeComboBox)
  926. zCheckBox = QCheckBox(self.tr('With Z'))
  927. mCheckBox = QCheckBox(self.tr('With M'))
  928. layout.addRow(zCheckBox)
  929. layout.addRow(mCheckBox)
  930. crsSelector = QgsProjectionSelectionWidget()
  931. crsSelector.setCrs(self.crs())
  932. layout.addRow(self.tr('CRS'), crsSelector)
  933. def selectedGeometryType():
  934. geomType = geometryTypeComboBox.currentData()
  935. if zCheckBox.isChecked():
  936. geomType += 'Z'
  937. if mCheckBox.isChecked():
  938. geomType += 'M'
  939. return geomType
  940. def selectedCrs():
  941. return crsSelector.crs()
  942. addButton = QPushButton(self.tr('Load Layer'))
  943. addButton.clicked.connect(lambda: self.addLayer(selectedGeometryType(), selectedCrs()))
  944. btns = QDialogButtonBox(QDialogButtonBox.Cancel)
  945. btns.addButton(addButton, QDialogButtonBox.ActionRole)
  946. layout.addRow(btns)
  947. addButton.clicked.connect(dlg.accept)
  948. btns.accepted.connect(dlg.accept)
  949. btns.rejected.connect(dlg.reject)
  950. dlg.exec_()
  951. settings = QgsSettings()
  952. settings.setValue("/DB_Manager/advancedAddDialog/geometry", dlg.saveGeometry())
  953. def addExtraContextMenuEntries(self, menu):
  954. """Called whenever a context menu is shown for this table. Can be used to add additional actions to the menu."""
  955. if self.geomType == 'GEOMETRY':
  956. menu.addAction(QApplication.translate("DBManagerPlugin", "Add Layer (Advanced)…"), self.showAdvancedVectorDialog)
  957. class RasterTable(Table):
  958. def __init__(self, db, schema=None, parent=None):
  959. if not hasattr(self, 'type'): # check if the superclass constructor was called yet!
  960. Table.__init__(self, db, schema, parent)
  961. self.type = Table.RasterType
  962. self.geomColumn = self.geomType = self.pixelSizeX = self.pixelSizeY = self.pixelType = self.isExternal = self.srid = None
  963. self.extent = None
  964. def info(self):
  965. from .info_model import RasterTableInfo
  966. return RasterTableInfo(self)
  967. class TableSubItemObject(QObject):
  968. def __init__(self, table):
  969. QObject.__init__(self, table)
  970. def table(self):
  971. return self.parent()
  972. def database(self):
  973. return self.table().database() if self.table() else None
  974. class TableField(TableSubItemObject):
  975. def __init__(self, table):
  976. TableSubItemObject.__init__(self, table)
  977. self.num = self.name = self.dataType = self.modifier = self.notNull = self.default = self.hasDefault = self.primaryKey = None
  978. self.comment = None
  979. def type2String(self):
  980. if self.modifier is None or self.modifier == -1:
  981. return "%s" % self.dataType
  982. return "%s (%s)" % (self.dataType, self.modifier)
  983. def default2String(self):
  984. if not self.hasDefault:
  985. return ''
  986. return self.default if self.default is not None else "NULL"
  987. def definition(self):
  988. from .connector import DBConnector
  989. quoteIdFunc = self.database().connector.quoteId if self.database() else DBConnector.quoteId
  990. name = quoteIdFunc(self.name)
  991. not_null = "NOT NULL" if self.notNull else ""
  992. txt = "%s %s %s" % (name, self.type2String(), not_null)
  993. if self.hasDefault:
  994. txt += " DEFAULT %s" % self.default2String()
  995. return txt
  996. def getComment(self):
  997. """Returns the comment for a field"""
  998. return ''
  999. def delete(self):
  1000. return self.table().deleteField(self)
  1001. def rename(self, new_name):
  1002. return self.update(new_name)
  1003. def update(self, new_name, new_type_str=None, new_not_null=None, new_default_str=None, new_comment=None):
  1004. self.table().aboutToChange.emit()
  1005. if self.name == new_name:
  1006. new_name = None
  1007. if self.type2String() == new_type_str:
  1008. new_type_str = None
  1009. if self.notNull == new_not_null:
  1010. new_not_null = None
  1011. if self.default2String() == new_default_str:
  1012. new_default_str = None
  1013. if self.comment == new_comment:
  1014. new_comment = None
  1015. ret = self.table().database().connector.updateTableColumn((self.table().schemaName(), self.table().name),
  1016. self.name, new_name, new_type_str,
  1017. new_not_null, new_default_str, new_comment)
  1018. if ret is not False:
  1019. self.table().refreshFields()
  1020. return ret
  1021. class TableConstraint(TableSubItemObject):
  1022. """ class that represents a constraint of a table (relation) """
  1023. TypeCheck, TypeForeignKey, TypePrimaryKey, TypeUnique, TypeExclusion, TypeUnknown = list(range(6))
  1024. types = {"c": TypeCheck, "f": TypeForeignKey, "p": TypePrimaryKey, "u": TypeUnique, "x": TypeExclusion}
  1025. onAction = {"a": "NO ACTION", "r": "RESTRICT", "c": "CASCADE", "n": "SET NULL", "d": "SET DEFAULT"}
  1026. matchTypes = {"u": "UNSPECIFIED", "f": "FULL", "p": "PARTIAL", "s": "SIMPLE"}
  1027. def __init__(self, table):
  1028. TableSubItemObject.__init__(self, table)
  1029. self.name = self.type = self.columns = None
  1030. def type2String(self):
  1031. if self.type == TableConstraint.TypeCheck:
  1032. return QApplication.translate("DBManagerPlugin", "Check")
  1033. if self.type == TableConstraint.TypePrimaryKey:
  1034. return QApplication.translate("DBManagerPlugin", "Primary key")
  1035. if self.type == TableConstraint.TypeForeignKey:
  1036. return QApplication.translate("DBManagerPlugin", "Foreign key")
  1037. if self.type == TableConstraint.TypeUnique:
  1038. return QApplication.translate("DBManagerPlugin", "Unique")
  1039. if self.type == TableConstraint.TypeExclusion:
  1040. return QApplication.translate("DBManagerPlugin", "Exclusion")
  1041. return QApplication.translate("DBManagerPlugin", 'Unknown')
  1042. def fields(self):
  1043. def fieldFromNum(num, fields):
  1044. """ return field specified by its number or None if doesn't exist """
  1045. for fld in fields:
  1046. if fld.num == num:
  1047. return fld
  1048. return None
  1049. fields = self.table().fields()
  1050. cols = {}
  1051. for num in self.columns:
  1052. cols[num] = fieldFromNum(num, fields)
  1053. return cols
  1054. def delete(self):
  1055. return self.table().deleteConstraint(self)
  1056. class TableIndex(TableSubItemObject):
  1057. def __init__(self, table):
  1058. TableSubItemObject.__init__(self, table)
  1059. self.name = self.columns = self.isUnique = None
  1060. def fields(self):
  1061. def fieldFromNum(num, fields):
  1062. """ return field specified by its number or None if doesn't exist """
  1063. for fld in fields:
  1064. if fld.num == num:
  1065. return fld
  1066. return None
  1067. fields = self.table().fields()
  1068. cols = {}
  1069. for num in self.columns:
  1070. cols[num] = fieldFromNum(num, fields)
  1071. return cols
  1072. def delete(self):
  1073. return self.table().deleteIndex(self)
  1074. class TableTrigger(TableSubItemObject):
  1075. """ class that represents a trigger """
  1076. # Bits within tgtype (pg_trigger.h)
  1077. TypeRow = (1 << 0) # row or statement
  1078. TypeBefore = (1 << 1) # before or after
  1079. # events: one or more
  1080. TypeInsert = (1 << 2)
  1081. TypeDelete = (1 << 3)
  1082. TypeUpdate = (1 << 4)
  1083. TypeTruncate = (1 << 5)
  1084. def __init__(self, table):
  1085. TableSubItemObject.__init__(self, table)
  1086. self.name = self.function = None
  1087. def type2String(self):
  1088. trig_type = ''
  1089. trig_type += "Before " if self.type & TableTrigger.TypeBefore else "After "
  1090. if self.type & TableTrigger.TypeInsert:
  1091. trig_type += "INSERT "
  1092. if self.type & TableTrigger.TypeUpdate:
  1093. trig_type += "UPDATE "
  1094. if self.type & TableTrigger.TypeDelete:
  1095. trig_type += "DELETE "
  1096. if self.type & TableTrigger.TypeTruncate:
  1097. trig_type += "TRUNCATE "
  1098. trig_type += "\n"
  1099. trig_type += "for each "
  1100. trig_type += "row" if self.type & TableTrigger.TypeRow else "statement"
  1101. return trig_type
  1102. class TableRule(TableSubItemObject):
  1103. def __init__(self, table):
  1104. TableSubItemObject.__init__(self, table)
  1105. self.name = self.definition = None