sql.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. from __future__ import unicode_literals
  2. import io
  3. import os
  4. import re
  5. import warnings
  6. from django.apps import apps
  7. from django.conf import settings
  8. from django.core.management.base import CommandError
  9. from django.db import models, router
  10. from django.utils.deprecation import RemovedInDjango19Warning
  11. from django.utils.version import get_docs_version
  12. def check_for_migrations(app_config, connection):
  13. # Inner import, else tests imports it too early as it needs settings
  14. from django.db.migrations.loader import MigrationLoader
  15. loader = MigrationLoader(connection)
  16. if app_config.label in loader.migrated_apps:
  17. raise CommandError(
  18. "App '%s' has migrations. Only the sqlmigrate and sqlflush commands "
  19. "can be used when an app has migrations." % app_config.label
  20. )
  21. def sql_create(app_config, style, connection):
  22. "Returns a list of the CREATE TABLE SQL statements for the given app."
  23. check_for_migrations(app_config, connection)
  24. if connection.settings_dict['ENGINE'] == 'django.db.backends.dummy':
  25. # This must be the "dummy" database backend, which means the user
  26. # hasn't set ENGINE for the database.
  27. raise CommandError(
  28. "Django doesn't know which syntax to use for your SQL statements,\n"
  29. "because you haven't properly specified the ENGINE setting for the database.\n"
  30. "see: https://docs.djangoproject.com/en/%s/ref/settings/#databases" % get_docs_version()
  31. )
  32. # Get installed models, so we generate REFERENCES right.
  33. # We trim models from the current app so that the sqlreset command does not
  34. # generate invalid SQL (leaving models out of known_models is harmless, so
  35. # we can be conservative).
  36. app_models = list(app_config.get_models(include_auto_created=True))
  37. final_output = []
  38. tables = connection.introspection.table_names()
  39. known_models = set(model for model in connection.introspection.installed_models(tables) if model not in app_models)
  40. pending_references = {}
  41. for model in router.get_migratable_models(app_config, connection.alias, include_auto_created=True):
  42. output, references = connection.creation.sql_create_model(model, style, known_models)
  43. final_output.extend(output)
  44. for refto, refs in references.items():
  45. pending_references.setdefault(refto, []).extend(refs)
  46. if refto in known_models:
  47. final_output.extend(connection.creation.sql_for_pending_references(refto, style, pending_references))
  48. final_output.extend(connection.creation.sql_for_pending_references(model, style, pending_references))
  49. # Keep track of the fact that we've created the table for this model.
  50. known_models.add(model)
  51. # Handle references to tables that are from other apps
  52. # but don't exist physically.
  53. not_installed_models = set(pending_references.keys())
  54. if not_installed_models:
  55. alter_sql = []
  56. for model in not_installed_models:
  57. alter_sql.extend('-- ' + sql for sql in
  58. connection.creation.sql_for_pending_references(model, style, pending_references))
  59. if alter_sql:
  60. final_output.append('-- The following references should be added but depend on non-existent tables:')
  61. final_output.extend(alter_sql)
  62. return final_output
  63. def sql_delete(app_config, style, connection, close_connection=True):
  64. "Returns a list of the DROP TABLE SQL statements for the given app."
  65. check_for_migrations(app_config, connection)
  66. # This should work even if a connection isn't available
  67. try:
  68. cursor = connection.cursor()
  69. except Exception:
  70. cursor = None
  71. try:
  72. # Figure out which tables already exist
  73. if cursor:
  74. table_names = connection.introspection.table_names(cursor)
  75. else:
  76. table_names = []
  77. output = []
  78. # Output DROP TABLE statements for standard application tables.
  79. to_delete = set()
  80. references_to_delete = {}
  81. app_models = router.get_migratable_models(app_config, connection.alias, include_auto_created=True)
  82. for model in app_models:
  83. if cursor and connection.introspection.table_name_converter(model._meta.db_table) in table_names:
  84. # The table exists, so it needs to be dropped
  85. opts = model._meta
  86. for f in opts.local_fields:
  87. if f.rel and f.rel.to not in to_delete:
  88. references_to_delete.setdefault(f.rel.to, []).append((model, f))
  89. to_delete.add(model)
  90. for model in app_models:
  91. if connection.introspection.table_name_converter(model._meta.db_table) in table_names:
  92. output.extend(connection.creation.sql_destroy_model(model, references_to_delete, style))
  93. finally:
  94. # Close database connection explicitly, in case this output is being piped
  95. # directly into a database client, to avoid locking issues.
  96. if cursor and close_connection:
  97. cursor.close()
  98. connection.close()
  99. if not output:
  100. output.append('-- App creates no tables in the database. Nothing to do.')
  101. return output[::-1] # Reverse it, to deal with table dependencies.
  102. def sql_flush(style, connection, only_django=False, reset_sequences=True, allow_cascade=False):
  103. """
  104. Returns a list of the SQL statements used to flush the database.
  105. If only_django is True, then only table names that have associated Django
  106. models and are in INSTALLED_APPS will be included.
  107. """
  108. if only_django:
  109. tables = connection.introspection.django_table_names(only_existing=True, include_views=False)
  110. else:
  111. tables = connection.introspection.table_names(include_views=False)
  112. seqs = connection.introspection.sequence_list() if reset_sequences else ()
  113. statements = connection.ops.sql_flush(style, tables, seqs, allow_cascade)
  114. return statements
  115. def sql_custom(app_config, style, connection):
  116. "Returns a list of the custom table modifying SQL statements for the given app."
  117. check_for_migrations(app_config, connection)
  118. output = []
  119. app_models = router.get_migratable_models(app_config, connection.alias)
  120. for model in app_models:
  121. output.extend(custom_sql_for_model(model, style, connection))
  122. return output
  123. def sql_indexes(app_config, style, connection):
  124. "Returns a list of the CREATE INDEX SQL statements for all models in the given app."
  125. check_for_migrations(app_config, connection)
  126. output = []
  127. for model in router.get_migratable_models(app_config, connection.alias, include_auto_created=True):
  128. output.extend(connection.creation.sql_indexes_for_model(model, style))
  129. return output
  130. def sql_destroy_indexes(app_config, style, connection):
  131. "Returns a list of the DROP INDEX SQL statements for all models in the given app."
  132. check_for_migrations(app_config, connection)
  133. output = []
  134. for model in router.get_migratable_models(app_config, connection.alias, include_auto_created=True):
  135. output.extend(connection.creation.sql_destroy_indexes_for_model(model, style))
  136. return output
  137. def sql_all(app_config, style, connection):
  138. check_for_migrations(app_config, connection)
  139. "Returns a list of CREATE TABLE SQL, initial-data inserts, and CREATE INDEX SQL for the given module."
  140. return (
  141. sql_create(app_config, style, connection) +
  142. sql_custom(app_config, style, connection) +
  143. sql_indexes(app_config, style, connection)
  144. )
  145. def _split_statements(content):
  146. # Private API only called from code that emits a RemovedInDjango19Warning.
  147. comment_re = re.compile(r"^((?:'[^']*'|[^'])*?)--.*$")
  148. statements = []
  149. statement = []
  150. for line in content.split("\n"):
  151. cleaned_line = comment_re.sub(r"\1", line).strip()
  152. if not cleaned_line:
  153. continue
  154. statement.append(cleaned_line)
  155. if cleaned_line.endswith(";"):
  156. statements.append(" ".join(statement))
  157. statement = []
  158. return statements
  159. def custom_sql_for_model(model, style, connection):
  160. opts = model._meta
  161. app_dirs = []
  162. app_dir = apps.get_app_config(model._meta.app_label).path
  163. app_dirs.append(os.path.normpath(os.path.join(app_dir, 'sql')))
  164. # Deprecated location -- remove in Django 1.9
  165. old_app_dir = os.path.normpath(os.path.join(app_dir, 'models/sql'))
  166. if os.path.exists(old_app_dir):
  167. warnings.warn("Custom SQL location '<app_label>/models/sql' is "
  168. "deprecated, use '<app_label>/sql' instead.",
  169. RemovedInDjango19Warning)
  170. app_dirs.append(old_app_dir)
  171. output = []
  172. # Post-creation SQL should come before any initial SQL data is loaded.
  173. # However, this should not be done for models that are unmanaged or
  174. # for fields that are part of a parent model (via model inheritance).
  175. if opts.managed:
  176. post_sql_fields = [f for f in opts.local_fields if hasattr(f, 'post_create_sql')]
  177. for f in post_sql_fields:
  178. output.extend(f.post_create_sql(style, model._meta.db_table))
  179. # Find custom SQL, if it's available.
  180. backend_name = connection.settings_dict['ENGINE'].split('.')[-1]
  181. sql_files = []
  182. for app_dir in app_dirs:
  183. sql_files.append(os.path.join(app_dir, "%s.%s.sql" % (opts.model_name, backend_name)))
  184. sql_files.append(os.path.join(app_dir, "%s.sql" % opts.model_name))
  185. for sql_file in sql_files:
  186. if os.path.exists(sql_file):
  187. with io.open(sql_file, encoding=settings.FILE_CHARSET) as fp:
  188. output.extend(connection.ops.prepare_sql_script(fp.read(), _allow_fallback=True))
  189. return output
  190. def emit_pre_migrate_signal(verbosity, interactive, db):
  191. # Emit the pre_migrate signal for every application.
  192. for app_config in apps.get_app_configs():
  193. if app_config.models_module is None:
  194. continue
  195. if verbosity >= 2:
  196. print("Running pre-migrate handlers for application %s" % app_config.label)
  197. models.signals.pre_migrate.send(
  198. sender=app_config,
  199. app_config=app_config,
  200. verbosity=verbosity,
  201. interactive=interactive,
  202. using=db)
  203. def emit_post_migrate_signal(verbosity, interactive, db):
  204. # Emit the post_migrate signal for every application.
  205. for app_config in apps.get_app_configs():
  206. if app_config.models_module is None:
  207. continue
  208. if verbosity >= 2:
  209. print("Running post-migrate handlers for application %s" % app_config.label)
  210. models.signals.post_migrate.send(
  211. sender=app_config,
  212. app_config=app_config,
  213. verbosity=verbosity,
  214. interactive=interactive,
  215. using=db)