You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

230 lines
9.8 KiB

  1. from __future__ import unicode_literals
  2. from collections import namedtuple
  3. from django.db.backends.base.introspection import (
  4. BaseDatabaseIntrospection, FieldInfo, TableInfo,
  5. )
  6. from django.utils.encoding import force_text
  7. FieldInfo = namedtuple('FieldInfo', FieldInfo._fields + ('default',))
  8. class DatabaseIntrospection(BaseDatabaseIntrospection):
  9. # Maps type codes to Django Field types.
  10. data_types_reverse = {
  11. 16: 'BooleanField',
  12. 17: 'BinaryField',
  13. 20: 'BigIntegerField',
  14. 21: 'SmallIntegerField',
  15. 23: 'IntegerField',
  16. 25: 'TextField',
  17. 700: 'FloatField',
  18. 701: 'FloatField',
  19. 869: 'GenericIPAddressField',
  20. 1042: 'CharField', # blank-padded
  21. 1043: 'CharField',
  22. 1082: 'DateField',
  23. 1083: 'TimeField',
  24. 1114: 'DateTimeField',
  25. 1184: 'DateTimeField',
  26. 1266: 'TimeField',
  27. 1700: 'DecimalField',
  28. }
  29. ignored_tables = []
  30. _get_indexes_query = """
  31. SELECT attr.attname, idx.indkey, idx.indisunique, idx.indisprimary
  32. FROM pg_catalog.pg_class c, pg_catalog.pg_class c2,
  33. pg_catalog.pg_index idx, pg_catalog.pg_attribute attr
  34. WHERE c.oid = idx.indrelid
  35. AND idx.indexrelid = c2.oid
  36. AND attr.attrelid = c.oid
  37. AND attr.attnum = idx.indkey[0]
  38. AND c.relname = %s"""
  39. def get_field_type(self, data_type, description):
  40. field_type = super(DatabaseIntrospection, self).get_field_type(data_type, description)
  41. if field_type == 'IntegerField' and description.default and 'nextval' in description.default:
  42. return 'AutoField'
  43. return field_type
  44. def get_table_list(self, cursor):
  45. """
  46. Returns a list of table and view names in the current database.
  47. """
  48. cursor.execute("""
  49. SELECT c.relname, c.relkind
  50. FROM pg_catalog.pg_class c
  51. LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
  52. WHERE c.relkind IN ('r', 'v')
  53. AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
  54. AND pg_catalog.pg_table_is_visible(c.oid)""")
  55. return [TableInfo(row[0], {'r': 't', 'v': 'v'}.get(row[1]))
  56. for row in cursor.fetchall()
  57. if row[0] not in self.ignored_tables]
  58. def get_table_description(self, cursor, table_name):
  59. "Returns a description of the table, with the DB-API cursor.description interface."
  60. # As cursor.description does not return reliably the nullable property,
  61. # we have to query the information_schema (#7783)
  62. cursor.execute("""
  63. SELECT column_name, is_nullable, column_default
  64. FROM information_schema.columns
  65. WHERE table_name = %s""", [table_name])
  66. field_map = {line[0]: line[1:] for line in cursor.fetchall()}
  67. cursor.execute("SELECT * FROM %s LIMIT 1" % self.connection.ops.quote_name(table_name))
  68. return [FieldInfo(*((force_text(line[0]),) + line[1:6]
  69. + (field_map[force_text(line[0])][0] == 'YES', field_map[force_text(line[0])][1])))
  70. for line in cursor.description]
  71. def get_relations(self, cursor, table_name):
  72. """
  73. Returns a dictionary of {field_name: (field_name_other_table, other_table)}
  74. representing all relationships to the given table.
  75. """
  76. cursor.execute("""
  77. SELECT c2.relname, a1.attname, a2.attname
  78. FROM pg_constraint con
  79. LEFT JOIN pg_class c1 ON con.conrelid = c1.oid
  80. LEFT JOIN pg_class c2 ON con.confrelid = c2.oid
  81. LEFT JOIN pg_attribute a1 ON c1.oid = a1.attrelid AND a1.attnum = con.conkey[1]
  82. LEFT JOIN pg_attribute a2 ON c2.oid = a2.attrelid AND a2.attnum = con.confkey[1]
  83. WHERE c1.relname = %s
  84. AND con.contype = 'f'""", [table_name])
  85. relations = {}
  86. for row in cursor.fetchall():
  87. relations[row[1]] = (row[2], row[0])
  88. return relations
  89. def get_key_columns(self, cursor, table_name):
  90. key_columns = []
  91. cursor.execute("""
  92. SELECT kcu.column_name, ccu.table_name AS referenced_table, ccu.column_name AS referenced_column
  93. FROM information_schema.constraint_column_usage ccu
  94. LEFT JOIN information_schema.key_column_usage kcu
  95. ON ccu.constraint_catalog = kcu.constraint_catalog
  96. AND ccu.constraint_schema = kcu.constraint_schema
  97. AND ccu.constraint_name = kcu.constraint_name
  98. LEFT JOIN information_schema.table_constraints tc
  99. ON ccu.constraint_catalog = tc.constraint_catalog
  100. AND ccu.constraint_schema = tc.constraint_schema
  101. AND ccu.constraint_name = tc.constraint_name
  102. WHERE kcu.table_name = %s AND tc.constraint_type = 'FOREIGN KEY'""", [table_name])
  103. key_columns.extend(cursor.fetchall())
  104. return key_columns
  105. def get_indexes(self, cursor, table_name):
  106. # This query retrieves each index on the given table, including the
  107. # first associated field name
  108. cursor.execute(self._get_indexes_query, [table_name])
  109. indexes = {}
  110. for row in cursor.fetchall():
  111. # row[1] (idx.indkey) is stored in the DB as an array. It comes out as
  112. # a string of space-separated integers. This designates the field
  113. # indexes (1-based) of the fields that have indexes on the table.
  114. # Here, we skip any indexes across multiple fields.
  115. if ' ' in row[1]:
  116. continue
  117. if row[0] not in indexes:
  118. indexes[row[0]] = {'primary_key': False, 'unique': False}
  119. # It's possible to have the unique and PK constraints in separate indexes.
  120. if row[3]:
  121. indexes[row[0]]['primary_key'] = True
  122. if row[2]:
  123. indexes[row[0]]['unique'] = True
  124. return indexes
  125. def get_constraints(self, cursor, table_name):
  126. """
  127. Retrieves any constraints or keys (unique, pk, fk, check, index) across one or more columns.
  128. """
  129. constraints = {}
  130. # Loop over the key table, collecting things as constraints
  131. # This will get PKs, FKs, and uniques, but not CHECK
  132. cursor.execute("""
  133. SELECT
  134. kc.constraint_name,
  135. kc.column_name,
  136. c.constraint_type,
  137. array(SELECT table_name::text || '.' || column_name::text
  138. FROM information_schema.constraint_column_usage
  139. WHERE constraint_name = kc.constraint_name)
  140. FROM information_schema.key_column_usage AS kc
  141. JOIN information_schema.table_constraints AS c ON
  142. kc.table_schema = c.table_schema AND
  143. kc.table_name = c.table_name AND
  144. kc.constraint_name = c.constraint_name
  145. WHERE
  146. kc.table_schema = %s AND
  147. kc.table_name = %s
  148. ORDER BY kc.ordinal_position ASC
  149. """, ["public", table_name])
  150. for constraint, column, kind, used_cols in cursor.fetchall():
  151. # If we're the first column, make the record
  152. if constraint not in constraints:
  153. constraints[constraint] = {
  154. "columns": [],
  155. "primary_key": kind.lower() == "primary key",
  156. "unique": kind.lower() in ["primary key", "unique"],
  157. "foreign_key": tuple(used_cols[0].split(".", 1)) if kind.lower() == "foreign key" else None,
  158. "check": False,
  159. "index": False,
  160. }
  161. # Record the details
  162. constraints[constraint]['columns'].append(column)
  163. # Now get CHECK constraint columns
  164. cursor.execute("""
  165. SELECT kc.constraint_name, kc.column_name
  166. FROM information_schema.constraint_column_usage AS kc
  167. JOIN information_schema.table_constraints AS c ON
  168. kc.table_schema = c.table_schema AND
  169. kc.table_name = c.table_name AND
  170. kc.constraint_name = c.constraint_name
  171. WHERE
  172. c.constraint_type = 'CHECK' AND
  173. kc.table_schema = %s AND
  174. kc.table_name = %s
  175. """, ["public", table_name])
  176. for constraint, column in cursor.fetchall():
  177. # If we're the first column, make the record
  178. if constraint not in constraints:
  179. constraints[constraint] = {
  180. "columns": [],
  181. "primary_key": False,
  182. "unique": False,
  183. "foreign_key": None,
  184. "check": True,
  185. "index": False,
  186. }
  187. # Record the details
  188. constraints[constraint]['columns'].append(column)
  189. # Now get indexes
  190. cursor.execute("""
  191. SELECT
  192. c2.relname,
  193. ARRAY(
  194. SELECT (SELECT attname FROM pg_catalog.pg_attribute WHERE attnum = i AND attrelid = c.oid)
  195. FROM unnest(idx.indkey) i
  196. ),
  197. idx.indisunique,
  198. idx.indisprimary
  199. FROM pg_catalog.pg_class c, pg_catalog.pg_class c2,
  200. pg_catalog.pg_index idx
  201. WHERE c.oid = idx.indrelid
  202. AND idx.indexrelid = c2.oid
  203. AND c.relname = %s
  204. """, [table_name])
  205. for index, columns, unique, primary in cursor.fetchall():
  206. if index not in constraints:
  207. constraints[index] = {
  208. "columns": list(columns),
  209. "primary_key": primary,
  210. "unique": unique,
  211. "foreign_key": None,
  212. "check": False,
  213. "index": True,
  214. }
  215. return constraints