introspection.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. from django.db.backends.base.introspection import (
  2. BaseDatabaseIntrospection, FieldInfo, TableInfo,
  3. )
  4. from django.db.models import Index
  5. class DatabaseIntrospection(BaseDatabaseIntrospection):
  6. # Maps type codes to Django Field types.
  7. data_types_reverse = {
  8. 16: 'BooleanField',
  9. 17: 'BinaryField',
  10. 20: 'BigIntegerField',
  11. 21: 'SmallIntegerField',
  12. 23: 'IntegerField',
  13. 25: 'TextField',
  14. 700: 'FloatField',
  15. 701: 'FloatField',
  16. 869: 'GenericIPAddressField',
  17. 1042: 'CharField', # blank-padded
  18. 1043: 'CharField',
  19. 1082: 'DateField',
  20. 1083: 'TimeField',
  21. 1114: 'DateTimeField',
  22. 1184: 'DateTimeField',
  23. 1186: 'DurationField',
  24. 1266: 'TimeField',
  25. 1700: 'DecimalField',
  26. 2950: 'UUIDField',
  27. 3802: 'JSONField',
  28. }
  29. # A hook for subclasses.
  30. index_default_access_method = 'btree'
  31. ignored_tables = []
  32. def get_field_type(self, data_type, description):
  33. field_type = super().get_field_type(data_type, description)
  34. if description.default and 'nextval' in description.default:
  35. if field_type == 'IntegerField':
  36. return 'AutoField'
  37. elif field_type == 'BigIntegerField':
  38. return 'BigAutoField'
  39. elif field_type == 'SmallIntegerField':
  40. return 'SmallAutoField'
  41. return field_type
  42. def get_table_list(self, cursor):
  43. """Return a list of table and view names in the current database."""
  44. cursor.execute("""
  45. SELECT c.relname,
  46. CASE WHEN {} THEN 'p' WHEN c.relkind IN ('m', 'v') THEN 'v' ELSE 't' END
  47. FROM pg_catalog.pg_class c
  48. LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
  49. WHERE c.relkind IN ('f', 'm', 'p', 'r', 'v')
  50. AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
  51. AND pg_catalog.pg_table_is_visible(c.oid)
  52. """.format('c.relispartition' if self.connection.features.supports_table_partitions else 'FALSE'))
  53. return [TableInfo(*row) for row in cursor.fetchall() if row[0] not in self.ignored_tables]
  54. def get_table_description(self, cursor, table_name):
  55. """
  56. Return a description of the table with the DB-API cursor.description
  57. interface.
  58. """
  59. # Query the pg_catalog tables as cursor.description does not reliably
  60. # return the nullable property and information_schema.columns does not
  61. # contain details of materialized views.
  62. cursor.execute("""
  63. SELECT
  64. a.attname AS column_name,
  65. NOT (a.attnotnull OR (t.typtype = 'd' AND t.typnotnull)) AS is_nullable,
  66. pg_get_expr(ad.adbin, ad.adrelid) AS column_default,
  67. CASE WHEN collname = 'default' THEN NULL ELSE collname END AS collation
  68. FROM pg_attribute a
  69. LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum
  70. LEFT JOIN pg_collation co ON a.attcollation = co.oid
  71. JOIN pg_type t ON a.atttypid = t.oid
  72. JOIN pg_class c ON a.attrelid = c.oid
  73. JOIN pg_namespace n ON c.relnamespace = n.oid
  74. WHERE c.relkind IN ('f', 'm', 'p', 'r', 'v')
  75. AND c.relname = %s
  76. AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
  77. AND pg_catalog.pg_table_is_visible(c.oid)
  78. """, [table_name])
  79. field_map = {line[0]: line[1:] for line in cursor.fetchall()}
  80. cursor.execute("SELECT * FROM %s LIMIT 1" % self.connection.ops.quote_name(table_name))
  81. return [
  82. FieldInfo(
  83. line.name,
  84. line.type_code,
  85. line.display_size,
  86. line.internal_size,
  87. line.precision,
  88. line.scale,
  89. *field_map[line.name],
  90. )
  91. for line in cursor.description
  92. ]
  93. def get_sequences(self, cursor, table_name, table_fields=()):
  94. cursor.execute("""
  95. SELECT s.relname as sequence_name, col.attname
  96. FROM pg_class s
  97. JOIN pg_namespace sn ON sn.oid = s.relnamespace
  98. JOIN pg_depend d ON d.refobjid = s.oid AND d.refclassid = 'pg_class'::regclass
  99. JOIN pg_attrdef ad ON ad.oid = d.objid AND d.classid = 'pg_attrdef'::regclass
  100. JOIN pg_attribute col ON col.attrelid = ad.adrelid AND col.attnum = ad.adnum
  101. JOIN pg_class tbl ON tbl.oid = ad.adrelid
  102. WHERE s.relkind = 'S'
  103. AND d.deptype in ('a', 'n')
  104. AND pg_catalog.pg_table_is_visible(tbl.oid)
  105. AND tbl.relname = %s
  106. """, [table_name])
  107. return [
  108. {'name': row[0], 'table': table_name, 'column': row[1]}
  109. for row in cursor.fetchall()
  110. ]
  111. def get_relations(self, cursor, table_name):
  112. """
  113. Return a dictionary of {field_name: (field_name_other_table, other_table)}
  114. representing all relationships to the given table.
  115. """
  116. return {row[0]: (row[2], row[1]) for row in self.get_key_columns(cursor, table_name)}
  117. def get_key_columns(self, cursor, table_name):
  118. cursor.execute("""
  119. SELECT a1.attname, c2.relname, a2.attname
  120. FROM pg_constraint con
  121. LEFT JOIN pg_class c1 ON con.conrelid = c1.oid
  122. LEFT JOIN pg_class c2 ON con.confrelid = c2.oid
  123. LEFT JOIN pg_attribute a1 ON c1.oid = a1.attrelid AND a1.attnum = con.conkey[1]
  124. LEFT JOIN pg_attribute a2 ON c2.oid = a2.attrelid AND a2.attnum = con.confkey[1]
  125. WHERE
  126. c1.relname = %s AND
  127. con.contype = 'f' AND
  128. c1.relnamespace = c2.relnamespace AND
  129. pg_catalog.pg_table_is_visible(c1.oid)
  130. """, [table_name])
  131. return cursor.fetchall()
  132. def get_constraints(self, cursor, table_name):
  133. """
  134. Retrieve any constraints or keys (unique, pk, fk, check, index) across
  135. one or more columns. Also retrieve the definition of expression-based
  136. indexes.
  137. """
  138. constraints = {}
  139. # Loop over the key table, collecting things as constraints. The column
  140. # array must return column names in the same order in which they were
  141. # created.
  142. cursor.execute("""
  143. SELECT
  144. c.conname,
  145. array(
  146. SELECT attname
  147. FROM unnest(c.conkey) WITH ORDINALITY cols(colid, arridx)
  148. JOIN pg_attribute AS ca ON cols.colid = ca.attnum
  149. WHERE ca.attrelid = c.conrelid
  150. ORDER BY cols.arridx
  151. ),
  152. c.contype,
  153. (SELECT fkc.relname || '.' || fka.attname
  154. FROM pg_attribute AS fka
  155. JOIN pg_class AS fkc ON fka.attrelid = fkc.oid
  156. WHERE fka.attrelid = c.confrelid AND fka.attnum = c.confkey[1]),
  157. cl.reloptions
  158. FROM pg_constraint AS c
  159. JOIN pg_class AS cl ON c.conrelid = cl.oid
  160. WHERE cl.relname = %s AND pg_catalog.pg_table_is_visible(cl.oid)
  161. """, [table_name])
  162. for constraint, columns, kind, used_cols, options in cursor.fetchall():
  163. constraints[constraint] = {
  164. "columns": columns,
  165. "primary_key": kind == "p",
  166. "unique": kind in ["p", "u"],
  167. "foreign_key": tuple(used_cols.split(".", 1)) if kind == "f" else None,
  168. "check": kind == "c",
  169. "index": False,
  170. "definition": None,
  171. "options": options,
  172. }
  173. # Now get indexes
  174. cursor.execute("""
  175. SELECT
  176. indexname, array_agg(attname ORDER BY arridx), indisunique, indisprimary,
  177. array_agg(ordering ORDER BY arridx), amname, exprdef, s2.attoptions
  178. FROM (
  179. SELECT
  180. c2.relname as indexname, idx.*, attr.attname, am.amname,
  181. CASE
  182. WHEN idx.indexprs IS NOT NULL THEN
  183. pg_get_indexdef(idx.indexrelid)
  184. END AS exprdef,
  185. CASE am.amname
  186. WHEN %s THEN
  187. CASE (option & 1)
  188. WHEN 1 THEN 'DESC' ELSE 'ASC'
  189. END
  190. END as ordering,
  191. c2.reloptions as attoptions
  192. FROM (
  193. SELECT *
  194. FROM pg_index i, unnest(i.indkey, i.indoption) WITH ORDINALITY koi(key, option, arridx)
  195. ) idx
  196. LEFT JOIN pg_class c ON idx.indrelid = c.oid
  197. LEFT JOIN pg_class c2 ON idx.indexrelid = c2.oid
  198. LEFT JOIN pg_am am ON c2.relam = am.oid
  199. LEFT JOIN pg_attribute attr ON attr.attrelid = c.oid AND attr.attnum = idx.key
  200. WHERE c.relname = %s AND pg_catalog.pg_table_is_visible(c.oid)
  201. ) s2
  202. GROUP BY indexname, indisunique, indisprimary, amname, exprdef, attoptions;
  203. """, [self.index_default_access_method, table_name])
  204. for index, columns, unique, primary, orders, type_, definition, options in cursor.fetchall():
  205. if index not in constraints:
  206. basic_index = (
  207. type_ == self.index_default_access_method and
  208. # '_btree' references
  209. # django.contrib.postgres.indexes.BTreeIndex.suffix.
  210. not index.endswith('_btree') and options is None
  211. )
  212. constraints[index] = {
  213. "columns": columns if columns != [None] else [],
  214. "orders": orders if orders != [None] else [],
  215. "primary_key": primary,
  216. "unique": unique,
  217. "foreign_key": None,
  218. "check": False,
  219. "index": True,
  220. "type": Index.suffix if basic_index else type_,
  221. "definition": definition,
  222. "options": options,
  223. }
  224. return constraints