app/django/core/management/sql.py
changeset 323 ff1a9aa48cfd
parent 54 03e267d67478
--- a/app/django/core/management/sql.py	Tue Oct 14 12:36:55 2008 +0000
+++ b/app/django/core/management/sql.py	Tue Oct 14 16:00:59 2008 +0000
@@ -7,65 +7,9 @@
 except NameError:
     from sets import Set as set   # Python 2.3 fallback
 
-def table_names():
-    "Returns a list of all table names that exist in the database."
-    from django.db import connection, get_introspection_module
-    cursor = connection.cursor()
-    return set(get_introspection_module().get_table_list(cursor))
-
-def django_table_names(only_existing=False):
-    """
-    Returns a list of all table names that have associated Django models and
-    are in INSTALLED_APPS.
-
-    If only_existing is True, the resulting list will only include the tables
-    that actually exist in the database.
-    """
-    from django.db import models
-    tables = set()
-    for app in models.get_apps():
-        for model in models.get_models(app):
-            tables.add(model._meta.db_table)
-            tables.update([f.m2m_db_table() for f in model._meta.local_many_to_many])
-    if only_existing:
-        tables = [t for t in tables if t in table_names()]
-    return tables
-
-def installed_models(table_list):
-    "Returns a set of all models that are installed, given a list of existing table names."
-    from django.db import connection, models
-    all_models = []
-    for app in models.get_apps():
-        for model in models.get_models(app):
-            all_models.append(model)
-    if connection.features.uses_case_insensitive_names:
-        converter = lambda x: x.upper()
-    else:
-        converter = lambda x: x
-    return set([m for m in all_models if converter(m._meta.db_table) in map(converter, table_list)])
-
-def sequence_list():
-    "Returns a list of information about all DB sequences for all models in all apps."
-    from django.db import models
-
-    apps = models.get_apps()
-    sequence_list = []
-
-    for app in apps:
-        for model in models.get_models(app):
-            for f in model._meta.local_fields:
-                if isinstance(f, models.AutoField):
-                    sequence_list.append({'table': model._meta.db_table, 'column': f.column})
-                    break # Only one AutoField is allowed per model, so don't bother continuing.
-
-            for f in model._meta.local_many_to_many:
-                sequence_list.append({'table': f.m2m_db_table(), 'column': None})
-
-    return sequence_list
-
 def sql_create(app, style):
     "Returns a list of the CREATE TABLE SQL statements for the given app."
-    from django.db import models
+    from django.db import connection, models
     from django.conf import settings
 
     if settings.DATABASE_ENGINE == 'dummy':
@@ -81,23 +25,24 @@
     # we can be conservative).
     app_models = models.get_models(app)
     final_output = []
-    known_models = set([model for model in installed_models(table_names()) if model not in app_models])
+    tables = connection.introspection.table_names()
+    known_models = set([model for model in connection.introspection.installed_models(tables) if model not in app_models])
     pending_references = {}
 
     for model in app_models:
-        output, references = sql_model_create(model, style, known_models)
+        output, references = connection.creation.sql_create_model(model, style, known_models)
         final_output.extend(output)
         for refto, refs in references.items():
             pending_references.setdefault(refto, []).extend(refs)
             if refto in known_models:
-                final_output.extend(sql_for_pending_references(refto, style, pending_references))
-        final_output.extend(sql_for_pending_references(model, style, pending_references))
+                final_output.extend(connection.creation.sql_for_pending_references(refto, style, pending_references))
+        final_output.extend(connection.creation.sql_for_pending_references(model, style, pending_references))
         # Keep track of the fact that we've created the table for this model.
         known_models.add(model)
 
     # Create the many-to-many join tables.
     for model in app_models:
-        final_output.extend(many_to_many_sql_for_model(model, style))
+        final_output.extend(connection.creation.sql_for_many_to_many(model, style))
 
     # Handle references to tables that are from other apps
     # but don't exist physically.
@@ -106,7 +51,7 @@
         alter_sql = []
         for model in not_installed_models:
             alter_sql.extend(['-- ' + sql for sql in
-                sql_for_pending_references(model, style, pending_references)])
+                connection.creation.sql_for_pending_references(model, style, pending_references)])
         if alter_sql:
             final_output.append('-- The following references should be added but depend on non-existent tables:')
             final_output.extend(alter_sql)
