sql.py 9.4 KB

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