diff -r 6641e941ef1e -r ff1a9aa48cfd app/django/core/management/sql.py --- 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)