summaryrefslogtreecommitdiff
path: root/migrate/versioning
diff options
context:
space:
mode:
authorchristian.simms <unknown>2008-03-24 14:32:45 +0000
committerchristian.simms <unknown>2008-03-24 14:32:45 +0000
commit8e753b52e0feeb4365a6d83f13dc0f7517a5f7a3 (patch)
treec3237c1f14368fdcad38bf2cd4bc8e18a2f4c55c /migrate/versioning
parent373a111c2c629ec0d92b01bf1c4dc8e2cdd5d2d7 (diff)
downloadsqlalchemy-migrate-8e753b52e0feeb4365a6d83f13dc0f7517a5f7a3.tar.gz
rename model/db sync commands and add new command update_db_from_model
Diffstat (limited to 'migrate/versioning')
-rw-r--r--migrate/versioning/api.py35
-rw-r--r--migrate/versioning/genmodel.py67
-rw-r--r--migrate/versioning/schema.py19
-rw-r--r--migrate/versioning/schemadiff.py6
-rw-r--r--migrate/versioning/script/py.py2
5 files changed, 108 insertions, 21 deletions
diff --git a/migrate/versioning/api.py b/migrate/versioning/api.py
index cd5467a..32ed33f 100644
--- a/migrate/versioning/api.py
+++ b/migrate/versioning/api.py
@@ -11,7 +11,7 @@ __all__=[
'help',
'create',
'script',
-'script_python_changes',
+'make_update_script_for_model',
'commit',
'version',
'source',
@@ -22,8 +22,9 @@ __all__=[
'drop_version_control',
'manage',
'test',
-'compare_db',
-'db_schema_dump',
+'compare_model_to_db',
+'create_model',
+'update_db_from_model',
]
cls_repository = repository.Repository
@@ -280,28 +281,28 @@ def manage(file,**opts):
"""
return repository.manage(file,**opts)
-def compare_db(url,model,repository,**opts):
- """%prog compare_db URL MODEL REPOSITORY_PATH
+def compare_model_to_db(url,model,repository,**opts):
+ """%prog compare_model_to_db URL MODEL REPOSITORY_PATH
Compare the current model (assumed to be a module level variable of type sqlalchemy.MetaData) against the current database.
NOTE: This is EXPERIMENTAL.
""" # TODO: get rid of EXPERIMENTAL label
engine=create_engine(url)
- print cls_schema.compare_db(engine,model,repository)
+ print cls_schema.compare_model_to_db(engine,model,repository)
-def db_schema_dump(url,repository,**opts):
- """%prog db_schema_dump URL REPOSITORY_PATH
+def create_model(url,repository,**opts):
+ """%prog create_model URL REPOSITORY_PATH
Dump the current database as a Python model to stdout.
NOTE: This is EXPERIMENTAL.
""" # TODO: get rid of EXPERIMENTAL label
engine=create_engine(url)
- print cls_schema.db_schema_dump(engine,repository)
+ print cls_schema.create_model(engine,repository)
-def script_python_changes(path,url,model,repository,**opts):
- """%prog script_python_changes PATH URL MODEL REPOSITORY_PATH
+def make_update_script_for_model(path,url,model,repository,**opts):
+ """%prog make_update_script_for_model PATH URL MODEL REPOSITORY_PATH
Create a script changing the current (old) database to the current (new) Python model.
@@ -309,8 +310,18 @@ def script_python_changes(path,url,model,repository,**opts):
""" # TODO: get rid of EXPERIMENTAL label
engine=create_engine(url)
try:
- cls_script_python.script_python_changes(path,engine,model,repository,**opts)
+ cls_script_python.make_update_script_for_model(path,engine,model,repository,**opts)
except exceptions.PathFoundError,e:
raise exceptions.KnownError("The path %s already exists"%e.args[0])
+def update_db_from_model(url,model,repository,**opts):
+ """%prog update_db_from_model URL MODEL REPOSITORY_PATH
+
+ Modify the database to match the structure of the current Python model.
+
+ NOTE: This is EXPERIMENTAL.
+ """ # TODO: get rid of EXPERIMENTAL label
+ engine=create_engine(url)
+ cls_schema.update_db_from_model(engine,model,repository)
+
diff --git a/migrate/versioning/genmodel.py b/migrate/versioning/genmodel.py
index 9274c23..bad96ff 100644
--- a/migrate/versioning/genmodel.py
+++ b/migrate/versioning/genmodel.py
@@ -3,7 +3,7 @@
# Some of this is borrowed heavily from the AutoCode project at: http://code.google.com/p/sqlautocode/
import sys
-import sqlalchemy
+import migrate, sqlalchemy
HEADER = """
@@ -28,11 +28,15 @@ class ModelGenerator(object):
kwarg.append('primary_key')
if not col.nullable: kwarg.append('nullable')
if col.onupdate: kwarg.append('onupdate')
- if col.default: kwarg.append('default')
+ if col.default:
+ if col.primary_key:
+ # I found that Postgres automatically creates a default value for the sequence, but let's not show that.
+ pass
+ else:
+ kwarg.append('default')
ks = ', '.join('%s=%r' % (k, getattr(col, k)) for k in kwarg )
name = col.name.encode('utf8') # crs: not sure if this is good idea, but it gets rid of extra u''
- #type = self.colTypeMappings[col.type.__class__]()
type = self.colTypeMappings.get(col.type.__class__, None)
if type:
# Make the column type be an instance of this type.
@@ -96,3 +100,60 @@ class ModelGenerator(object):
def toDowngradePython(self, indent=' '):
return ' pass #TODO DOWNGRADE'
+ def applyModel(self):
+ ''' Apply model to current database. '''
+
+ # Yuck! We have to import from changeset to apply the monkey-patch to allow column adding/dropping.
+ from migrate.changeset import schema
+
+ def dbCanHandleThisChange(missingInDatabase, missingInModel, diffDecl):
+ if missingInDatabase and not missingInModel and not diffDecl:
+ # Even sqlite can handle this.
+ return True
+ else:
+ return not self.diff.conn.url.drivername.startswith('sqlite')
+
+ meta = sqlalchemy.MetaData(self.diff.conn.engine)
+
+ for table in self.diff.tablesMissingInModel:
+ table = table.tometadata(meta)
+ table.drop()
+ for table in self.diff.tablesMissingInDatabase:
+ table = table.tometadata(meta)
+ table.create()
+ for modelTable in self.diff.tablesWithDiff:
+ modelTable = modelTable.tometadata(meta)
+ dbTable = self.diff.reflected_model.tables[modelTable.name]
+ #print 'TODO DEBUG.cols1', [x.name for x in dbTable.columns]
+ #dbTable = dbTable.tometadata(meta)
+ #print 'TODO DEBUG.cols2', [x.name for x in dbTable.columns]
+ tableName = modelTable.name
+ missingInDatabase, missingInModel, diffDecl = self.diff.colDiffs[tableName]
+ if dbCanHandleThisChange(missingInDatabase, missingInModel, diffDecl):
+ for col in missingInDatabase:
+ modelTable.columns[col.name].create()
+ for col in missingInModel:
+ dbTable.columns[col.name].drop()
+ for modelCol, databaseCol, modelDecl, databaseDecl in diffDecl:
+ dbTable.columns[databaseCol.name].drop()
+ modelTable.columns[modelCol.name].create()
+ else:
+ # Sqlite doesn't support drop column, so you have to do more:
+ # create temp table, copy data to it, drop old table, create new table, copy data back.
+
+ tempName = '_temp_%s' % modelTable.name # I wonder if this is guaranteed to be unique?
+ def getCopyStatement():
+ preparer = self.diff.conn.engine.dialect.preparer
+ commonCols = []
+ for modelCol in modelTable.columns:
+ if dbTable.columns.has_key(modelCol.name):
+ commonCols.append(modelCol.name)
+ commonColsStr = ', '.join(commonCols)
+ return 'INSERT INTO %s (%s) SELECT %s FROM %s' % (tableName, commonColsStr, commonColsStr, tempName)
+
+ self.diff.conn.execute('CREATE TEMPORARY TABLE %s as SELECT * from %s' % (tempName, modelTable.name))
+ modelTable.drop()
+ modelTable.create()
+ self.diff.conn.execute(getCopyStatement())
+ self.diff.conn.execute('DROP TABLE %s' % tempName)
+
diff --git a/migrate/versioning/schema.py b/migrate/versioning/schema.py
index 4cb0976..df94efa 100644
--- a/migrate/versioning/schema.py
+++ b/migrate/versioning/schema.py
@@ -93,7 +93,7 @@ class ControlledSchema(object):
return table
@classmethod
- def compare_db(cls,engine,model,repository):
+ def compare_model_to_db(cls,engine,model,repository):
"""Compare the current model against the current database."""
if isinstance(repository, basestring):
@@ -108,13 +108,28 @@ class ControlledSchema(object):
return diff
@classmethod
- def db_schema_dump(cls,engine,repository):
+ def create_model(cls,engine,repository):
"""Dump the current database as a Python model."""
if isinstance(repository, basestring):
repository=Repository(repository)
diff = schemadiff.getDiffOfModelAgainstDatabase(MetaData(), engine, excludeTables=[repository.version_table])
return genmodel.ModelGenerator(diff).toPython()
+
+ @classmethod
+ def update_db_from_model(cls,engine,model,repository):
+ """Modify the database to match the structure of the current Python model."""
+
+ if isinstance(repository, basestring):
+ repository=Repository(repository)
+ if isinstance(model, basestring): # TODO: centralize this code?
+ # Assume model is of form "mod1.mod2.varname".
+ varname = model.split('.')[-1]
+ modules = '.'.join(model.split('.')[:-1])
+ module = __import__(modules, globals(), {}, ['dummy-not-used'], -1)
+ model = getattr(module, varname)
+ diff = schemadiff.getDiffOfModelAgainstDatabase(model, engine, excludeTables=[repository.version_table])
+ return genmodel.ModelGenerator(diff).applyModel()
def drop(self):
"""Remove version control from a database"""
diff --git a/migrate/versioning/schemadiff.py b/migrate/versioning/schemadiff.py
index 6b37412..1005986 100644
--- a/migrate/versioning/schemadiff.py
+++ b/migrate/versioning/schemadiff.py
@@ -71,7 +71,7 @@ class SchemaDiff(object):
# Types and nullable are the same.
pass
else:
- self.storeColumnDiff(modelTable, modelDecl, databaseDecl)
+ self.storeColumnDiff(modelTable, modelCol, databaseCol, modelDecl, databaseDecl)
else:
self.storeColumnMissingInModel(modelTable, databaseCol)
else:
@@ -126,9 +126,9 @@ class SchemaDiff(object):
missingInDatabase, missingInModel, diffDecl = self.colDiffs.setdefault(table.name, ([], [], []))
missingInModel.append(col)
- def storeColumnDiff(self, table, modelDecl, databaseDecl):
+ def storeColumnDiff(self, table, modelCol, databaseCol, modelDecl, databaseDecl):
if table not in self.tablesWithDiff:
self.tablesWithDiff.append(table)
missingInDatabase, missingInModel, diffDecl = self.colDiffs.setdefault(table.name, ([], [], []))
- diffDecl.append( (modelDecl, databaseDecl) )
+ diffDecl.append( (modelCol, databaseCol, modelDecl, databaseDecl) )
diff --git a/migrate/versioning/script/py.py b/migrate/versioning/script/py.py
index f373131..6103827 100644
--- a/migrate/versioning/script/py.py
+++ b/migrate/versioning/script/py.py
@@ -20,7 +20,7 @@ class PythonScript(base.BaseScript):
shutil.copy(src,path)
@classmethod
- def script_python_changes(cls,path,engine,model,repository,**opts):
+ def make_update_script_for_model(cls,path,engine,model,repository,**opts):
"""Create a migration script"""
cls.require_notfound(path)