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.

introspection.py 7.2 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. from collections import namedtuple
  2. from django.utils import six
  3. # Structure returned by DatabaseIntrospection.get_table_list()
  4. TableInfo = namedtuple('TableInfo', ['name', 'type'])
  5. # Structure returned by the DB-API cursor.description interface (PEP 249)
  6. FieldInfo = namedtuple('FieldInfo',
  7. 'name type_code display_size internal_size precision scale null_ok')
  8. class BaseDatabaseIntrospection(object):
  9. """
  10. This class encapsulates all backend-specific introspection utilities
  11. """
  12. data_types_reverse = {}
  13. def __init__(self, connection):
  14. self.connection = connection
  15. def get_field_type(self, data_type, description):
  16. """Hook for a database backend to use the cursor description to
  17. match a Django field type to a database column.
  18. For Oracle, the column data_type on its own is insufficient to
  19. distinguish between a FloatField and IntegerField, for example."""
  20. return self.data_types_reverse[data_type]
  21. def table_name_converter(self, name):
  22. """Apply a conversion to the name for the purposes of comparison.
  23. The default table name converter is for case sensitive comparison.
  24. """
  25. return name
  26. def column_name_converter(self, name):
  27. """
  28. Apply a conversion to the column name for the purposes of comparison.
  29. Uses table_name_converter() by default.
  30. """
  31. return self.table_name_converter(name)
  32. def table_names(self, cursor=None, include_views=False):
  33. """
  34. Returns a list of names of all tables that exist in the database.
  35. The returned table list is sorted by Python's default sorting. We
  36. do NOT use database's ORDER BY here to avoid subtle differences
  37. in sorting order between databases.
  38. """
  39. def get_names(cursor):
  40. return sorted(ti.name for ti in self.get_table_list(cursor)
  41. if include_views or ti.type == 't')
  42. if cursor is None:
  43. with self.connection.cursor() as cursor:
  44. return get_names(cursor)
  45. return get_names(cursor)
  46. def get_table_list(self, cursor):
  47. """
  48. Returns an unsorted list of TableInfo named tuples of all tables and
  49. views that exist in the database.
  50. """
  51. raise NotImplementedError('subclasses of BaseDatabaseIntrospection may require a get_table_list() method')
  52. def django_table_names(self, only_existing=False, include_views=True):
  53. """
  54. Returns a list of all table names that have associated Django models and
  55. are in INSTALLED_APPS.
  56. If only_existing is True, the resulting list will only include the tables
  57. that actually exist in the database.
  58. """
  59. from django.apps import apps
  60. from django.db import router
  61. tables = set()
  62. for app_config in apps.get_app_configs():
  63. for model in router.get_migratable_models(app_config, self.connection.alias):
  64. if not model._meta.managed:
  65. continue
  66. tables.add(model._meta.db_table)
  67. tables.update(f.m2m_db_table() for f in model._meta.local_many_to_many)
  68. tables = list(tables)
  69. if only_existing:
  70. existing_tables = self.table_names(include_views=include_views)
  71. tables = [
  72. t
  73. for t in tables
  74. if self.table_name_converter(t) in existing_tables
  75. ]
  76. return tables
  77. def installed_models(self, tables):
  78. "Returns a set of all models represented by the provided list of table names."
  79. from django.apps import apps
  80. from django.db import router
  81. all_models = []
  82. for app_config in apps.get_app_configs():
  83. all_models.extend(router.get_migratable_models(app_config, self.connection.alias))
  84. tables = list(map(self.table_name_converter, tables))
  85. return {
  86. m for m in all_models
  87. if self.table_name_converter(m._meta.db_table) in tables
  88. }
  89. def sequence_list(self):
  90. "Returns a list of information about all DB sequences for all models in all apps."
  91. from django.apps import apps
  92. from django.db import models, router
  93. sequence_list = []
  94. for app_config in apps.get_app_configs():
  95. for model in router.get_migratable_models(app_config, self.connection.alias):
  96. if not model._meta.managed:
  97. continue
  98. if model._meta.swapped:
  99. continue
  100. for f in model._meta.local_fields:
  101. if isinstance(f, models.AutoField):
  102. sequence_list.append({'table': model._meta.db_table, 'column': f.column})
  103. break # Only one AutoField is allowed per model, so don't bother continuing.
  104. for f in model._meta.local_many_to_many:
  105. # If this is an m2m using an intermediate table,
  106. # we don't need to reset the sequence.
  107. if f.remote_field.through is None:
  108. sequence_list.append({'table': f.m2m_db_table(), 'column': None})
  109. return sequence_list
  110. def get_key_columns(self, cursor, table_name):
  111. """
  112. Backends can override this to return a list of (column_name, referenced_table_name,
  113. referenced_column_name) for all key columns in given table.
  114. """
  115. raise NotImplementedError('subclasses of BaseDatabaseIntrospection may require a get_key_columns() method')
  116. def get_primary_key_column(self, cursor, table_name):
  117. """
  118. Returns the name of the primary key column for the given table.
  119. """
  120. for column in six.iteritems(self.get_indexes(cursor, table_name)):
  121. if column[1]['primary_key']:
  122. return column[0]
  123. return None
  124. def get_indexes(self, cursor, table_name):
  125. """
  126. Returns a dictionary of indexed fieldname -> infodict for the given
  127. table, where each infodict is in the format:
  128. {'primary_key': boolean representing whether it's the primary key,
  129. 'unique': boolean representing whether it's a unique index}
  130. Only single-column indexes are introspected.
  131. """
  132. raise NotImplementedError('subclasses of BaseDatabaseIntrospection may require a get_indexes() method')
  133. def get_constraints(self, cursor, table_name):
  134. """
  135. Retrieves any constraints or keys (unique, pk, fk, check, index)
  136. across one or more columns.
  137. Returns a dict mapping constraint names to their attributes,
  138. where attributes is a dict with keys:
  139. * columns: List of columns this covers
  140. * primary_key: True if primary key, False otherwise
  141. * unique: True if this is a unique constraint, False otherwise
  142. * foreign_key: (table, column) of target, or None
  143. * check: True if check constraint, False otherwise
  144. * index: True if index, False otherwise.
  145. Some backends may return special constraint names that don't exist
  146. if they don't name constraints of a certain type (e.g. SQLite)
  147. """
  148. raise NotImplementedError('subclasses of BaseDatabaseIntrospection may require a get_constraints() method')