introspection.py 11 KB

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