@@ -115,10 +60,9 @@
 
 def sql_delete(app, style):
     "Returns a list of the DROP TABLE SQL statements for the given app."
-    from django.db import connection, models, get_introspection_module
+    from django.db import connection, models
     from django.db.backends.util import truncate_name
     from django.contrib.contenttypes import generic
-    introspection = get_introspection_module()
 
     # This should work even if a connection isn't available
     try:
@@ -128,16 +72,11 @@
 
     # Figure out which tables already exist
     if cursor:
-        table_names = introspection.get_table_list(cursor)
+        table_names = connection.introspection.get_table_list(cursor)
     else:
         table_names = []
-    if connection.features.uses_case_insensitive_names:
-        table_name_converter = lambda x: x.upper()
-    else:
-        table_name_converter = lambda x: x
 
     output = []
-    qn = connection.ops.quote_name
 
     # Output DROP TABLE statements for standard application tables.
     to_delete = set()
@@ -145,7 +84,7 @@
     references_to_delete = {}
     app_models = models.get_models(app)
     for model in app_models:
-        if cursor and table_name_converter(model._meta.db_table) in table_names:
+        if cursor and connection.introspection.table_name_converter(model._meta.db_table) in table_names:
             # The table exists, so it needs to be dropped
             opts = model._meta
             for f in opts.local_fields:
@@ -155,42 +94,15 @@
             to_delete.add(model)
 
     for model in app_models:
-        if cursor and table_name_converter(model._meta.db_table) in table_names:
-            # Drop the table now
-            output.append('%s %s;' % (style.SQL_KEYWORD('DROP TABLE'),
-                style.SQL_TABLE(qn(model._meta.db_table))))
-            if connection.features.supports_constraints and model in references_to_delete:
-                for rel_class, f in references_to_delete[model]:
-                    table = rel_class._meta.db_table
-                    col = f.column
-                    r_table = model._meta.db_table
-                    r_col = model._meta.get_field(f.rel.field_name).column
-                    r_name = '%s_refs_%s_%x' % (col, r_col, abs(hash((table, r_table))))
-                    output.append('%s %s %s %s;' % \
-                        (style.SQL_KEYWORD('ALTER TABLE'),
-                        style.SQL_TABLE(qn(table)),
-                        style.SQL_KEYWORD(connection.ops.drop_foreignkey_sql()),
-                        style.SQL_FIELD(truncate_name(r_name, connection.ops.max_name_length()))))
-                del references_to_delete[model]
-            if model._meta.has_auto_field:
-                ds = connection.ops.drop_sequence_sql(model._meta.db_table)
-                if ds:
-                    output.append(ds)
+        if connection.introspection.table_name_converter(model._meta.db_table) in table_names:
+            output.extend(connection.creation.sql_destroy_model(model, references_to_delete, style))
 
     # Output DROP TABLE statements for many-to-many tables.
     for model in app_models:
         opts = model._meta
         for f in opts.local_many_to_many:
-            if isinstance(f.rel, generic.GenericRel):
-                continue
-            if cursor and table_name_converter(f.m2m_db_table()) in table_names:
-                output.append("%s %s;" % (style.SQL_KEYWORD('DROP TABLE'),
-                    style.SQL_TABLE(qn(f.m2m_db_table()))))
-                ds = connection.ops.drop_sequence_sql("%s_%s" % (model._meta.db_table, f.column))
-                if ds:
-                    output.append(ds)
-
-    app_label = app_models[0]._meta.app_label
+            if cursor and connection.introspection.table_name_converter(f.m2m_db_table()) in table_names:
+                output.extend(connection.creation.sql_destroy_many_to_many(model, f, style))
 
     # Close database connection explicitly, in case this output is being piped
     # directly into a database client, to avoid locking issues.
