From c05d52cdfe8955e9fb501497087367cd4daafbf6 Mon Sep 17 00:00:00 2001 From: Derek Anderson Date: Fri, 3 Aug 2007 16:48:51 +0000 Subject: [PATCH] schema-evolution: moved most of the sql_evolve code into its own module git-svn-id: http://code.djangoproject.com/svn/django/branches/schema-evolution@5789 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/management.py | 158 ++------------------------------ django/core/schema_evolution.py | 153 +++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+), 150 deletions(-) create mode 100644 django/core/schema_evolution.py diff --git a/django/core/management.py b/django/core/management.py index 96230c2a48..a843c411e1 100644 --- a/django/core/management.py +++ b/django/core/management.py @@ -481,9 +481,10 @@ def get_sql_indexes_for_model(model): "%s;" % tablespace_sql ) return output - + def get_sql_evolution(app): "Returns SQL to update an existing schema to match the existing models." + import schema_evolution from django.db import get_creation_module, models, backend, get_introspection_module, connection data_types = get_creation_module().DATA_TYPES @@ -532,169 +533,26 @@ def get_sql_evolution(app): for klass in app_models: - output, new_table_name = get_sql_evolution_check_for_changed_model_name(klass) + output, new_table_name = schema_evolution.get_sql_evolution_check_for_changed_model_name(klass) final_output.extend(output) - output = get_sql_evolution_check_for_changed_field_flags(klass, new_table_name) + output = schema_evolution.get_sql_evolution_check_for_changed_field_flags(klass, new_table_name) final_output.extend(output) - output = get_sql_evolution_check_for_changed_field_name(klass, new_table_name) + output = schema_evolution.get_sql_evolution_check_for_changed_field_name(klass, new_table_name) final_output.extend(output) - output = get_sql_evolution_check_for_new_fields(klass, new_table_name) + output = schema_evolution.get_sql_evolution_check_for_new_fields(klass, new_table_name) final_output.extend(output) - output = get_sql_evolution_check_for_dead_fields(klass, new_table_name) + output = schema_evolution.get_sql_evolution_check_for_dead_fields(klass, new_table_name) final_output.extend(output) return final_output + get_sql_evolution.help_doc = "Returns SQL to update an existing schema to match the existing models." get_sql_evolution.args = APP_ARGS -def get_sql_evolution_check_for_new_fields(klass, new_table_name): - "checks for model fields that are not in the existing data structure" - from django.db import backend, get_creation_module, models, get_introspection_module, connection - data_types = get_creation_module().DATA_TYPES - cursor = connection.cursor() - introspection = get_introspection_module() - opts = klass._meta - output = [] - db_table = klass._meta.db_table - if new_table_name: - db_table = new_table_name - for f in opts.fields: - existing_fields = introspection.get_columns(cursor,db_table) - if f.column not in existing_fields and (not f.aka or f.aka not in existing_fields and len(set(f.aka) & set(existing_fields))==0): - rel_field = f - data_type = f.get_internal_type() - col_type = data_types[data_type] - if col_type is not None: - output.extend( backend.get_add_column_sql( db_table, f.column, style.SQL_COLTYPE(col_type % rel_field.__dict__), f.null, f.unique, f.primary_key ) ) - return output - -def get_sql_evolution_check_for_changed_model_name(klass): - from django.db import backend, get_creation_module, models, get_introspection_module, connection - cursor = connection.cursor() - introspection = get_introspection_module() - table_list = introspection.get_table_list(cursor) - if klass._meta.db_table in table_list: - return [], None - if klass._meta.aka in table_list: - return backend.get_change_table_name_sql( klass._meta.db_table, klass._meta.aka), klass._meta.aka - elif len(set(klass._meta.aka) & set(table_list))==1: - return backend.get_change_table_name_sql( klass._meta.db_table, klass._meta.aka[0]), klass._meta.aka[0] - else: - return [], None - -def get_sql_evolution_check_for_changed_field_name(klass, new_table_name): - from django.db import backend, get_creation_module, models, get_introspection_module, connection - data_types = get_creation_module().DATA_TYPES - cursor = connection.cursor() - introspection = get_introspection_module() - opts = klass._meta - output = [] - db_table = klass._meta.db_table - if new_table_name: - db_table = new_table_name - for f in opts.fields: - existing_fields = introspection.get_columns(cursor,db_table) - if f.column not in existing_fields and f.aka and (f.aka in existing_fields or len(set(f.aka) & set(existing_fields)))==1: - old_col = None - if isinstance( f.aka, str ): - old_col = f.aka - else: - old_col = f.aka[0] - rel_field = f - data_type = f.get_internal_type() - col_type = data_types[data_type] - if col_type is not None: - col_def = style.SQL_COLTYPE(col_type % rel_field.__dict__) +' '+ style.SQL_KEYWORD('%sNULL' % (not f.null and 'NOT ' or '')) - if f.unique: - col_def += style.SQL_KEYWORD(' UNIQUE') - if f.primary_key: - col_def += style.SQL_KEYWORD(' PRIMARY KEY') - output.extend( backend.get_change_column_name_sql( klass._meta.db_table, introspection.get_indexes(cursor,db_table), old_col, f.column, col_def ) ) - return output - -def get_sql_evolution_check_for_changed_field_flags(klass, new_table_name): - from django.db import backend, get_creation_module, models, get_introspection_module, connection - from django.db.models.fields import CharField, SlugField - from django.db.models.fields.related import RelatedField, ForeignKey - data_types = get_creation_module().DATA_TYPES - cursor = connection.cursor() - introspection = get_introspection_module() - opts = klass._meta - output = [] - db_table = klass._meta.db_table - if new_table_name: - db_table = new_table_name - for f in opts.fields: - existing_fields = introspection.get_columns(cursor,db_table) -# print existing_fields - cf = None # current field, ie what it is before any renames - if f.column in existing_fields: - cf = f.column - elif f.aka in existing_fields: - cf = f.aka - elif f.aka and len(set(f.aka) & set(existing_fields))==1: - cf = f.aka[0] - else: - continue # no idea what column you're talking about - should be handled by get_sql_evolution_check_for_new_fields()) - data_type = f.get_internal_type() - if data_types.has_key(data_type): - column_flags = introspection.get_known_column_flags(cursor, db_table, cf) -# print db_table, cf, column_flags - if column_flags['allow_null']!=f.null or \ - ( not f.primary_key and isinstance(f, CharField) and column_flags['maxlength']!=str(f.maxlength) ) or \ - ( not f.primary_key and isinstance(f, SlugField) and column_flags['maxlength']!=str(f.maxlength) ) or \ - ( column_flags['unique']!=f.unique and ( settings.DATABASE_ENGINE!='postgresql' or not f.primary_key ) ) or \ - column_flags['primary_key']!=f.primary_key: - #column_flags['foreign_key']!=f.foreign_key: -# print 'need to change' -# print db_table, f.column, column_flags -# print "column_flags['allow_null']!=f.null", column_flags['allow_null']!=f.null -# print "not f.primary_key and isinstance(f, CharField) and column_flags['maxlength']!=str(f.maxlength)", not f.primary_key and isinstance(f, CharField) and column_flags['maxlength']!=str(f.maxlength) -# print "not f.primary_key and isinstance(f, SlugField) and column_flags['maxlength']!=str(f.maxlength)", not f.primary_key and isinstance(f, SlugField) and column_flags['maxlength']!=str(f.maxlength) -# print "column_flags['unique']!=f.unique", column_flags['unique']!=f.unique -# print "column_flags['primary_key']!=f.primary_key", column_flags['primary_key']!=f.primary_key - col_type = data_types[data_type] - col_type_def = style.SQL_COLTYPE(col_type % f.__dict__) -# col_def = style.SQL_COLTYPE(col_type % f.__dict__) +' '+ style.SQL_KEYWORD('%sNULL' % (not f.null and 'NOT ' or '')) -# if f.unique: -# col_def += ' '+ style.SQL_KEYWORD('UNIQUE') -# if f.primary_key: -# col_def += ' '+ style.SQL_KEYWORD('PRIMARY KEY') - output.extend( backend.get_change_column_def_sql( db_table, cf, col_type_def, f.null, f.unique, f.primary_key ) ) - #print db_table, cf, f.maxlength, introspection.get_known_column_flags(cursor, db_table, cf) - return output - -def get_sql_evolution_check_for_dead_fields(klass, new_table_name): - from django.db import backend, get_creation_module, models, get_introspection_module, connection - from django.db.models.fields import CharField, SlugField - from django.db.models.fields.related import RelatedField, ForeignKey - data_types = get_creation_module().DATA_TYPES - cursor = connection.cursor() - introspection = get_introspection_module() - opts = klass._meta - output = [] - db_table = klass._meta.db_table - if new_table_name: - db_table = new_table_name - suspect_fields = set(introspection.get_columns(cursor,db_table)) -# print 'suspect_fields = ', suspect_fields - for f in opts.fields: -# print 'f = ', f -# print 'f.aka = ', f.aka - suspect_fields.discard(f.column) - suspect_fields.discard(f.aka) - if f.aka: suspect_fields.difference_update(f.aka) - if len(suspect_fields)>0: - output.append( '-- warning: the following may cause data loss' ) - for suspect_field in suspect_fields: - output.extend( backend.get_drop_column_sql( db_table, suspect_field ) ) - output.append( '-- end warning' ) - return output - def get_sql_all(app): "Returns a list of CREATE TABLE SQL, initial-data inserts, and CREATE INDEX SQL for the given module." return get_sql_create(app) + get_custom_sql(app) + get_sql_indexes(app) diff --git a/django/core/schema_evolution.py b/django/core/schema_evolution.py new file mode 100644 index 0000000000..76660827d3 --- /dev/null +++ b/django/core/schema_evolution.py @@ -0,0 +1,153 @@ +import django +from django.core.exceptions import ImproperlyConfigured +from optparse import OptionParser +from django.utils import termcolors +from django.conf import settings +import os, re, shutil, sys, textwrap +import management + + +def get_sql_evolution_check_for_new_fields(klass, new_table_name): + "checks for model fields that are not in the existing data structure" + from django.db import backend, get_creation_module, models, get_introspection_module, connection + data_types = get_creation_module().DATA_TYPES + cursor = connection.cursor() + introspection = get_introspection_module() + opts = klass._meta + output = [] + db_table = klass._meta.db_table + if new_table_name: + db_table = new_table_name + for f in opts.fields: + existing_fields = introspection.get_columns(cursor,db_table) + if f.column not in existing_fields and (not f.aka or f.aka not in existing_fields and len(set(f.aka) & set(existing_fields))==0): + rel_field = f + data_type = f.get_internal_type() + col_type = data_types.get(data_type) + if col_type is not None: + output.extend( backend.get_add_column_sql( klass._meta.db_table, f.column, management.style.SQL_COLTYPE(col_type % rel_field.__dict__), f.null, f.unique, f.primary_key ) ) + return output + +def get_sql_evolution_check_for_changed_model_name(klass): + from django.db import backend, get_creation_module, models, get_introspection_module, connection + cursor = connection.cursor() + introspection = get_introspection_module() + table_list = introspection.get_table_list(cursor) + if klass._meta.db_table in table_list: + return [], None + if klass._meta.aka in table_list: + return backend.get_change_table_name_sql( klass._meta.db_table, klass._meta.aka), klass._meta.aka + elif len(set(klass._meta.aka) & set(table_list))==1: + return backend.get_change_table_name_sql( klass._meta.db_table, klass._meta.aka[0]), klass._meta.aka[0] + else: + return [], None + +def get_sql_evolution_check_for_changed_field_name(klass, new_table_name): + from django.db import backend, get_creation_module, models, get_introspection_module, connection + data_types = get_creation_module().DATA_TYPES + cursor = connection.cursor() + introspection = get_introspection_module() + opts = klass._meta + output = [] + db_table = klass._meta.db_table + if new_table_name: + db_table = new_table_name + for f in opts.fields: + existing_fields = introspection.get_columns(cursor,db_table) + if f.column not in existing_fields and f.aka and (f.aka in existing_fields or len(set(f.aka) & set(existing_fields)))==1: + old_col = None + if isinstance( f.aka, str ): + old_col = f.aka + else: + old_col = f.aka[0] + rel_field = f + data_type = f.get_internal_type() + col_type = data_types[data_type] + if col_type is not None: + col_def = management.style.SQL_COLTYPE(col_type % rel_field.__dict__) +' '+ management.style.SQL_KEYWORD('%sNULL' % (not f.null and 'NOT ' or '')) + if f.unique: + col_def += management.style.SQL_KEYWORD(' UNIQUE') + if f.primary_key: + col_def += management.style.SQL_KEYWORD(' PRIMARY KEY') + output.extend( backend.get_change_column_name_sql( klass._meta.db_table, introspection.get_indexes(cursor,db_table), old_col, f.column, col_def ) ) + return output + +def get_sql_evolution_check_for_changed_field_flags(klass, new_table_name): + from django.db import backend, get_creation_module, models, get_introspection_module, connection + from django.db.models.fields import CharField, SlugField + from django.db.models.fields.related import RelatedField, ForeignKey + data_types = get_creation_module().DATA_TYPES + cursor = connection.cursor() + introspection = get_introspection_module() + opts = klass._meta + output = [] + db_table = klass._meta.db_table + if new_table_name: + db_table = new_table_name + for f in opts.fields: + existing_fields = introspection.get_columns(cursor,db_table) +# print existing_fields + cf = None # current field, ie what it is before any renames + if f.column in existing_fields: + cf = f.column + elif f.aka in existing_fields: + cf = f.aka + elif f.aka and len(set(f.aka) & set(existing_fields))==1: + cf = f.aka[0] + else: + continue # no idea what column you're talking about - should be handled by get_sql_evolution_check_for_new_fields()) + data_type = f.get_internal_type() + if data_types.has_key(data_type): + column_flags = introspection.get_known_column_flags(cursor, db_table, cf) +# print db_table, cf, column_flags + if column_flags['allow_null']!=f.null or \ + ( not f.primary_key and isinstance(f, CharField) and column_flags['maxlength']!=str(f.maxlength) ) or \ + ( not f.primary_key and isinstance(f, SlugField) and column_flags['maxlength']!=str(f.maxlength) ) or \ + ( column_flags['unique']!=f.unique and ( settings.DATABASE_ENGINE!='postgresql' or not f.primary_key ) ) or \ + column_flags['primary_key']!=f.primary_key: + #column_flags['foreign_key']!=f.foreign_key: +# print 'need to change' +# print db_table, f.column, column_flags +# print "column_flags['allow_null']!=f.null", column_flags['allow_null']!=f.null +# print "not f.primary_key and isinstance(f, CharField) and column_flags['maxlength']!=str(f.maxlength)", not f.primary_key and isinstance(f, CharField) and column_flags['maxlength']!=str(f.maxlength) +# print "not f.primary_key and isinstance(f, SlugField) and column_flags['maxlength']!=str(f.maxlength)", not f.primary_key and isinstance(f, SlugField) and column_flags['maxlength']!=str(f.maxlength) +# print "column_flags['unique']!=f.unique", column_flags['unique']!=f.unique +# print "column_flags['primary_key']!=f.primary_key", column_flags['primary_key']!=f.primary_key + col_type = data_types[data_type] + col_type_def = management.style.SQL_COLTYPE(col_type % f.__dict__) +# col_def = style.SQL_COLTYPE(col_type % f.__dict__) +' '+ style.SQL_KEYWORD('%sNULL' % (not f.null and 'NOT ' or '')) +# if f.unique: +# col_def += ' '+ style.SQL_KEYWORD('UNIQUE') +# if f.primary_key: +# col_def += ' '+ style.SQL_KEYWORD('PRIMARY KEY') + output.extend( backend.get_change_column_def_sql( klass._meta.db_table, cf, col_type_def, f.null, f.unique, f.primary_key ) ) + #print db_table, cf, f.maxlength, introspection.get_known_column_flags(cursor, db_table, cf) + return output + +def get_sql_evolution_check_for_dead_fields(klass, new_table_name): + from django.db import backend, get_creation_module, models, get_introspection_module, connection + from django.db.models.fields import CharField, SlugField + from django.db.models.fields.related import RelatedField, ForeignKey + data_types = get_creation_module().DATA_TYPES + cursor = connection.cursor() + introspection = get_introspection_module() + opts = klass._meta + output = [] + db_table = klass._meta.db_table + if new_table_name: + db_table = new_table_name + suspect_fields = set(introspection.get_columns(cursor,db_table)) +# print 'suspect_fields = ', suspect_fields + for f in opts.fields: +# print 'f = ', f +# print 'f.aka = ', f.aka + suspect_fields.discard(f.column) + suspect_fields.discard(f.aka) + if f.aka: suspect_fields.difference_update(f.aka) + if len(suspect_fields)>0: + output.append( '-- warning: the following may cause data loss' ) + for suspect_field in suspect_fields: + output.extend( backend.get_drop_column_sql( klass._meta.db_table, suspect_field ) ) + output.append( '-- end warning' ) + return output +