connector.py 53 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262
  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. The content of this file is based on
  9. - PG_Manager by Martin Dobias <wonder.sk@gmail.com> (GPLv2 license)
  10. ***************************************************************************/
  11. /***************************************************************************
  12. * *
  13. * This program is free software; you can redistribute it and/or modify *
  14. * it under the terms of the GNU General Public License as published by *
  15. * the Free Software Foundation; either version 2 of the License, or *
  16. * (at your option) any later version. *
  17. * *
  18. ***************************************************************************/
  19. """
  20. from functools import cmp_to_key
  21. from qgis.PyQt.QtCore import (
  22. QRegExp,
  23. QFile,
  24. QVariant,
  25. QDateTime,
  26. QTime,
  27. QDate,
  28. Qt,
  29. )
  30. from qgis.core import (
  31. Qgis,
  32. QgsCoordinateReferenceSystem,
  33. QgsVectorLayer,
  34. QgsDataSourceUri,
  35. QgsProviderRegistry,
  36. QgsProviderConnectionException,
  37. QgsFeedback,
  38. )
  39. from ..connector import DBConnector
  40. from ..plugin import DbError, Table
  41. import os
  42. import re
  43. import psycopg2
  44. import psycopg2.extensions
  45. # use unicode!
  46. psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
  47. psycopg2.extensions.register_type(psycopg2.extensions.UNICODEARRAY)
  48. def classFactory():
  49. return PostGisDBConnector
  50. class CursorAdapter():
  51. def _debug(self, msg):
  52. pass
  53. # print("XXX CursorAdapter[" + hex(id(self)) + "]: " + msg)
  54. def __init__(self, connection, sql=None, feedback=None):
  55. self._debug("Created with sql: " + str(sql))
  56. self.connection = connection
  57. self.sql = sql
  58. self.result = None
  59. self.cursor = 0
  60. self.feedback = feedback
  61. self.closed = False
  62. if (self.sql is not None):
  63. self._execute()
  64. def _toStrResultSet(self, res):
  65. newres = []
  66. for rec in res:
  67. newrec = []
  68. for col in rec:
  69. if type(col) == type(QVariant(None)): # noqa
  70. if (str(col) == 'NULL'):
  71. col = None
  72. else:
  73. col = str(col) # force to string
  74. if isinstance(col, QDateTime) or isinstance(col, QDate) or isinstance(col, QTime):
  75. col = col.toString(Qt.ISODate)
  76. newrec.append(col)
  77. newres.append(newrec)
  78. return newres
  79. def _execute(self, sql=None):
  80. if (sql is None or self.sql == sql) and self.result is not None:
  81. return
  82. if (sql is not None):
  83. self.sql = sql
  84. if (self.sql is None):
  85. return
  86. self._debug("execute called with sql " + self.sql)
  87. try:
  88. result = self.connection.execSql(self.sql, feedback=self.feedback)
  89. self._description = [] # reset description
  90. self.result = self._toStrResultSet(result.rows())
  91. for c in result.columns():
  92. self._description.append([
  93. c, # name
  94. '', # type_code
  95. -1, # display_size
  96. -1, # internal_size
  97. -1, # precision
  98. None, # scale
  99. True # null_ok
  100. ])
  101. except QgsProviderConnectionException as e:
  102. self._description = None
  103. raise DbError(e, self.sql)
  104. self._debug("execute returned " + str(len(self.result)) + " rows")
  105. self.cursor = 0
  106. @property
  107. def description(self):
  108. """Returns columns description, it should be already set by _execute"""
  109. if self._description is None:
  110. self._description = []
  111. if re.match('^SHOW', self.sql.strip().upper()):
  112. try:
  113. count = len(self.connection.executeSql(self.sql)[0])
  114. except QgsProviderConnectionException:
  115. count = 1
  116. for i in range(count):
  117. self._description.append([
  118. '', # name
  119. '', # type_code
  120. -1, # display_size
  121. -1, # internal_size
  122. -1, # precision
  123. None, # scale
  124. True # null_ok
  125. ])
  126. else:
  127. uri = QgsDataSourceUri(self.connection.uri())
  128. # TODO: make this part provider-agnostic
  129. sql = self.sql if self.sql.upper().find(' LIMIT ') >= 0 else self.sql + ' LIMIT 1 '
  130. uri.setTable('(SELECT row_number() OVER () AS __rid__, * FROM (' + sql + ') as foo)')
  131. uri.setKeyColumn('__rid__')
  132. uri.setParam('checkPrimaryKeyUnicity', '0')
  133. # TODO: fetch provider name from connection (QgsAbstractConnectionProvider)
  134. # TODO: re-use the VectorLayer for fetching rows in batch mode
  135. vl = QgsVectorLayer(uri.uri(False), 'dbmanager_cursor', 'postgres')
  136. fields = vl.fields()
  137. for i in range(1, len(fields)): # skip first field (__rid__)
  138. f = fields[i]
  139. self._description.append([
  140. f.name(), # name
  141. f.type(), # type_code
  142. f.length(), # display_size
  143. f.length(), # internal_size
  144. f.precision(), # precision
  145. None, # scale
  146. True # null_ok
  147. ])
  148. self._debug("get_description returned " + str(len(self._description)) + " cols")
  149. return self._description
  150. def fetchone(self):
  151. self._execute()
  152. if len(self.result) - self.cursor:
  153. res = self.result[self.cursor]
  154. self.cursor += 1
  155. return res
  156. return None
  157. def fetchmany(self, size):
  158. self._execute()
  159. if self.result is None:
  160. self._debug("fetchmany: none result after _execute (self.sql is " + str(self.sql) + ", returning []")
  161. return []
  162. leftover = len(self.result) - self.cursor
  163. self._debug("fetchmany: cursor: " + str(self.cursor) + " leftover: " + str(leftover) + " requested: " + str(size))
  164. if leftover < 1:
  165. return []
  166. if size > leftover:
  167. size = leftover
  168. stop = self.cursor + size
  169. res = self.result[self.cursor:stop]
  170. self.cursor = stop
  171. self._debug("fetchmany: new cursor: " + str(self.cursor) + " reslen: " + str(len(self.result)))
  172. return res
  173. def fetchall(self):
  174. self._execute()
  175. res = self.result[self.cursor:]
  176. self.cursor = len(self.result)
  177. return res
  178. def scroll(self, pos, mode='relative'):
  179. self._execute()
  180. if pos < 0:
  181. self._debug("scroll pos is negative: " + str(pos))
  182. if mode == 'relative':
  183. self.cursor = self.cursor + pos
  184. elif mode == 'absolute':
  185. self.cursor = pos
  186. def close(self):
  187. self.result = None
  188. self.closed = True
  189. class PostGisDBConnector(DBConnector):
  190. def __init__(self, uri, connection):
  191. """Creates a new PostgreSQL connector
  192. :param uri: data source URI
  193. :type uri: QgsDataSourceUri
  194. :param connection: the plugin parent instance
  195. :type connection: PostGisDBPlugin
  196. """
  197. DBConnector.__init__(self, uri)
  198. username = uri.username() or os.environ.get('PGUSER')
  199. # Do not get db and user names from the env if service is used
  200. if not uri.service():
  201. if username is None:
  202. username = os.environ.get('USER')
  203. self.dbname = uri.database() or os.environ.get('PGDATABASE') or username
  204. uri.setDatabase(self.dbname)
  205. # self.connName = connName
  206. # self.user = uri.username() or os.environ.get('USER')
  207. # self.passwd = uri.password()
  208. self.host = uri.host()
  209. md = QgsProviderRegistry.instance().providerMetadata(connection.providerName())
  210. # QgsAbstractDatabaseProviderConnection instance
  211. self.core_connection = md.findConnection(connection.connectionName())
  212. if self.core_connection is None:
  213. self.core_connection = md.createConnection(uri.uri(), {})
  214. c = self._execute(None, "SELECT current_user,current_database()")
  215. self.user, self.dbname = self._fetchone(c)
  216. self._close_cursor(c)
  217. self._checkSpatial()
  218. self._checkRaster()
  219. self._checkGeometryColumnsTable()
  220. self._checkRasterColumnsTable()
  221. self.feedback = None
  222. def _connectionInfo(self):
  223. return str(self.uri().connectionInfo(True))
  224. def _clearSslTempCertsIfAny(self, connectionInfo):
  225. # remove certs (if any) of the connectionInfo
  226. expandedUri = QgsDataSourceUri(connectionInfo)
  227. def removeCert(certFile):
  228. certFile = certFile.replace("'", "")
  229. file = QFile(certFile)
  230. # set permission to allow removing on Win.
  231. # On linux and Mac if file is set with QFile::>ReadUser
  232. # does not create problem removing certs
  233. if not file.setPermissions(QFile.WriteOwner):
  234. raise Exception('Cannot change permissions on {}: error code: {}'.format(file.fileName(), file.error()))
  235. if not file.remove():
  236. raise Exception('Cannot remove {}: error code: {}'.format(file.fileName(), file.error()))
  237. sslCertFile = expandedUri.param("sslcert")
  238. if sslCertFile:
  239. removeCert(sslCertFile)
  240. sslKeyFile = expandedUri.param("sslkey")
  241. if sslKeyFile:
  242. removeCert(sslKeyFile)
  243. sslCAFile = expandedUri.param("sslrootcert")
  244. if sslCAFile:
  245. removeCert(sslCAFile)
  246. def _checkSpatial(self):
  247. """ check whether postgis_version is present in catalog """
  248. c = self._execute(None, "SELECT COUNT(*) FROM pg_proc WHERE proname = 'postgis_version'")
  249. self.has_spatial = self._fetchone(c)[0] > 0
  250. self._close_cursor(c)
  251. return self.has_spatial
  252. def _checkRaster(self):
  253. """ check whether postgis_version is present in catalog """
  254. c = self._execute(None, "SELECT COUNT(*) FROM pg_proc WHERE proname = 'postgis_raster_lib_version'")
  255. self.has_raster = self._fetchone(c)[0] > 0
  256. self._close_cursor(c)
  257. return self.has_raster
  258. def _checkGeometryColumnsTable(self):
  259. c = self._execute(None,
  260. "SELECT relkind = 'v' OR relkind = 'm' FROM pg_class WHERE relname = 'geometry_columns' AND relkind IN ('v', 'r', 'm', 'p')")
  261. res = self._fetchone(c)
  262. self._close_cursor(c)
  263. self.has_geometry_columns = (res is not None and len(res) != 0)
  264. if not self.has_geometry_columns:
  265. self.has_geometry_columns_access = self.is_geometry_columns_view = False
  266. else:
  267. self.is_geometry_columns_view = res[0]
  268. # find out whether has privileges to access geometry_columns table
  269. priv = self.getTablePrivileges('geometry_columns')
  270. self.has_geometry_columns_access = priv[0]
  271. return self.has_geometry_columns
  272. def _checkRasterColumnsTable(self):
  273. c = self._execute(None,
  274. "SELECT relkind = 'v' OR relkind = 'm' FROM pg_class WHERE relname = 'raster_columns' AND relkind IN ('v', 'r', 'm', 'p')")
  275. res = self._fetchone(c)
  276. self._close_cursor(c)
  277. self.has_raster_columns = (res is not None and len(res) != 0)
  278. if not self.has_raster_columns:
  279. self.has_raster_columns_access = self.is_raster_columns_view = False
  280. else:
  281. self.is_raster_columns_view = res[0]
  282. # find out whether has privileges to access geometry_columns table
  283. self.has_raster_columns_access = self.getTablePrivileges('raster_columns')[0]
  284. return self.has_raster_columns
  285. def cancel(self):
  286. if self.connection:
  287. self.connection.cancel()
  288. if self.core_connection:
  289. self.feedback.cancel()
  290. def getInfo(self):
  291. c = self._execute(None, "SELECT version()")
  292. res = self._fetchone(c)
  293. self._close_cursor(c)
  294. return res
  295. def getPsqlVersion(self):
  296. regex = r"^PostgreSQL\s([0-9]{1,2})"
  297. match = re.match(regex, self.getInfo()[0])
  298. if match:
  299. return int(match.group(1))
  300. raise DbError(f"Unknown PostgreSQL version: {self.getInfo()[0]}")
  301. def getSpatialInfo(self):
  302. """ returns tuple about PostGIS support:
  303. - lib version
  304. - geos version
  305. - proj version
  306. - installed scripts version
  307. - released scripts version
  308. """
  309. if not self.has_spatial:
  310. return
  311. try:
  312. c = self._execute(None,
  313. "SELECT postgis_lib_version(), postgis_geos_version(), postgis_proj_version(), postgis_scripts_installed(), postgis_scripts_released()")
  314. except DbError:
  315. return
  316. res = self._fetchone(c)
  317. self._close_cursor(c)
  318. return res
  319. def hasSpatialSupport(self):
  320. return self.has_spatial
  321. def hasRasterSupport(self):
  322. return self.has_raster
  323. def hasCustomQuerySupport(self):
  324. return Qgis.QGIS_VERSION[0:3] >= "1.5"
  325. def hasTableColumnEditingSupport(self):
  326. return True
  327. def hasCreateSpatialViewSupport(self):
  328. return True
  329. def fieldTypes(self):
  330. return [
  331. "integer", "bigint", "smallint", # integers
  332. "serial", "bigserial", # auto-incrementing ints
  333. "real", "double precision", "numeric", # floats
  334. "varchar", "varchar(255)", "char(20)", "text", # strings
  335. "date", "time", "timestamp", # date/time
  336. "boolean" # bool
  337. ]
  338. def getDatabasePrivileges(self):
  339. """ db privileges: (can create schemas, can create temp. tables) """
  340. sql = "SELECT has_database_privilege(current_database(), 'CREATE'), has_database_privilege(current_database(), 'TEMP')"
  341. c = self._execute(None, sql)
  342. res = self._fetchone(c)
  343. self._close_cursor(c)
  344. return res
  345. def getSchemaPrivileges(self, schema):
  346. """ schema privileges: (can create new objects, can access objects in schema) """
  347. schema = 'current_schema()' if schema is None else self.quoteString(schema)
  348. sql = "SELECT has_schema_privilege(%(s)s, 'CREATE'), has_schema_privilege(%(s)s, 'USAGE')" % {'s': schema}
  349. c = self._execute(None, sql)
  350. res = self._fetchone(c)
  351. self._close_cursor(c)
  352. return res
  353. def getTablePrivileges(self, table):
  354. """ table privileges: (select, insert, update, delete) """
  355. schema, tablename = self.getSchemaTableName(table)
  356. schema_priv = self.getSchemaPrivileges(schema)
  357. if not schema_priv[1]:
  358. return
  359. t = self.quoteId(table)
  360. sql = """SELECT has_table_privilege(%(t)s, 'SELECT'), has_table_privilege(%(t)s, 'INSERT'),
  361. has_table_privilege(%(t)s, 'UPDATE'), has_table_privilege(%(t)s, 'DELETE')""" % {
  362. 't': self.quoteString(t)}
  363. c = self._execute(None, sql)
  364. res = self._fetchone(c)
  365. self._close_cursor(c)
  366. return res
  367. def getSchemas(self):
  368. """ get list of schemas in tuples: (oid, name, owner, perms) """
  369. sql = "SELECT oid, nspname, pg_get_userbyid(nspowner), nspacl, pg_catalog.obj_description(oid) FROM pg_namespace WHERE nspname !~ '^pg_' AND nspname != 'information_schema' ORDER BY nspname"
  370. c = self._execute(None, sql)
  371. res = self._fetchall(c)
  372. self._close_cursor(c)
  373. return res
  374. def getTables(self, schema=None, add_sys_tables=False):
  375. """ get list of tables """
  376. tablenames = []
  377. items = []
  378. sys_tables = ["spatial_ref_sys", "geography_columns", "geometry_columns",
  379. "raster_columns", "raster_overviews"]
  380. try:
  381. vectors = self.getVectorTables(schema)
  382. for tbl in vectors:
  383. if not add_sys_tables and tbl[1] in sys_tables and tbl[2] in ['', 'public']:
  384. continue
  385. tablenames.append((tbl[2], tbl[1]))
  386. items.append(tbl)
  387. except DbError:
  388. pass
  389. try:
  390. rasters = self.getRasterTables(schema)
  391. for tbl in rasters:
  392. if not add_sys_tables and tbl[1] in sys_tables and tbl[2] in ['', 'public']:
  393. continue
  394. tablenames.append((tbl[2], tbl[1]))
  395. items.append(tbl)
  396. except DbError:
  397. pass
  398. sys_tables = ["spatial_ref_sys", "geography_columns", "geometry_columns",
  399. "raster_columns", "raster_overviews"]
  400. if schema:
  401. schema_where = " AND nspname = %s " % self.quoteString(schema)
  402. else:
  403. schema_where = " AND (nspname != 'information_schema' AND nspname !~ 'pg_') "
  404. # get all tables and views
  405. sql = """SELECT
  406. cla.relname, nsp.nspname, cla.relkind,
  407. pg_get_userbyid(relowner), reltuples, relpages,
  408. pg_catalog.obj_description(cla.oid)
  409. FROM pg_class AS cla
  410. JOIN pg_namespace AS nsp ON nsp.oid = cla.relnamespace
  411. WHERE cla.relkind IN ('v', 'r', 'm', 'p') """ + schema_where + """
  412. ORDER BY nsp.nspname, cla.relname"""
  413. c = self._execute(None, sql)
  414. for tbl in self._fetchall(c):
  415. if tablenames.count((tbl[1], tbl[0])) <= 0:
  416. item = list(tbl)
  417. item.insert(0, Table.TableType)
  418. items.append(item)
  419. self._close_cursor(c)
  420. return sorted(items, key=cmp_to_key(lambda x, y: (x[1] > y[1]) - (x[1] < y[1])))
  421. def getVectorTables(self, schema=None):
  422. """ get list of table with a geometry column
  423. it returns:
  424. name (table name)
  425. namespace (schema)
  426. type = 'view' (is a view?)
  427. owner
  428. tuples
  429. pages
  430. geometry_column:
  431. f_geometry_column (or pg_attribute.attname, the geometry column name)
  432. type (or pg_attribute.atttypid::regtype, the geometry column type name)
  433. coord_dimension
  434. srid
  435. """
  436. if not self.has_spatial:
  437. return []
  438. if schema:
  439. schema_where = " AND nspname = %s " % self.quoteString(schema)
  440. else:
  441. schema_where = " AND (nspname != 'information_schema' AND nspname !~ 'pg_') "
  442. geometry_column_from = ""
  443. geometry_fields_select = """att.attname,
  444. textin(regtypeout(att.atttypid::regtype)),
  445. NULL, NULL"""
  446. if self.has_geometry_columns and self.has_geometry_columns_access:
  447. geometry_column_from = """LEFT OUTER JOIN geometry_columns AS geo ON
  448. cla.relname = geo.f_table_name AND nsp.nspname = f_table_schema AND
  449. lower(att.attname) = lower(f_geometry_column)"""
  450. geometry_fields_select = """CASE WHEN geo.f_geometry_column IS NOT NULL THEN geo.f_geometry_column ELSE att.attname END,
  451. CASE WHEN geo.type IS NOT NULL THEN geo.type ELSE textin(regtypeout(att.atttypid::regtype)) END,
  452. geo.coord_dimension, geo.srid"""
  453. # discovery of all tables and whether they contain a geometry column
  454. sql = """SELECT
  455. cla.relname, nsp.nspname, cla.relkind,
  456. pg_get_userbyid(relowner), cla.reltuples, cla.relpages,
  457. pg_catalog.obj_description(cla.oid),
  458. """ + geometry_fields_select + """
  459. FROM pg_class AS cla
  460. JOIN pg_namespace AS nsp ON
  461. nsp.oid = cla.relnamespace
  462. JOIN pg_attribute AS att ON
  463. att.attrelid = cla.oid AND
  464. att.atttypid = 'geometry'::regtype OR
  465. att.atttypid IN (SELECT oid FROM pg_type WHERE typbasetype='geometry'::regtype )
  466. """ + geometry_column_from + """
  467. WHERE cla.relkind IN ('v', 'r', 'm', 'p') """ + schema_where + """
  468. ORDER BY nsp.nspname, cla.relname, att.attname"""
  469. items = []
  470. c = self._execute(None, sql)
  471. for i, tbl in enumerate(self._fetchall(c)):
  472. item = list(tbl)
  473. item.insert(0, Table.VectorType)
  474. items.append(item)
  475. self._close_cursor(c)
  476. return items
  477. def getRasterTables(self, schema=None):
  478. """ get list of table with a raster column
  479. it returns:
  480. name (table name)
  481. namespace (schema)
  482. type = 'view' (is a view?)
  483. owner
  484. tuples
  485. pages
  486. raster_column:
  487. r_raster_column (or pg_attribute.attname, the raster column name)
  488. pixel type
  489. block size
  490. internal or external
  491. srid
  492. """
  493. if not self.has_spatial:
  494. return []
  495. if not self.has_raster:
  496. return []
  497. if schema:
  498. schema_where = " AND nspname = %s " % self.quoteString(schema)
  499. else:
  500. schema_where = " AND (nspname != 'information_schema' AND nspname !~ 'pg_') "
  501. raster_column_from = ""
  502. raster_fields_select = """att.attname, NULL, NULL, NULL, NULL, NULL"""
  503. if self.has_raster_columns and self.has_raster_columns_access:
  504. raster_column_from = """LEFT OUTER JOIN raster_columns AS rast ON
  505. cla.relname = rast.r_table_name AND nsp.nspname = r_table_schema AND
  506. lower(att.attname) = lower(r_raster_column)"""
  507. raster_fields_select = """CASE WHEN rast.r_raster_column IS NOT NULL THEN rast.r_raster_column ELSE att.attname END,
  508. rast.pixel_types,
  509. rast.scale_x,
  510. rast.scale_y,
  511. rast.out_db,
  512. rast.srid"""
  513. # discovery of all tables and whether they contain a raster column
  514. sql = """SELECT
  515. cla.relname, nsp.nspname, cla.relkind,
  516. pg_get_userbyid(relowner), cla.reltuples, cla.relpages,
  517. pg_catalog.obj_description(cla.oid),
  518. """ + raster_fields_select + """
  519. FROM pg_class AS cla
  520. JOIN pg_namespace AS nsp ON
  521. nsp.oid = cla.relnamespace
  522. JOIN pg_attribute AS att ON
  523. att.attrelid = cla.oid AND
  524. att.atttypid = 'raster'::regtype OR
  525. att.atttypid IN (SELECT oid FROM pg_type WHERE typbasetype='raster'::regtype )
  526. """ + raster_column_from + """
  527. WHERE cla.relkind IN ('v', 'r', 'm', 'p') """ + schema_where + """
  528. ORDER BY nsp.nspname, cla.relname, att.attname"""
  529. items = []
  530. c = self._execute(None, sql)
  531. for i, tbl in enumerate(self._fetchall(c)):
  532. item = list(tbl)
  533. item.insert(0, Table.RasterType)
  534. items.append(item)
  535. self._close_cursor(c)
  536. return items
  537. def getTableRowCount(self, table):
  538. c = self._execute(None, "SELECT COUNT(*) FROM %s" % self.quoteId(table))
  539. res = self._fetchone(c)[0]
  540. self._close_cursor(c)
  541. return res
  542. def getTableFields(self, table):
  543. """ return list of columns in table """
  544. schema, tablename = self.getSchemaTableName(table)
  545. schema_where = " AND nspname=%s " % self.quoteString(schema) if schema is not None else ""
  546. version_number = self.getPsqlVersion()
  547. ad_col_name = 'adsrc' if version_number < 12 else 'adbin'
  548. sql = """SELECT a.attnum AS ordinal_position,
  549. a.attname AS column_name,
  550. t.typname AS data_type,
  551. a.attlen AS char_max_len,
  552. a.atttypmod AS modifier,
  553. a.attnotnull AS notnull,
  554. a.atthasdef AS hasdefault,
  555. adef.%s AS default_value,
  556. pg_catalog.format_type(a.atttypid,a.atttypmod) AS formatted_type
  557. FROM pg_class c
  558. JOIN pg_attribute a ON a.attrelid = c.oid
  559. JOIN pg_type t ON a.atttypid = t.oid
  560. JOIN pg_namespace nsp ON c.relnamespace = nsp.oid
  561. LEFT JOIN pg_attrdef adef ON adef.adrelid = a.attrelid AND adef.adnum = a.attnum
  562. WHERE
  563. a.attnum > 0 AND c.relname=%s %s
  564. ORDER BY a.attnum""" % (ad_col_name, self.quoteString(tablename), schema_where)
  565. c = self._execute(None, sql)
  566. res = self._fetchall(c)
  567. self._close_cursor(c)
  568. return res
  569. def getTableIndexes(self, table):
  570. """ get info about table's indexes. ignore primary key constraint index, they get listed in constraints """
  571. schema, tablename = self.getSchemaTableName(table)
  572. schema_where = " AND nspname=%s " % self.quoteString(schema) if schema is not None else ""
  573. sql = """SELECT idxcls.relname, indkey, indisunique = 't'
  574. FROM pg_index JOIN pg_class ON pg_index.indrelid=pg_class.oid
  575. JOIN pg_class AS idxcls ON pg_index.indexrelid=idxcls.oid
  576. JOIN pg_namespace nsp ON pg_class.relnamespace = nsp.oid
  577. WHERE pg_class.relname=%s %s
  578. AND indisprimary != 't' """ % (
  579. self.quoteString(tablename), schema_where)
  580. c = self._execute(None, sql)
  581. res = self._fetchall(c)
  582. self._close_cursor(c)
  583. return res
  584. def getTableConstraints(self, table):
  585. schema, tablename = self.getSchemaTableName(table)
  586. schema_where = " AND nspname=%s " % self.quoteString(schema) if schema is not None else ""
  587. version_number = self.getPsqlVersion()
  588. con_col_name = 'consrc' if version_number < 12 else 'conbin'
  589. # In the query below, we exclude rows where pg_constraint.contype whose values are equal to 't'
  590. # because 't' describes a CONSTRAINT TRIGGER, which is not really a constraint in the traditional
  591. # sense, but a special type of trigger, and an extension to the SQL standard.
  592. sql = """SELECT c.conname, c.contype, c.condeferrable, c.condeferred, array_to_string(c.conkey, ' '), c.%s,
  593. t2.relname, c.confupdtype, c.confdeltype, c.confmatchtype, array_to_string(c.confkey, ' ') FROM pg_constraint c
  594. LEFT JOIN pg_class t ON c.conrelid = t.oid
  595. LEFT JOIN pg_class t2 ON c.confrelid = t2.oid
  596. JOIN pg_namespace nsp ON t.relnamespace = nsp.oid
  597. WHERE c.contype <> 't' AND t.relname = %s %s """ % (con_col_name, self.quoteString(tablename), schema_where)
  598. c = self._execute(None, sql)
  599. res = self._fetchall(c)
  600. self._close_cursor(c)
  601. return res
  602. def getTableTriggers(self, table):
  603. schema, tablename = self.getSchemaTableName(table)
  604. schema_where = " AND nspname=%s " % self.quoteString(schema) if schema is not None else ""
  605. sql = """SELECT tgname, proname, tgtype, tgenabled NOT IN ('f', 'D') FROM pg_trigger trig
  606. LEFT JOIN pg_class t ON trig.tgrelid = t.oid
  607. LEFT JOIN pg_proc p ON trig.tgfoid = p.oid
  608. JOIN pg_namespace nsp ON t.relnamespace = nsp.oid
  609. WHERE t.relname = %s %s """ % (
  610. self.quoteString(tablename), schema_where)
  611. c = self._execute(None, sql)
  612. res = self._fetchall(c)
  613. self._close_cursor(c)
  614. return res
  615. def enableAllTableTriggers(self, enable, table):
  616. """ enable or disable all triggers on table """
  617. self.enableTableTrigger(None, enable, table)
  618. def enableTableTrigger(self, trigger, enable, table):
  619. """ enable or disable one trigger on table """
  620. trigger = self.quoteId(trigger) if trigger is not None else "ALL"
  621. sql = "ALTER TABLE %s %s TRIGGER %s" % (self.quoteId(table), "ENABLE" if enable else "DISABLE", trigger)
  622. self._execute_and_commit(sql)
  623. def deleteTableTrigger(self, trigger, table):
  624. """Deletes trigger on table """
  625. sql = "DROP TRIGGER %s ON %s" % (self.quoteId(trigger), self.quoteId(table))
  626. self._execute_and_commit(sql)
  627. def getTableRules(self, table):
  628. schema, tablename = self.getSchemaTableName(table)
  629. schema_where = " AND schemaname=%s " % self.quoteString(schema) if schema is not None else ""
  630. sql = """SELECT rulename, definition FROM pg_rules
  631. WHERE tablename=%s %s """ % (self.quoteString(tablename), schema_where)
  632. c = self._execute(None, sql)
  633. res = self._fetchall(c)
  634. self._close_cursor(c)
  635. return res
  636. def deleteTableRule(self, rule, table):
  637. """Deletes rule on table """
  638. sql = "DROP RULE %s ON %s" % (self.quoteId(rule), self.quoteId(table))
  639. self._execute_and_commit(sql)
  640. def getTableExtent(self, table, geom):
  641. """ find out table extent """
  642. subquery = "SELECT st_extent(%s) AS extent FROM %s" % (self.quoteId(geom), self.quoteId(table))
  643. sql = "SELECT st_xmin(extent), st_ymin(extent), st_xmax(extent), st_ymax(extent) FROM (%s) AS subquery" % subquery
  644. c = self._execute(None, sql)
  645. res = self._fetchone(c)
  646. self._close_cursor(c)
  647. return res
  648. def getTableEstimatedExtent(self, table, geom):
  649. """ find out estimated extent (from the statistics) """
  650. if self.isRasterTable(table):
  651. return
  652. schema, tablename = self.getSchemaTableName(table)
  653. schema_part = "%s," % self.quoteString(schema) if schema is not None else ""
  654. pgis_versions = self.getSpatialInfo()[0].split('.')
  655. pgis_major_version = int(pgis_versions[0])
  656. pgis_minor_version = int(pgis_versions[1])
  657. pgis_old = False
  658. if pgis_major_version < 2:
  659. pgis_old = True
  660. elif pgis_major_version == 2 and pgis_minor_version < 1:
  661. pgis_old = True
  662. subquery = "SELECT %s(%s%s,%s) AS extent" % (
  663. 'st_estimated_extent' if pgis_old else 'st_estimatedextent',
  664. schema_part, self.quoteString(tablename), self.quoteString(geom))
  665. sql = """SELECT st_xmin(extent), st_ymin(extent), st_xmax(extent), st_ymax(extent) FROM (%s) AS subquery """ % subquery
  666. try:
  667. c = self._execute(None, sql)
  668. except DbError: # No statistics for the current table
  669. return
  670. res = self._fetchone(c)
  671. self._close_cursor(c)
  672. return res
  673. def getViewDefinition(self, view):
  674. """ returns definition of the view """
  675. schema, tablename = self.getSchemaTableName(view)
  676. schema_where = " AND nspname=%s " % self.quoteString(schema) if schema is not None else ""
  677. sql = """SELECT pg_get_viewdef(c.oid) FROM pg_class c
  678. JOIN pg_namespace nsp ON c.relnamespace = nsp.oid
  679. WHERE relname=%s %s AND (relkind='v' OR relkind='m') """ % (
  680. self.quoteString(tablename), schema_where)
  681. c = self._execute(None, sql)
  682. res = self._fetchone(c)
  683. self._close_cursor(c)
  684. return res[0] if res is not None else None
  685. def getCrs(self, srid):
  686. if not self.has_spatial:
  687. return QgsCoordinateReferenceSystem()
  688. try:
  689. c = self._execute(None, "SELECT proj4text FROM spatial_ref_sys WHERE srid = '%d'" % srid)
  690. except DbError:
  691. return QgsCoordinateReferenceSystem()
  692. res = self._fetchone(c)
  693. self._close_cursor(c)
  694. if res is None:
  695. return QgsCoordinateReferenceSystem()
  696. proj4text = res[0]
  697. crs = QgsCoordinateReferenceSystem.fromProj(proj4text)
  698. return crs
  699. def getSpatialRefInfo(self, srid):
  700. if not self.has_spatial:
  701. return
  702. try:
  703. c = self._execute(None, "SELECT srtext FROM spatial_ref_sys WHERE srid = '%d'" % srid)
  704. except DbError:
  705. return
  706. sr = self._fetchone(c)
  707. self._close_cursor(c)
  708. if sr is None:
  709. return
  710. srtext = sr[0]
  711. # try to extract just SR name (should be quoted in double quotes)
  712. regex = QRegExp('"([^"]+)"')
  713. if regex.indexIn(srtext) > -1:
  714. srtext = regex.cap(1)
  715. return srtext
  716. def isVectorTable(self, table):
  717. if self.has_geometry_columns and self.has_geometry_columns_access:
  718. schema, tablename = self.getSchemaTableName(table)
  719. sql = "SELECT count(*) FROM geometry_columns WHERE f_table_schema = %s AND f_table_name = %s" % (
  720. self.quoteString(schema), self.quoteString(tablename))
  721. c = self._execute(None, sql)
  722. res = self._fetchone(c)
  723. self._close_cursor(c)
  724. return res is not None and res[0] > 0
  725. return False
  726. def isRasterTable(self, table):
  727. if self.has_raster_columns and self.has_raster_columns_access:
  728. schema, tablename = self.getSchemaTableName(table)
  729. sql = "SELECT count(*) FROM raster_columns WHERE r_table_schema = %s AND r_table_name = %s" % (
  730. self.quoteString(schema), self.quoteString(tablename))
  731. c = self._execute(None, sql)
  732. res = self._fetchone(c)
  733. self._close_cursor(c)
  734. return res is not None and res[0] > 0
  735. return False
  736. def createTable(self, table, field_defs, pkey):
  737. """Creates ordinary table
  738. 'fields' is array containing field definitions
  739. 'pkey' is the primary key name
  740. """
  741. if len(field_defs) == 0:
  742. return False
  743. sql = "CREATE TABLE %s (" % self.quoteId(table)
  744. sql += ", ".join(field_defs)
  745. if pkey is not None and pkey != "":
  746. sql += ", PRIMARY KEY (%s)" % self.quoteId(pkey)
  747. sql += ")"
  748. self._execute_and_commit(sql)
  749. return True
  750. def deleteTable(self, table):
  751. """Deletes table and its reference in either geometry_columns or raster_columns """
  752. schema, tablename = self.getSchemaTableName(table)
  753. schema_part = "%s, " % self.quoteString(schema) if schema is not None else ""
  754. if self.isVectorTable(table):
  755. sql = "SELECT DropGeometryTable(%s%s)" % (schema_part, self.quoteString(tablename))
  756. elif self.isRasterTable(table):
  757. # Fix #8521: delete raster table and references from raster_columns table
  758. sql = "DROP TABLE %s" % self.quoteId(table)
  759. else:
  760. sql = "DROP TABLE %s" % self.quoteId(table)
  761. self._execute_and_commit(sql)
  762. def emptyTable(self, table):
  763. """Deletes all rows from table """
  764. sql = "TRUNCATE %s" % self.quoteId(table)
  765. self._execute_and_commit(sql)
  766. def renameTable(self, table, new_table):
  767. """Renames a table in database """
  768. schema, tablename = self.getSchemaTableName(table)
  769. if new_table == tablename:
  770. return
  771. sql = "ALTER TABLE %s RENAME TO %s" % (self.quoteId(table), self.quoteId(new_table))
  772. self._executeSql(sql)
  773. # update geometry_columns if PostGIS is enabled
  774. if self.has_geometry_columns and not self.is_geometry_columns_view:
  775. schema_where = " AND f_table_schema=%s " % self.quoteString(schema) if schema is not None else ""
  776. sql = "UPDATE geometry_columns SET f_table_name=%s WHERE f_table_name=%s %s" % (
  777. self.quoteString(new_table), self.quoteString(tablename), schema_where)
  778. self._executeSql(sql)
  779. def renameSchema(self, schema, new_schema):
  780. try:
  781. self.core_connection.renameSchema(schema, new_schema)
  782. return True
  783. except QgsProviderConnectionException:
  784. return False
  785. def commentTable(self, schema, tablename, comment=None):
  786. if comment is None:
  787. self._execute(None, 'COMMENT ON TABLE "{}"."{}" IS NULL;'.format(schema, tablename))
  788. else:
  789. self._execute(None, 'COMMENT ON TABLE "{}"."{}" IS $escape${}$escape$;'.format(schema, tablename, comment))
  790. def getComment(self, tablename, field):
  791. """Returns the comment for a field"""
  792. # SQL Query checking if a comment exists for the field
  793. sql_cpt = "Select count(*) from pg_description pd, pg_class pc, pg_attribute pa where relname = '%s' and attname = '%s' and pa.attrelid = pc.oid and pd.objoid = pc.oid and pd.objsubid = pa.attnum" % (tablename, field)
  794. # SQL Query that return the comment of the field
  795. sql = "Select pd.description from pg_description pd, pg_class pc, pg_attribute pa where relname = '%s' and attname = '%s' and pa.attrelid = pc.oid and pd.objoid = pc.oid and pd.objsubid = pa.attnum" % (tablename, field)
  796. c = self._execute(None, sql_cpt) # Execute Check query
  797. res = self._fetchone(c)[0] # Store result
  798. if res == 1:
  799. # When a comment exists
  800. c = self._execute(None, sql) # Execute query
  801. res = self._fetchone(c)[0] # Store result
  802. self._close_cursor(c) # Close cursor
  803. return res # Return comment
  804. else:
  805. return ''
  806. def moveTableToSchema(self, table, new_schema):
  807. schema, tablename = self.getSchemaTableName(table)
  808. if new_schema == schema:
  809. return
  810. c = self._get_cursor()
  811. sql = "ALTER TABLE %s SET SCHEMA %s" % (self.quoteId(table), self.quoteId(new_schema))
  812. self._execute(c, sql)
  813. # update geometry_columns if PostGIS is enabled
  814. if self.has_geometry_columns and not self.is_geometry_columns_view:
  815. schema, tablename = self.getSchemaTableName(table)
  816. schema_where = " AND f_table_schema=%s " % self.quoteString(schema) if schema is not None else ""
  817. sql = "UPDATE geometry_columns SET f_table_schema=%s WHERE f_table_name=%s %s" % (
  818. self.quoteString(new_schema), self.quoteString(tablename), schema_where)
  819. self._execute(c, sql)
  820. self._commit()
  821. def moveTable(self, table, new_table, new_schema=None):
  822. schema, tablename = self.getSchemaTableName(table)
  823. if new_schema == schema and new_table == tablename:
  824. return
  825. if new_schema == schema:
  826. return self.renameTable(table, new_table)
  827. if new_table == table:
  828. return self.moveTableToSchema(table, new_schema)
  829. c = self._get_cursor()
  830. t = "__new_table__"
  831. sql = "ALTER TABLE %s RENAME TO %s" % (self.quoteId(table), self.quoteId(t))
  832. self._execute(c, sql)
  833. sql = "ALTER TABLE %s SET SCHEMA %s" % (self.quoteId((schema, t)), self.quoteId(new_schema))
  834. self._execute(c, sql)
  835. sql = "ALTER TABLE %s RENAME TO %s" % (self.quoteId((new_schema, t)), self.quoteId(table))
  836. self._execute(c, sql)
  837. # update geometry_columns if PostGIS is enabled
  838. if self.has_geometry_columns and not self.is_geometry_columns_view:
  839. schema, tablename = self.getSchemaTableName(table)
  840. schema_where = " f_table_schema=%s AND " % self.quoteString(schema) if schema is not None else ""
  841. schema_part = " f_table_schema=%s, " % self.quoteString(new_schema) if schema is not None else ""
  842. sql = "UPDATE geometry_columns SET %s f_table_name=%s WHERE %s f_table_name=%s" % (
  843. schema_part, self.quoteString(new_table), schema_where, self.quoteString(tablename))
  844. self._execute(c, sql)
  845. self._commit()
  846. def createView(self, view, query):
  847. sql = "CREATE VIEW %s AS %s" % (self.quoteId(view), query)
  848. self._execute_and_commit(sql)
  849. def createSpatialView(self, view, query):
  850. self.createView(view, query)
  851. def deleteView(self, view, isMaterialized=False):
  852. sql = "DROP %s VIEW %s" % ('MATERIALIZED' if isMaterialized else '', self.quoteId(view))
  853. self._execute_and_commit(sql)
  854. def renameView(self, view, new_name):
  855. """Renames view in database """
  856. self.renameTable(view, new_name)
  857. def createSchema(self, schema):
  858. """Creates a new empty schema in database """
  859. sql = "CREATE SCHEMA %s" % self.quoteId(schema)
  860. self._execute_and_commit(sql)
  861. def deleteSchema(self, schema):
  862. """Drops (empty) schema from database """
  863. sql = "DROP SCHEMA %s" % self.quoteId(schema)
  864. self._execute_and_commit(sql)
  865. def renamesSchema(self, schema, new_schema):
  866. """Renames a schema in database """
  867. sql = "ALTER SCHEMA %s RENAME TO %s" % (self.quoteId(schema), self.quoteId(new_schema))
  868. self._execute_and_commit(sql)
  869. def runVacuum(self):
  870. """Runs vacuum on the db """
  871. self._execute_and_commit("VACUUM")
  872. def runVacuumAnalyze(self, table):
  873. """Runs vacuum analyze on a table """
  874. sql = "VACUUM ANALYZE %s" % self.quoteId(table)
  875. self._execute(None, sql)
  876. self._commit()
  877. def runRefreshMaterializedView(self, table):
  878. """Runs refresh materialized view on a table """
  879. sql = "REFRESH MATERIALIZED VIEW %s" % self.quoteId(table)
  880. self._execute(None, sql)
  881. self._commit()
  882. def addTableColumn(self, table, field_def):
  883. """Adds a column to table """
  884. sql = "ALTER TABLE %s ADD %s" % (self.quoteId(table), field_def)
  885. self._execute_and_commit(sql)
  886. def deleteTableColumn(self, table, column):
  887. """Deletes column from a table """
  888. if self.isGeometryColumn(table, column):
  889. # use PostGIS function to delete geometry column correctly
  890. schema, tablename = self.getSchemaTableName(table)
  891. schema_part = "%s, " % self.quoteString(schema) if schema else ""
  892. sql = "SELECT DropGeometryColumn(%s%s, %s)" % (
  893. schema_part, self.quoteString(tablename), self.quoteString(column))
  894. else:
  895. sql = "ALTER TABLE %s DROP %s" % (self.quoteId(table), self.quoteId(column))
  896. self._execute_and_commit(sql)
  897. def updateTableColumn(self, table, column, new_name=None, data_type=None, not_null=None, default=None, comment=None, test=None):
  898. if new_name is None and data_type is None and not_null is None and default is None and comment is None:
  899. return
  900. c = self._get_cursor()
  901. # update column definition
  902. col_actions = []
  903. if data_type is not None:
  904. col_actions.append("TYPE %s" % data_type)
  905. if not_null is not None:
  906. col_actions.append("SET NOT NULL" if not_null else "DROP NOT NULL")
  907. if default is not None:
  908. if default and default != '':
  909. col_actions.append("SET DEFAULT %s" % default)
  910. else:
  911. col_actions.append("DROP DEFAULT")
  912. if len(col_actions) > 0:
  913. sql = "ALTER TABLE %s" % self.quoteId(table)
  914. alter_col_str = "ALTER %s" % self.quoteId(column)
  915. for a in col_actions:
  916. sql += " %s %s," % (alter_col_str, a)
  917. self._execute(c, sql[:-1])
  918. # Renames the column
  919. if new_name is not None and new_name != column:
  920. sql = "ALTER TABLE %s RENAME %s TO %s" % (
  921. self.quoteId(table), self.quoteId(column), self.quoteId(new_name))
  922. self._execute(c, sql)
  923. # update geometry_columns if PostGIS is enabled
  924. if self.has_geometry_columns and not self.is_geometry_columns_view:
  925. schema, tablename = self.getSchemaTableName(table)
  926. schema_where = " f_table_schema=%s AND " % self.quoteString(schema) if schema is not None else ""
  927. sql = "UPDATE geometry_columns SET f_geometry_column=%s WHERE %s f_table_name=%s AND f_geometry_column=%s" % (
  928. self.quoteString(new_name), schema_where, self.quoteString(tablename), self.quoteString(column))
  929. self._execute(c, sql)
  930. # comment the column
  931. if comment is not None:
  932. schema, tablename = self.getSchemaTableName(table)
  933. column_name = new_name if new_name is not None and new_name != column else column
  934. sql = "COMMENT ON COLUMN %s.%s.%s IS '%s'" % (schema, tablename, column_name, comment)
  935. self._execute(c, sql)
  936. self._commit()
  937. def renamesTableColumn(self, table, column, new_name):
  938. """Renames column in a table """
  939. return self.updateTableColumn(table, column, new_name)
  940. def setTableColumnType(self, table, column, data_type):
  941. """Changes column type """
  942. return self.updateTableColumn(table, column, None, data_type)
  943. def setTableColumnNull(self, table, column, is_null):
  944. """Changes whether column can contain null values """
  945. return self.updateTableColumn(table, column, None, None, not is_null)
  946. def setTableColumnDefault(self, table, column, default):
  947. """Changes column's default value.
  948. If default=None or an empty string drop default value """
  949. return self.updateTableColumn(table, column, None, None, None, default)
  950. def isGeometryColumn(self, table, column):
  951. schema, tablename = self.getSchemaTableName(table)
  952. schema_where = " f_table_schema=%s AND " % self.quoteString(schema) if schema is not None else ""
  953. sql = "SELECT count(*) > 0 FROM geometry_columns WHERE %s f_table_name=%s AND f_geometry_column=%s" % (
  954. schema_where, self.quoteString(tablename), self.quoteString(column))
  955. c = self._execute(None, sql)
  956. res = self._fetchone(c)[0] == 't'
  957. self._close_cursor(c)
  958. return res
  959. def addGeometryColumn(self, table, geom_column='geom', geom_type='POINT', srid=-1, dim=2):
  960. schema, tablename = self.getSchemaTableName(table)
  961. schema_part = "%s, " % self.quoteString(schema) if schema else ""
  962. sql = "SELECT AddGeometryColumn(%s%s, %s, %d, %s, %d)" % (
  963. schema_part, self.quoteString(tablename), self.quoteString(geom_column), srid, self.quoteString(geom_type), dim)
  964. self._execute_and_commit(sql)
  965. def deleteGeometryColumn(self, table, geom_column):
  966. return self.deleteTableColumn(table, geom_column)
  967. def addTableUniqueConstraint(self, table, column):
  968. """Adds a unique constraint to a table """
  969. sql = "ALTER TABLE %s ADD UNIQUE (%s)" % (self.quoteId(table), self.quoteId(column))
  970. self._execute_and_commit(sql)
  971. def deleteTableConstraint(self, table, constraint):
  972. """Deletes constraint in a table """
  973. sql = "ALTER TABLE %s DROP CONSTRAINT %s" % (self.quoteId(table), self.quoteId(constraint))
  974. self._execute_and_commit(sql)
  975. def addTablePrimaryKey(self, table, column):
  976. """Adds a primery key (with one column) to a table """
  977. sql = "ALTER TABLE %s ADD PRIMARY KEY (%s)" % (self.quoteId(table), self.quoteId(column))
  978. self._execute_and_commit(sql)
  979. def createTableIndex(self, table, name, column):
  980. """Creates index on one column using default options """
  981. sql = "CREATE INDEX %s ON %s (%s)" % (self.quoteId(name), self.quoteId(table), self.quoteId(column))
  982. self._execute_and_commit(sql)
  983. def deleteTableIndex(self, table, name):
  984. schema, tablename = self.getSchemaTableName(table)
  985. sql = "DROP INDEX %s" % self.quoteId((schema, name))
  986. self._execute_and_commit(sql)
  987. def createSpatialIndex(self, table, geom_column='geom'):
  988. schema, tablename = self.getSchemaTableName(table)
  989. idx_name = self.quoteId("sidx_%s_%s" % (tablename, geom_column))
  990. sql = "CREATE INDEX %s ON %s USING GIST(%s)" % (idx_name, self.quoteId(table), self.quoteId(geom_column))
  991. self._execute_and_commit(sql)
  992. def deleteSpatialIndex(self, table, geom_column='geom'):
  993. schema, tablename = self.getSchemaTableName(table)
  994. idx_name = self.quoteId("sidx_%s_%s" % (tablename, geom_column))
  995. return self.deleteTableIndex(table, idx_name)
  996. def execution_error_types(self):
  997. return psycopg2.Error, psycopg2.ProgrammingError, psycopg2.Warning
  998. def connection_error_types(self):
  999. return psycopg2.InterfaceError, psycopg2.OperationalError
  1000. def _execute(self, cursor, sql):
  1001. if cursor is not None:
  1002. cursor._execute(sql)
  1003. return cursor
  1004. self.feedback = QgsFeedback()
  1005. return CursorAdapter(self.core_connection, sql, feedback=self.feedback)
  1006. def _executeSql(self, sql):
  1007. return self.core_connection.executeSql(sql)
  1008. def _get_cursor(self, name=None):
  1009. # if name is not None:
  1010. # print("XXX _get_cursor called with a Name: " + name)
  1011. return CursorAdapter(self.core_connection, name)
  1012. def _commit(self):
  1013. pass
  1014. # moved into the parent class: DbConnector._rollback()
  1015. def _rollback(self):
  1016. pass
  1017. # moved into the parent class: DbConnector._get_cursor_columns()
  1018. # def _get_cursor_columns(self, c):
  1019. # pass
  1020. def getSqlDictionary(self):
  1021. from .sql_dictionary import getSqlDictionary
  1022. sql_dict = getSqlDictionary()
  1023. # get schemas, tables and field names
  1024. sql = """SELECT nspname FROM pg_namespace WHERE nspname !~ '^pg_' AND nspname != 'information_schema'
  1025. UNION SELECT relname FROM pg_class WHERE relkind IN ('v', 'r', 'm', 'p')
  1026. UNION SELECT attname FROM pg_attribute WHERE attnum > 0"""
  1027. c = self._execute(None, sql)
  1028. items = [
  1029. row[0]
  1030. for row in self._fetchall(c)
  1031. ]
  1032. self._close_cursor(c)
  1033. sql_dict["identifier"] = items
  1034. return sql_dict
  1035. def getQueryBuilderDictionary(self):
  1036. from .sql_dictionary import getQueryBuilderDictionary
  1037. return getQueryBuilderDictionary()