info_model.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667
  1. """
  2. /***************************************************************************
  3. Name : DB Manager
  4. Description : Database manager plugin for QGIS (Oracle)
  5. Date : Aug 27, 2014
  6. copyright : (C) 2014 by Médéric RIBREUX
  7. email : mederic.ribreux@gmail.com
  8. The content of this file is based on
  9. - PG_Manager by Martin Dobias <wonder.sk@gmail.com> (GPLv2 license)
  10. - DB Manager by Giuseppe Sucameli <brush.tyler@gmail.com> (GPLv2 license)
  11. ***************************************************************************/
  12. /***************************************************************************
  13. * *
  14. * This program is free software; you can redistribute it and/or modify *
  15. * it under the terms of the GNU General Public License as published by *
  16. * the Free Software Foundation; either version 2 of the License, or *
  17. * (at your option) any later version. *
  18. * *
  19. ***************************************************************************/
  20. """
  21. from qgis.PyQt.QtWidgets import QApplication
  22. from qgis.core import QgsWkbTypes
  23. from ..info_model import TableInfo, VectorTableInfo, DatabaseInfo
  24. from ..html_elems import HtmlContent, HtmlSection, HtmlParagraph, \
  25. HtmlTable, HtmlTableHeader, HtmlTableCol
  26. # Syntax Highlight for VIEWS/MVIEWS
  27. from pygments import highlight
  28. from pygments.lexers import get_lexer_by_name
  29. from pygments.formatters import HtmlFormatter
  30. class ORDatabaseInfo(DatabaseInfo):
  31. def __init__(self, db):
  32. self.db = db
  33. def connectionDetails(self):
  34. tbl = []
  35. if self.db.connector.host != "":
  36. tbl.append((QApplication.translate("DBManagerPlugin", "Host:"),
  37. self.db.connector.host))
  38. tbl.append((QApplication.translate("DBManagerPlugin", "Database:"),
  39. self.db.connector.dbname))
  40. tbl.append((QApplication.translate("DBManagerPlugin", "User:"),
  41. self.db.connector.user))
  42. tbl.append((QApplication.translate("DBManagerPlugin",
  43. "SQLite list tables cache:"),
  44. "Enabled" if self.db.connector.hasCache else
  45. "Unavailable"))
  46. return HtmlTable(tbl)
  47. def spatialInfo(self):
  48. ret = []
  49. info = self.db.connector.getSpatialInfo()
  50. if not info:
  51. return
  52. tbl = [
  53. (QApplication.translate("DBManagerPlugin", "Oracle Spatial:"),
  54. info[0])
  55. ]
  56. ret.append(HtmlTable(tbl))
  57. if not self.db.connector.has_geometry_columns:
  58. ret.append(
  59. HtmlParagraph(
  60. QApplication.translate(
  61. "DBManagerPlugin",
  62. "<warning> ALL_SDO_GEOM_METADATA"
  63. " view doesn't exist!\n"
  64. "This view is essential for many"
  65. " GIS applications for enumeration of tables.")))
  66. return ret
  67. def privilegesDetails(self):
  68. """ find if user can create schemas (CREATE ANY TABLE or something)"""
  69. # TODO
  70. return None
  71. class ORTableInfo(TableInfo):
  72. def __init__(self, table):
  73. self.table = table
  74. if not self.table.objectType:
  75. self.table.getType()
  76. if not self.table.comment:
  77. self.table.getComment()
  78. if not self.table.estimatedRowCount and not self.table.isView:
  79. self.table.refreshRowEstimation()
  80. if not self.table.creationDate:
  81. self.table.getDates()
  82. def generalInfo(self):
  83. ret = []
  84. # if the estimation is less than 100 rows, try to count them - it
  85. # shouldn't take long time
  86. if (not self.table.isView and
  87. not self.table.rowCount and
  88. self.table.estimatedRowCount < 100):
  89. # row count information is not displayed yet, so just block
  90. # table signals to avoid double refreshing
  91. # (infoViewer->refreshRowCount->tableChanged->infoViewer)
  92. self.table.blockSignals(True)
  93. self.table.refreshRowCount()
  94. self.table.blockSignals(False)
  95. relation_type = QApplication.translate(
  96. "DBManagerPlugin", self.table.objectType) if isinstance(self.table.objectType, str) else QApplication.translate("DBManagerPlugin", "Unknown")
  97. tbl = [
  98. (QApplication.translate("DBManagerPlugin", "Object type:"),
  99. relation_type),
  100. (QApplication.translate("DBManagerPlugin", "Owner:"),
  101. self.table.owner)
  102. ]
  103. if self.table.comment:
  104. tbl.append(
  105. (QApplication.translate(
  106. "DBManagerPlugin",
  107. "Comment:"),
  108. self.table.comment))
  109. # Estimated rows
  110. if not self.table.isView:
  111. tbl.append(
  112. (QApplication.translate(
  113. "DBManagerPlugin", "Rows (estimation):"),
  114. self.table.estimatedRowCount)
  115. )
  116. if self.table.rowCount is not None and self.table.rowCount >= 0:
  117. # Add a real count of rows
  118. tbl.append(
  119. (QApplication.translate("DBManagerPlugin", "Rows (counted):"),
  120. self.table.rowCount)
  121. )
  122. else:
  123. tbl.append(
  124. (QApplication.translate("DBManagerPlugin", "Rows (counted):"),
  125. 'Unknown (<a href="action:rows/recount">find out</a>)')
  126. )
  127. # Add creation and modification dates
  128. if self.table.creationDate:
  129. tbl.append(
  130. (QApplication.translate("DBManagerPlugin", "Creation Date:"),
  131. self.table.creationDate))
  132. if self.table.modificationDate:
  133. tbl.append(
  134. (QApplication.translate(
  135. "DBManagerPlugin", "Last Modification Date:"),
  136. self.table.modificationDate))
  137. # privileges
  138. # has the user access to this schema?
  139. schema_priv = self.table.database().connector.getSchemaPrivileges(
  140. self.table.schemaName()) if self.table.schema() else None
  141. if not schema_priv:
  142. pass
  143. elif schema_priv[1] is False: # no usage privileges on the schema
  144. tbl.append((QApplication.translate(
  145. "DBManagerPlugin", "Privileges:"),
  146. QApplication.translate(
  147. "DBManagerPlugin",
  148. "<warning> This user doesn't have usage privileges"
  149. " for this schema!")))
  150. else:
  151. table_priv = self.table.database().connector.getTablePrivileges(
  152. (self.table.schemaName(), self.table.name))
  153. privileges = []
  154. if table_priv[0]:
  155. privileges.append("select")
  156. if table_priv[1]:
  157. privileges.append("insert")
  158. if table_priv[2]:
  159. privileges.append("update")
  160. if table_priv[3]:
  161. privileges.append("delete")
  162. if len(privileges) > 0:
  163. priv_string = ", ".join(privileges)
  164. else:
  165. priv_string = QApplication.translate(
  166. "DBManagerPlugin",
  167. '<warning> This user has no privileges!')
  168. tbl.append(
  169. (QApplication.translate(
  170. "DBManagerPlugin", "Privileges:"),
  171. priv_string))
  172. ret.append(HtmlTable(tbl))
  173. if schema_priv and schema_priv[1]:
  174. if (table_priv[0] and
  175. not table_priv[1] and
  176. not table_priv[2] and
  177. not table_priv[3]):
  178. ret.append(
  179. HtmlParagraph(QApplication.translate(
  180. "DBManagerPlugin",
  181. "<warning> This user has read-only privileges.")))
  182. # primary key defined?
  183. if (not self.table.isView and
  184. self.table.objectType != "MATERIALIZED VIEW"):
  185. pk = [fld for fld in self.table.fields() if fld.primaryKey]
  186. if len(pk) <= 0:
  187. ret.append(
  188. HtmlParagraph(QApplication.translate(
  189. "DBManagerPlugin",
  190. "<warning> No primary key defined for this table!")))
  191. return ret
  192. def getSpatialInfo(self):
  193. ret = []
  194. info = self.db.connector.getSpatialInfo()
  195. if not info:
  196. return
  197. tbl = [
  198. (QApplication.translate(
  199. "DBManagerPlugin", "Library:"), info[0]) # ,
  200. ]
  201. ret.append(HtmlTable(tbl))
  202. if not self.db.connector.has_geometry_columns:
  203. ret.append(HtmlParagraph(
  204. QApplication.translate(
  205. "DBManagerPlugin",
  206. "<warning> ALL_SDO_GEOM_METADATA table doesn't exist!\n"
  207. "This table is essential for many GIS"
  208. " applications for enumeration of tables.")))
  209. return ret
  210. def fieldsDetails(self):
  211. tbl = []
  212. # define the table header
  213. header = (
  214. "#",
  215. QApplication.translate("DBManagerPlugin", "Name"),
  216. QApplication.translate("DBManagerPlugin", "Type"),
  217. QApplication.translate("DBManagerPlugin", "Length"),
  218. QApplication.translate("DBManagerPlugin", "Null"),
  219. QApplication.translate("DBManagerPlugin", "Default"),
  220. QApplication.translate("DBManagerPlugin", "Comment"))
  221. tbl.append(HtmlTableHeader(header))
  222. # add table contents
  223. for fld in self.table.fields():
  224. char_max_len = fld.charMaxLen if fld.charMaxLen else ""
  225. if fld.modifier:
  226. char_max_len = "{},{}".format(char_max_len, fld.modifier)
  227. is_null_txt = "N" if fld.notNull else "Y"
  228. # make primary key field underlined
  229. attrs = {"class": "underline"} if fld.primaryKey else None
  230. name = HtmlTableCol(fld.name, attrs)
  231. tbl.append(
  232. (fld.num, name, fld.type2String(), char_max_len,
  233. is_null_txt, fld.default2String(), fld.comment))
  234. return HtmlTable(tbl, {"class": "header"})
  235. def constraintsDetails(self):
  236. if not self.table.constraints():
  237. return None
  238. tbl = []
  239. # define the table header
  240. header = (QApplication.translate("DBManagerPlugin", "Name"),
  241. QApplication.translate("DBManagerPlugin", "Type"),
  242. QApplication.translate("DBManagerPlugin", "Column"),
  243. QApplication.translate("DBManagerPlugin", "Status"),
  244. QApplication.translate("DBManagerPlugin", "Validated"),
  245. QApplication.translate("DBManagerPlugin", "Generated"),
  246. QApplication.translate("DBManagerPlugin", "Check condition"),
  247. QApplication.translate("DBManagerPlugin", "Foreign Table"),
  248. QApplication.translate("DBManagerPlugin", "Foreign column"),
  249. QApplication.translate("DBManagerPlugin", "On Delete"))
  250. tbl.append(HtmlTableHeader(header))
  251. # add table contents
  252. for con in self.table.constraints():
  253. tbl.append((con.name, con.type2String(), con.column,
  254. con.status, con.validated, con.generated,
  255. con.checkSource, con.foreignTable,
  256. con.foreignKey, con.foreignOnDelete))
  257. return HtmlTable(tbl, {"class": "header"})
  258. def indexesDetails(self):
  259. if not self.table.indexes():
  260. return None
  261. tbl = []
  262. # define the table header
  263. header = (QApplication.translate("DBManagerPlugin", "Name"),
  264. QApplication.translate("DBManagerPlugin", "Column(s)"),
  265. QApplication.translate("DBManagerPlugin", "Index Type"),
  266. QApplication.translate("DBManagerPlugin", "Status"),
  267. QApplication.translate("DBManagerPlugin", "Last analyzed"),
  268. QApplication.translate("DBManagerPlugin", "Compression"),
  269. QApplication.translate("DBManagerPlugin", "Uniqueness"),
  270. QApplication.translate("DBManagerPlugin", "Action"))
  271. tbl.append(HtmlTableHeader(header))
  272. # add table contents
  273. for idx in self.table.indexes():
  274. # get the fields the index is defined on
  275. tbl.append((idx.name, idx.column, idx.indexType,
  276. idx.status, idx.analyzed, idx.compression,
  277. idx.isUnique,
  278. ('<a href="action:index/{}/rebuild">Rebuild'
  279. """</a>""".format(idx.name))))
  280. return HtmlTable(tbl, {"class": "header"})
  281. def triggersDetails(self):
  282. if not self.table.triggers():
  283. return None
  284. ret = []
  285. tbl = []
  286. # define the table header
  287. header = (
  288. QApplication.translate("DBManagerPlugin", "Name"),
  289. QApplication.translate("DBManagerPlugin", "Event"),
  290. QApplication.translate("DBManagerPlugin", "Type"),
  291. QApplication.translate("DBManagerPlugin", "Enabled"))
  292. tbl.append(HtmlTableHeader(header))
  293. # add table contents
  294. for trig in self.table.triggers():
  295. name = ("""{0} (<a href="action:trigger/"""
  296. """{0}/{1}">{1}</a>)""".format(trig.name, "delete"))
  297. if trig.enabled == "ENABLED":
  298. enabled, action = (
  299. QApplication.translate("DBManagerPlugin", "Yes"),
  300. "disable")
  301. else:
  302. enabled, action = (
  303. QApplication.translate("DBManagerPlugin", "No"),
  304. "enable")
  305. txt_enabled = ("""{0} (<a href="action:trigger/"""
  306. """{1}/{2}">{2}</a>)""".format(
  307. enabled, trig.name, action))
  308. tbl.append((name, trig.event, trig.type, txt_enabled))
  309. ret.append(HtmlTable(tbl, {"class": "header"}))
  310. ret.append(
  311. HtmlParagraph(
  312. QApplication.translate(
  313. "DBManagerPlugin",
  314. '<a href="action:triggers/enable">'
  315. 'Enable all triggers</a> / '
  316. '<a href="action:triggers/disable">'
  317. 'Disable all triggers</a>')))
  318. return ret
  319. def getTableInfo(self):
  320. ret = []
  321. general_info = self.generalInfo()
  322. if not general_info:
  323. pass
  324. else:
  325. ret.append(
  326. HtmlSection(
  327. QApplication.translate(
  328. "DBManagerPlugin", 'General info'),
  329. general_info))
  330. # spatial info
  331. spatial_info = self.spatialInfo()
  332. if not spatial_info:
  333. pass
  334. else:
  335. spatial_info = HtmlContent(spatial_info)
  336. if not spatial_info.hasContents():
  337. spatial_info = QApplication.translate(
  338. "DBManagerPlugin",
  339. '<warning> This is not a spatial table.')
  340. ret.append(
  341. HtmlSection(
  342. self.table.database().connection().typeNameString(),
  343. spatial_info))
  344. # fields
  345. fields_details = self.fieldsDetails()
  346. if not fields_details:
  347. pass
  348. else:
  349. ret.append(
  350. HtmlSection(
  351. QApplication.translate(
  352. "DBManagerPlugin",
  353. 'Fields'),
  354. fields_details))
  355. # constraints
  356. constraints_details = self.constraintsDetails()
  357. if not constraints_details:
  358. pass
  359. else:
  360. ret.append(
  361. HtmlSection(
  362. QApplication.translate(
  363. "DBManagerPlugin",
  364. 'Constraints'),
  365. constraints_details))
  366. # indexes
  367. indexes_details = self.indexesDetails()
  368. if not indexes_details:
  369. pass
  370. else:
  371. ret.append(
  372. HtmlSection(
  373. QApplication.translate(
  374. "DBManagerPlugin",
  375. 'Indexes'),
  376. indexes_details))
  377. # triggers
  378. triggers_details = self.triggersDetails()
  379. if not triggers_details:
  380. pass
  381. else:
  382. ret.append(
  383. HtmlSection(
  384. QApplication.translate(
  385. "DBManagerPlugin",
  386. 'Triggers'),
  387. triggers_details))
  388. if self.table.objectType == "MATERIALIZED VIEW":
  389. mview_info = self.getMViewInfo()
  390. ret.append(
  391. HtmlSection(
  392. QApplication.translate(
  393. "DBManagerPlugin",
  394. 'Materialized View information'),
  395. mview_info))
  396. return ret
  397. def getMViewInfo(self):
  398. """If the table is a materialized view, grab more
  399. information...
  400. """
  401. ret = []
  402. tbl = []
  403. values = self.table.getMViewInfo()
  404. tbl.append((QApplication.translate("DBManagerPlugin",
  405. "Refresh Mode:"),
  406. values[0]))
  407. tbl.append((QApplication.translate("DBManagerPlugin",
  408. "Refresh Method:"),
  409. values[1]))
  410. tbl.append((QApplication.translate("DBManagerPlugin",
  411. "Build Mode:"),
  412. values[2]))
  413. tbl.append((QApplication.translate("DBManagerPlugin",
  414. "Last Refresh Date:"),
  415. values[5]))
  416. tbl.append((QApplication.translate("DBManagerPlugin",
  417. "Last Refresh Type:"),
  418. values[4]))
  419. tbl.append((QApplication.translate("DBManagerPlugin",
  420. "Fast Refreshable:"),
  421. values[3]))
  422. tbl.append((QApplication.translate("DBManagerPlugin",
  423. "Staleness:"),
  424. values[6]))
  425. tbl.append((QApplication.translate("DBManagerPlugin",
  426. "Stale since:"),
  427. values[7]))
  428. tbl.append((QApplication.translate("DBManagerPlugin",
  429. "Compile State:"),
  430. values[8]))
  431. tbl.append((QApplication.translate("DBManagerPlugin",
  432. "Use no index:"),
  433. values[9]))
  434. tbl.append(('<a href="action:mview/refresh">{}</a>'.format(
  435. QApplication.translate("DBManagerPlugin", "Refresh the materialized view")),
  436. ""))
  437. ret.append(HtmlTable(tbl))
  438. return ret
  439. def getViewInfo(self):
  440. """If the table is a view or a materialized view, add the
  441. definition of the view.
  442. """
  443. if self.table.objectType not in ["VIEW", "MATERIALIZED VIEW"]:
  444. return []
  445. ret = self.getTableInfo()
  446. # view definition
  447. view_def = self.table.getDefinition()
  448. # Syntax highlight
  449. lexer = get_lexer_by_name("sql")
  450. formatter = HtmlFormatter(
  451. linenos=True, cssclass="source", noclasses=True)
  452. result = highlight(view_def, lexer, formatter)
  453. if view_def:
  454. if self.table.objectType == "VIEW":
  455. title = "View Definition"
  456. else:
  457. title = "Materialized View Definition"
  458. ret.append(
  459. HtmlSection(
  460. QApplication.translate("DBManagerPlugin", title),
  461. result))
  462. return ret
  463. def toHtml(self):
  464. if self.table.objectType in ["VIEW", "MATERIALIZED VIEW"]:
  465. ret = self.getViewInfo()
  466. else:
  467. ret = self.getTableInfo()
  468. return HtmlContent(ret).toHtml()
  469. class ORVectorTableInfo(ORTableInfo, VectorTableInfo):
  470. def __init__(self, table):
  471. VectorTableInfo.__init__(self, table)
  472. ORTableInfo.__init__(self, table)
  473. def spatialInfo(self):
  474. ret = []
  475. if not self.table.geomType:
  476. return ret
  477. tbl = [
  478. (QApplication.translate("DBManagerPlugin", "Column:"),
  479. self.table.geomColumn),
  480. (QApplication.translate("DBManagerPlugin", "Geometry:"),
  481. self.table.geomType),
  482. (QApplication.translate("DBManagerPlugin",
  483. "QGIS Geometry type:"),
  484. QgsWkbTypes.displayString(self.table.wkbType))
  485. ]
  486. # only if we have info from geometry_columns
  487. if self.table.geomDim:
  488. tbl.append(
  489. (QApplication.translate(
  490. "DBManagerPlugin",
  491. "Dimension:"),
  492. self.table.geomDim))
  493. srid = self.table.srid if self.table.srid else -1
  494. if srid != -1:
  495. sr_info = (
  496. self.table.database().connector.getSpatialRefInfo(srid))
  497. else:
  498. sr_info = QApplication.translate("DBManagerPlugin",
  499. "Undefined")
  500. if sr_info:
  501. tbl.append(
  502. (QApplication.translate(
  503. "DBManagerPlugin", "Spatial ref:"),
  504. "{} ({})".format(sr_info, srid)))
  505. # estimated extent
  506. if not self.table.estimatedExtent:
  507. # estimated extent information is not displayed yet, so just block
  508. # table signals to avoid double refreshing
  509. # (infoViewer->refreshEstimatedExtent->tableChanged->infoViewer)
  510. self.table.blockSignals(True)
  511. self.table.refreshTableEstimatedExtent()
  512. self.table.blockSignals(False)
  513. if self.table.estimatedExtent:
  514. estimated_extent_str = ("{:.9f}, {:.9f} - {:.9f}, "
  515. "{:.9f}".format(
  516. *self.table.estimatedExtent))
  517. tbl.append(
  518. (QApplication.translate(
  519. "DBManagerPlugin", "Estimated extent:"),
  520. estimated_extent_str))
  521. # extent
  522. extent_str = None
  523. if self.table.extent and len(self.table.extent) == 4:
  524. extent_str = ("{:.9f}, {:.9f} - {:.9f}, "
  525. "{:.9f}".format(*self.table.extent))
  526. elif (self.table.rowCount is not None and self.table.rowCount > 0) or (self.table.estimatedRowCount is not None and self.table.estimatedRowCount > 0):
  527. # Can't calculate an extent on empty layer
  528. extent_str = QApplication.translate(
  529. "DBManagerPlugin",
  530. '(unknown) (<a href="action:extent/get">find out</a>)')
  531. if extent_str:
  532. tbl.append(
  533. (QApplication.translate(
  534. "DBManagerPlugin", "Extent:"),
  535. extent_str))
  536. ret.append(HtmlTable(tbl))
  537. # Handle extent update metadata
  538. if (self.table.extent and
  539. self.table.extent != self.table.estimatedExtent and
  540. self.table.canUpdateMetadata()):
  541. ret.append(
  542. HtmlParagraph(
  543. QApplication.translate(
  544. "DBManagerPlugin",
  545. '<warning> Metadata extent is different from'
  546. ' real extent. You should <a href="action:extent'
  547. '/update">update it</a>!')))
  548. # is there an entry in geometry_columns?
  549. if self.table.geomType.lower() == 'geometry':
  550. ret.append(
  551. HtmlParagraph(
  552. QApplication.translate(
  553. "DBManagerPlugin",
  554. "<warning> There is no entry in geometry_columns!")))
  555. # find out whether the geometry column has spatial index on it
  556. if not self.table.isView:
  557. if not self.table.hasSpatialIndex():
  558. ret.append(
  559. HtmlParagraph(
  560. QApplication.translate(
  561. "DBManagerPlugin",
  562. '<warning> No spatial index defined (<a href='
  563. '"action:spatialindex/create">'
  564. 'create it</a>).')))
  565. return ret