summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorDerek Anderson <public@kered.org>2007-08-06 14:19:27 +0000
committerDerek Anderson <public@kered.org>2007-08-06 14:19:27 +0000
commit0af6ed0c4853e11086e277ba352d27db4c466c89 (patch)
treeeec872ac94269364e3b9c04a7544c9afdc825d11 /django
parentdb79faa3285361e6f6778bfc003edd8844196f7e (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.py93
-rw-r--r--django/db/backends/mysql/introspection.py30
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',