@@ -213,13 +125,13 @@
     """
     from django.db import connection
     if only_django:
-        tables = django_table_names()
+        tables = connection.introspection.django_table_names()
     else:
-        tables = table_names()
-    statements = connection.ops.sql_flush(style, tables, sequence_list())
+        tables = connection.introspection.table_names()
+    statements = connection.ops.sql_flush(style, tables, connection.introspection.sequence_list())
     return statements
 
-def sql_custom(app):
+def sql_custom(app, style):
     "Returns a list of the custom table modifying SQL statements for the given app."
     from django.db.models import get_models
     output = []
@@ -228,205 +140,23 @@
     app_dir = os.path.normpath(os.path.join(os.path.dirname(app.__file__), 'sql'))
 
     for model in app_models:
-        output.extend(custom_sql_for_model(model))
+        output.extend(custom_sql_for_model(model, style))
 
     return output
 
 def sql_indexes(app, style):
     "Returns a list of the CREATE INDEX SQL statements for all models in the given app."
-    from django.db import models
+    from django.db import connection, models
     output = []
     for model in models.get_models(app):
-        output.extend(sql_indexes_for_model(model, style))
+        output.extend(connection.creation.sql_indexes_for_model(model, style))
     return output
 
 def sql_all(app, style):
     "Returns a list of CREATE TABLE SQL, initial-data inserts, and CREATE INDEX SQL for the given module."
-    return sql_create(app, style) + sql_custom(app) + sql_indexes(app, style)
-
-def sql_model_create(model, style, known_models=set()):
-    """
-    Returns the SQL required to create a single model, as a tuple of:
-        (list_of_sql, pending_references_dict)
-    """
-    from django.db import connection, models
-
-    opts = model._meta
-    final_output = []
-    table_output = []
-    pending_references = {}
-    qn = connection.ops.quote_name
-    inline_references = connection.features.inline_fk_references
-    for f in opts.local_fields:
-        col_type = f.db_type()
-        tablespace = f.db_tablespace or opts.db_tablespace
-        if col_type is None:
-            # Skip ManyToManyFields, because they're not represented as
-            # database columns in this table.
-            continue
-        # Make the definition (e.g. 'foo VARCHAR(30)') for this field.
-        field_output = [style.SQL_FIELD(qn(f.column)),
-            style.SQL_COLTYPE(col_type)]
-        field_output.append(style.SQL_KEYWORD('%sNULL' % (not f.null and 'NOT ' or '')))
-        if f.unique and (not f.primary_key or connection.features.allows_unique_and_pk):
-            field_output.append(style.SQL_KEYWORD('UNIQUE'))
-        if f.primary_key:
-            field_output.append(style.SQL_KEYWORD('PRIMARY KEY'))
-        if tablespace and connection.features.supports_tablespaces and (f.unique or f.primary_key) and connection.features.autoindexes_primary_keys:
-            # We must specify the index tablespace inline, because we
-            # won't be generating a CREATE INDEX statement for this field.
-            field_output.append(connection.ops.tablespace_sql(tablespace, inline=True))
-        if f.rel:
-            if inline_references and f.rel.to in known_models:
-                field_output.append(style.SQL_KEYWORD('REFERENCES') + ' ' + \
-                    style.SQL_TABLE(qn(f.rel.to._meta.db_table)) + ' (' + \
-                    style.SQL_FIELD(qn(f.rel.to._meta.get_field(f.rel.field_name).column)) + ')' +
-                    connection.ops.deferrable_sql()
-                )
-            else:
-                # We haven't yet created the table to which this field
-                # is related, so save it for later.
-                pr = pending_references.setdefault(f.rel.to, []).append((model, f))
-        table_output.append(' '.join(field_output))
-    if opts.order_with_respect_to:
-        table_output.append(style.SQL_FIELD(qn('_order')) + ' ' + \
-            style.SQL_COLTYPE(models.IntegerField().db_type()) + ' ' + \
-            style.SQL_KEYWORD('NULL'))
-    for field_constraints in opts.unique_together:
-        table_output.append(style.SQL_KEYWORD('UNIQUE') + ' (%s)' % \
-            ", ".join([style.SQL_FIELD(qn(opts.get_field(f).column)) for f in field_constraints]))
-
-    full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(qn(opts.db_table)) + ' (']
-    for i, line in enumerate(table_output): # Combine and add commas.
-        full_statement.append('    %s%s' % (line, i < len(table_output)-1 and ',' or ''))
-    full_statement.append(')')
-    if opts.db_tablespace and connection.features.supports_tablespaces:
-        full_statement.append(connection.ops.tablespace_sql(opts.db_tablespace))
-    full_statement.append(';')
-    final_output.append('\n'.join(full_statement))
-
-    if opts.has_auto_field:
-        # Add any extra SQL needed to support auto-incrementing primary keys.
-        auto_column = opts.auto_field.db_column or opts.auto_field.name
-        autoinc_sql = connection.ops.autoinc_sql(opts.db_table, auto_column)
-        if autoinc_sql:
-            for stmt in autoinc_sql:
-                final_output.append(stmt)
-
-    return final_output, pending_references
-
-def sql_for_pending_references(model, style, pending_references):
-    """
-    Returns any ALTER TABLE statements to add constraints after the fact.
-    """
-    from django.db import connection
-    from django.db.backends.util import truncate_name
+    return sql_create(app, style) + sql_custom(app, style) + sql_indexes(app, style)
 
-    qn = connection.ops.quote_name
-    final_output = []
-    if connection.features.supports_constraints:
-        opts = model._meta
-        if model in pending_references:
-            for rel_class, f in pending_references[model]:
-                rel_opts = rel_class._meta
-                r_table = rel_opts.db_table
-                r_col = f.column
-                table = opts.db_table
-                col = opts.get_field(f.rel.field_name).column
-                # For MySQL, r_name must be unique in the first 64 characters.
-                # So we are careful with character usage here.
-                r_name = '%s_refs_%s_%x' % (r_col, col, abs(hash((r_table, table))))
-                final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \
-                    (qn(r_table), truncate_name(r_name, connection.ops.max_name_length()),
-                    qn(r_col), qn(table), qn(col),
-                    connection.ops.deferrable_sql()))
-            del pending_references[model]
-    return final_output
-
-def many_to_many_sql_for_model(model, style):
-    from django.db import connection, models
-    from django.contrib.contenttypes import generic
-    from django.db.backends.util import truncate_name
-
-    opts = model._meta
-    final_output = []
-    qn = connection.ops.quote_name
-    inline_references = connection.features.inline_fk_references
-    for f in opts.local_many_to_many:
-        if not isinstance(f.rel, generic.GenericRel):
-            tablespace = f.db_tablespace or opts.db_tablespace
-            if tablespace and connection.features.supports_tablespaces and connection.features.autoindexes_primary_keys:
-                tablespace_sql = ' ' + connection.ops.tablespace_sql(tablespace, inline=True)
-            else:
-                tablespace_sql = ''
-            table_output = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + \
-                style.SQL_TABLE(qn(f.m2m_db_table())) + ' (']
-            table_output.append('    %s %s %s%s,' %
-                (style.SQL_FIELD(qn('id')),
-                style.SQL_COLTYPE(models.AutoField(primary_key=True).db_type()),
-                style.SQL_KEYWORD('NOT NULL PRIMARY KEY'),
-                tablespace_sql))
-            if inline_references:
-                deferred = []
-                table_output.append('    %s %s %s %s (%s)%s,' %
-                    (style.SQL_FIELD(qn(f.m2m_column_name())),
-                    style.SQL_COLTYPE(models.ForeignKey(model).db_type()),
-                    style.SQL_KEYWORD('NOT NULL REFERENCES'),
-                    style.SQL_TABLE(qn(opts.db_table)),
-                    style.SQL_FIELD(qn(opts.pk.column)),
-                    connection.ops.deferrable_sql()))
-                table_output.append('    %s %s %s %s (%s)%s,' %
-                    (style.SQL_FIELD(qn(f.m2m_reverse_name())),
-                    style.SQL_COLTYPE(models.ForeignKey(f.rel.to).db_type()),
-                    style.SQL_KEYWORD('NOT NULL REFERENCES'),
-                    style.SQL_TABLE(qn(f.rel.to._meta.db_table)),
-                    style.SQL_FIELD(qn(f.rel.to._meta.pk.column)),
-                    connection.ops.deferrable_sql()))
-            else:
-                table_output.append('    %s %s %s,' %
-                    (style.SQL_FIELD(qn(f.m2m_column_name())),
-                    style.SQL_COLTYPE(models.ForeignKey(model).db_type()),
-                    style.SQL_KEYWORD('NOT NULL')))
-                table_output.append('    %s %s %s,' %
-                    (style.SQL_FIELD(qn(f.m2m_reverse_name())),
-                    style.SQL_COLTYPE(models.ForeignKey(f.rel.to).db_type()),
-                    style.SQL_KEYWORD('NOT NULL')))
-                deferred = [
-                    (f.m2m_db_table(), f.m2m_column_name(), opts.db_table,
-                        opts.pk.column),
-                    ( f.m2m_db_table(), f.m2m_reverse_name(),
-                        f.rel.to._meta.db_table, f.rel.to._meta.pk.column)
-                    ]
-            table_output.append('    %s (%s, %s)%s' %
-                (style.SQL_KEYWORD('UNIQUE'),
-                style.SQL_FIELD(qn(f.m2m_column_name())),
-                style.SQL_FIELD(qn(f.m2m_reverse_name())),
-                tablespace_sql))
-            table_output.append(')')
-            if opts.db_tablespace and connection.features.supports_tablespaces:
-                # f.db_tablespace is only for indices, so ignore its value here.
-                table_output.append(connection.ops.tablespace_sql(opts.db_tablespace))
-            table_output.append(';')
-            final_output.append('\n'.join(table_output))
-
-            for r_table, r_col, table, col in deferred:
-                r_name = '%s_refs_%s_%x' % (r_col, col,
-                        abs(hash((r_table, table))))
-                final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % 
-                (qn(r_table),
-                truncate_name(r_name, connection.ops.max_name_length()),
-                qn(r_col), qn(table), qn(col),
-                connection.ops.deferrable_sql()))
-
-            # Add any extra SQL needed to support auto-incrementing PKs
-            autoinc_sql = connection.ops.autoinc_sql(f.m2m_db_table(), 'id')
-            if autoinc_sql:
-                for stmt in autoinc_sql:
-                    final_output.append(stmt)
-
-    return final_output
-
-def custom_sql_for_model(model):
+def custom_sql_for_model(model, style):
     from django.db import models
     from django.conf import settings
 
@@ -434,6 +164,14 @@
     app_dir = os.path.normpath(os.path.join(os.path.dirname(models.get_app(model._meta.app_label).__file__), 'sql'))
     output = []
 
+    # Post-creation SQL should come before any initial SQL data is loaded.
+    # However, this should not be done for fields that are part of a a parent
+    # model (via model inheritance).
+    nm = opts.init_name_map()
+    post_sql_fields = [f for f in opts.local_fields if hasattr(f, 'post_create_sql')]
+    for f in post_sql_fields:
+        output.extend(f.post_create_sql(style, model._meta.db_table))
+
     # Some backends can't execute more than one SQL statement at a time,
     # so split into separate statements.
     statements = re.compile(r";[ \t]*$", re.M)
@@ -446,36 +184,13 @@
             fp = open(sql_file, 'U')
             for statement in statements.split(fp.read().decode(settings.FILE_CHARSET)):
                 # Remove any comments from the file
-                statement = re.sub(ur"--.*[\n\Z]", "", statement)
+                statement = re.sub(ur"--.*([\n\Z]|$)", "", statement)
                 if statement.strip():
                     output.append(statement + u";")
             fp.close()
 
     return output
 
-def sql_indexes_for_model(model, style):
-    "Returns the CREATE INDEX SQL statements for a single model"
-    from django.db import connection
-    output = []
-
-    qn = connection.ops.quote_name
-    for f in model._meta.local_fields:
-        if f.db_index and not ((f.primary_key or f.unique) and connection.features.autoindexes_primary_keys):
-            unique = f.unique and 'UNIQUE ' or ''
-            tablespace = f.db_tablespace or model._meta.db_tablespace
-            if tablespace and connection.features.supports_tablespaces:
-                tablespace_sql = ' ' + connection.ops.tablespace_sql(tablespace)
-            else:
-                tablespace_sql = ''
-            output.append(
-                style.SQL_KEYWORD('CREATE %sINDEX' % unique) + ' ' + \
-                style.SQL_TABLE(qn('%s_%s' % (model._meta.db_table, f.column))) + ' ' + \
-                style.SQL_KEYWORD('ON') + ' ' + \
-                style.SQL_TABLE(qn(model._meta.db_table)) + ' ' + \
-                "(%s)" % style.SQL_FIELD(qn(f.column)) + \
-                "%s;" % tablespace_sql
-            )
-    return output
 
 def emit_post_sync_signal(created_models, verbosity, interactive):
     from django.db import models
@@ -485,6 +200,6 @@
         app_name = app.__name__.split('.')[-2]
         if verbosity >= 2:
             print "Running post-sync handlers for application", app_name
-        dispatcher.send(signal=models.signals.post_syncdb, sender=app,
-            app=app, created_models=created_models,
-            verbosity=verbosity, interactive=interactive)
+        models.signals.post_syncdb.send(sender=app, app=app,
+            created_models=created_models, verbosity=verbosity,
+            interactive=interactive)