diff options
| author | Derek Anderson <public@kered.org> | 2007-08-06 14:19:27 +0000 |
|---|---|---|
| committer | Derek Anderson <public@kered.org> | 2007-08-06 14:19:27 +0000 |
| commit | 0af6ed0c4853e11086e277ba352d27db4c466c89 (patch) | |
| tree | eec872ac94269364e3b9c04a7544c9afdc825d11 /django | |
| parent | db79faa3285361e6f6778bfc003edd8844196f7e (diff) | |
schema-evolution:
added support for custom migration scripts
git-svn-id: http://code.djangoproject.com/svn/django/branches/schema-evolution@5821 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django')
| -rw-r--r-- | django/core/management.py | 93 | ||||
| -rw-r--r-- | django/db/backends/mysql/introspection.py | 30 |
2 files changed, 112 insertions, 11 deletions
diff --git a/django/core/management.py b/django/core/management.py index 96723235fe..e12f6da78f 100644 --- a/django/core/management.py +++ b/django/core/management.py @@ -482,8 +482,37 @@ def get_sql_indexes_for_model(model): ) return output +def get_sql_fingerprint(app): + "Returns the fingerprint of the current schema, used in schema evolution." + from django.db import get_creation_module, models, backend, get_introspection_module, connection + # This should work even if a connecton isn't available + try: + cursor = connection.cursor() + except: + cursor = None + introspection = get_introspection_module() + app_name = app.__name__.split('.')[-2] + schema_fingerprint = introspection.get_schema_fingerprint(cursor, app) + try: + # is this a schema we recognize? + app_se = __import__(app_name +'.schema_evolution').schema_evolution + schema_recognized = schema_fingerprint in app_se.fingerprints + if schema_recognized: + sys.stderr.write(style.NOTICE("Notice: Current schema fingerprint for '%s' is '%s' (recognized)\n" % (app_name, schema_fingerprint))) + else: + sys.stderr.write(style.NOTICE("Notice: Current schema fingerprint for '%s' is '%s' (unrecognized)\n" % (app_name, schema_fingerprint))) + except: + sys.stderr.write(style.NOTICE("Notice: Current schema fingerprint for '%s' is '%s' (no schema_evolution module found)\n" % (app_name, schema_fingerprint))) + return +get_sql_fingerprint.help_doc = "Returns the fingerprint of the current schema, used in schema evolution." +get_sql_fingerprint.args = APP_ARGS + def get_sql_evolution(app): "Returns SQL to update an existing schema to match the existing models." + return get_sql_evolution_detailed(app)[2] + +def get_sql_evolution_detailed(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 @@ -507,8 +536,47 @@ def get_sql_evolution(app): # First, try validating the models. _check_for_validation_errors() + # This should work even if a connecton isn't available + try: + cursor = connection.cursor() + except: + cursor = None + + introspection = get_introspection_module() + app_name = app.__name__.split('.')[-2] + final_output = [] + schema_fingerprint = introspection.get_schema_fingerprint(cursor, app) + try: + # is this a schema we recognize? + app_se = __import__(app_name +'.schema_evolution').schema_evolution + schema_recognized = schema_fingerprint in app_se.fingerprints + if schema_recognized: + sys.stderr.write(style.NOTICE("Notice: Current schema fingerprint for '%s' is '%s' (recognized)\n" % (app_name, schema_fingerprint))) + available_upgrades = [] + for (vfrom, vto), upgrade in app_se.evolutions.iteritems(): + if vfrom == schema_fingerprint: + try: + distance = app_se.fingerprints.index(vto)-app_se.fingerprints.index(vfrom) + available_upgrades.append( ( vfrom, vto, upgrade, distance ) ) + sys.stderr.write(style.NOTICE("\tan upgrade from %s to %s is available (distance: %i)\n" % ( vfrom, vto, distance ))) + except: + available_upgrades.append( ( vfrom, vto, upgrade, -1 ) ) + sys.stderr.write(style.NOTICE("\tan upgrade from %s to %s is available, but %s is not in schema_evolution.fingerprints\n" % ( vfrom, vto, vto ))) + if len(available_upgrades): + best_upgrade = available_upgrades[0] + for an_upgrade in available_upgrades: + if an_upgrade[3] > best_upgrade[3]: + best_upgrade = an_upgrade + final_output.extend( best_upgrade[2] ) + return schema_fingerprint, False, final_output + else: + sys.stderr.write(style.NOTICE("Notice: Current schema fingerprint for '%s' is '%s' (unrecognized)\n" % (app_name, schema_fingerprint))) + except: + # sys.stderr.write(style.NOTICE("Notice: Current schema fingerprint for '%s' is '%s' (no schema_evolution module found)\n" % (app_name, schema_fingerprint))) + pass # ^^^ lets not be chatty + # stolen and trimmed from syncdb so that we know which models are about # to be created (so we don't check them for updates) table_list = _get_table_list() @@ -526,13 +594,6 @@ def get_sql_evolution(app): created_models.add(model) table_list.append(model._meta.db_table) - introspection = get_introspection_module() - # This should work even if a connecton isn't available - try: - cursor = connection.cursor() - except: - cursor = None - # get the existing models, minus the models we've just created app_models = models.get_models(app) for model in created_models: @@ -556,7 +617,7 @@ def get_sql_evolution(app): output = schema_evolution.get_sql_evolution_check_for_dead_fields(klass, new_table_name) final_output.extend(output) - return final_output + return schema_fingerprint, True, 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 @@ -648,9 +709,18 @@ def syncdb(verbosity=1, interactive=True): for statement in sql: cursor.execute(statement) - for sql in get_sql_evolution(app): - print sql -# cursor.execute(sql) + # keep evolving until there is nothing left to do + schema_fingerprint, introspected_upgrade, evolution = get_sql_evolution_detailed(app) + last_schema_fingerprint = None + while evolution and schema_fingerprint!=last_schema_fingerprint: + for sql in evolution: + if introspected_upgrade: + print sql + else: + cursor.execute(sql) + last_schema_fingerprint = schema_fingerprint + if not introspected_upgrade: # only do one round of introspection generated upgrades + schema_fingerprint, introspected_upgrade, evolution = get_sql_evolution_detailed(app) transaction.commit_unless_managed() @@ -1602,6 +1672,7 @@ DEFAULT_ACTION_MAPPING = { 'sqlreset': get_sql_reset, 'sqlsequencereset': get_sql_sequence_reset, 'sqlevolve': get_sql_evolution, + 'sqlfingerprint': get_sql_fingerprint, 'startapp': startapp, 'startproject': startproject, 'syncdb': syncdb, diff --git a/django/db/backends/mysql/introspection.py b/django/db/backends/mysql/introspection.py index 7e3e174db8..59658b0a6c 100644 --- a/django/db/backends/mysql/introspection.py +++ b/django/db/backends/mysql/introspection.py @@ -109,6 +109,36 @@ def get_known_column_flags( cursor, table_name, column_name ): # print table_name, column_name, dict return dict + +def get_schema_fingerprint(cursor, app): + """it's important that the output of these methods don't change, otherwise the hashes they + produce will be inconsistent (and detection of existing schemas will fail. unless you are + absolutely sure the outout for ALL valid inputs will remain the same, you should bump the version by creating a new method""" + return get_schema_fingerprint_fv1(cursor, app) + +def get_schema_fingerprint_fv1(cursor, app): + from django.db import models + app_name = app.__name__.split('.')[-2] + + schema = ['app_name := '+ app_name] + + cursor.execute('SHOW TABLES;') + for table_name in [row[0] for row in cursor.fetchall()]: + if not table_name.startswith(app_name): + continue # skip tables not in this app + schema.append('table_name := '+ table_name) + cursor.execute("describe %s" % quote_name(table_name)) + for row in cursor.fetchall(): + tmp = [] + for x in row: + tmp.append(str(x)) + schema.append( '\t'.join(tmp) ) + cursor.execute("SHOW INDEX FROM %s" % quote_name(table_name)) + for row in cursor.fetchall(): + schema.append( '\t'.join([ str(row[0]), str(row[1]), str(row[2]), str(row[3]), str(row[4]), str(row[5]), str(row[9]), ]) ) + + return 'fv1:'+ str('\n'.join(schema).__hash__()) + DATA_TYPES_REVERSE = { FIELD_TYPE.BLOB: 'TextField', |
