##// END OF EJS Templates
update migrations for 1.2
marcink -
r1442:7f31de15 beta
parent child Browse files
Show More
@@ -7,3 +7,5 b''
7 7
8 8 from rhodecode.lib.dbmigrate.migrate.versioning import *
9 9 from rhodecode.lib.dbmigrate.migrate.changeset import *
10
11 __version__ = '0.7.2.dev' No newline at end of file
@@ -15,6 +15,7 b" warnings.simplefilter('always', Deprecat"
15 15 _sa_version = tuple(int(re.match("\d+", x).group(0))
16 16 for x in _sa_version.split("."))
17 17 SQLA_06 = _sa_version >= (0, 6)
18 SQLA_07 = _sa_version >= (0, 7)
18 19
19 20 del re
20 21 del _sa_version
@@ -11,7 +11,7 b' from sqlalchemy.schema import ForeignKey'
11 11 from sqlalchemy.schema import UniqueConstraint
12 12
13 13 from rhodecode.lib.dbmigrate.migrate.exceptions import *
14 from rhodecode.lib.dbmigrate.migrate.changeset import SQLA_06
14 from rhodecode.lib.dbmigrate.migrate.changeset import SQLA_06, SQLA_07
15 15 from rhodecode.lib.dbmigrate.migrate.changeset.databases.visitor import (get_engine_visitor,
16 16 run_single_visitor)
17 17
@@ -555,6 +555,9 b' populated with defaults'
555 555
556 556 def add_to_table(self, table):
557 557 if table is not None and self.table is None:
558 if SQLA_07:
559 table.append_column(self)
560 else:
558 561 self._set_parent(table)
559 562
560 563 def _col_name_in_constraint(self,cons,name):
@@ -590,6 +593,9 b' populated with defaults'
590 593 table.constraints = table.constraints - to_drop
591 594
592 595 if table.c.contains_column(self):
596 if SQLA_07:
597 table._columns.remove(self)
598 else:
593 599 table.c.remove(self)
594 600
595 601 # TODO: this is fixed in 0.6
@@ -71,6 +71,11 b' class InvalidScriptError(ScriptError):'
71 71 """Invalid script error."""
72 72
73 73
74 class InvalidVersionError(Error):
75 """Invalid version error."""
76
77 # migrate.changeset
78
74 79 class NotSupportedError(Error):
75 80 """Not supported error"""
76 81
@@ -110,19 +110,19 b' def script(description, repository, **op'
110 110
111 111
112 112 @catch_known_errors
113 def script_sql(database, repository, **opts):
114 """%prog script_sql DATABASE REPOSITORY_PATH
113 def script_sql(database, description, repository, **opts):
114 """%prog script_sql DATABASE DESCRIPTION REPOSITORY_PATH
115 115
116 116 Create empty change SQL scripts for given DATABASE, where DATABASE
117 is either specific ('postgres', 'mysql', 'oracle', 'sqlite', etc.)
117 is either specific ('postgresql', 'mysql', 'oracle', 'sqlite', etc.)
118 118 or generic ('default').
119 119
120 For instance, manage.py script_sql postgres creates:
121 repository/versions/001_postgres_upgrade.sql and
122 repository/versions/001_postgres_postgres.sql
120 For instance, manage.py script_sql postgresql description creates:
121 repository/versions/001_description_postgresql_upgrade.sql and
122 repository/versions/001_description_postgresql_postgres.sql
123 123 """
124 124 repo = Repository(repository)
125 repo.create_script_sql(database, **opts)
125 repo.create_script_sql(database, description, **opts)
126 126
127 127
128 128 def version(repository, **opts):
@@ -14,6 +14,7 b' import sqlalchemy'
14 14 from rhodecode.lib.dbmigrate import migrate
15 15 from rhodecode.lib.dbmigrate.migrate import changeset
16 16
17
17 18 log = logging.getLogger(__name__)
18 19 HEADER = """
19 20 ## File autogenerated by genmodel.py
@@ -33,6 +34,13 b' Base = declarative.declarative_base()'
33 34
34 35
35 36 class ModelGenerator(object):
37 """Various transformations from an A, B diff.
38
39 In the implementation, A tends to be called the model and B
40 the database (although this is not true of all diffs).
41 The diff is directionless, but transformations apply the diff
42 in a particular direction, described in the method name.
43 """
36 44
37 45 def __init__(self, diff, engine, declarative=False):
38 46 self.diff = diff
@@ -58,7 +66,7 b' class ModelGenerator(object):'
58 66 pass
59 67 else:
60 68 kwarg.append('default')
61 ks = ', '.join('%s=%r' % (k, getattr(col, k)) for k in kwarg)
69 args = ['%s=%r' % (k, getattr(col, k)) for k in kwarg]
62 70
63 71 # crs: not sure if this is good idea, but it gets rid of extra
64 72 # u''
@@ -72,43 +80,38 b' class ModelGenerator(object):'
72 80 type_ = cls()
73 81 break
74 82
83 type_repr = repr(type_)
84 if type_repr.endswith('()'):
85 type_repr = type_repr[:-2]
86
87 constraints = [repr(cn) for cn in col.constraints]
88
75 89 data = {
76 90 'name': name,
77 'type': type_,
78 'constraints': ', '.join([repr(cn) for cn in col.constraints]),
79 'args': ks and ks or ''}
91 'commonStuff': ', '.join([type_repr] + constraints + args),
92 }
80 93
81 if data['constraints']:
82 if data['args']:
83 data['args'] = ',' + data['args']
84
85 if data['constraints'] or data['args']:
86 data['maybeComma'] = ','
94 if self.declarative:
95 return """%(name)s = Column(%(commonStuff)s)""" % data
87 96 else:
88 data['maybeComma'] = ''
97 return """Column(%(name)r, %(commonStuff)s)""" % data
89 98
90 commonStuff = """ %(maybeComma)s %(constraints)s %(args)s)""" % data
91 commonStuff = commonStuff.strip()
92 data['commonStuff'] = commonStuff
93 if self.declarative:
94 return """%(name)s = Column(%(type)r%(commonStuff)s""" % data
95 else:
96 return """Column(%(name)r, %(type)r%(commonStuff)s""" % data
97
98 def getTableDefn(self, table):
99 def _getTableDefn(self, table, metaName='meta'):
99 100 out = []
100 101 tableName = table.name
101 102 if self.declarative:
102 103 out.append("class %(table)s(Base):" % {'table': tableName})
103 out.append(" __tablename__ = '%(table)s'" % {'table': tableName})
104 out.append(" __tablename__ = '%(table)s'\n" %
105 {'table': tableName})
104 106 for col in table.columns:
105 107 out.append(" %s" % self.column_repr(col))
108 out.append('\n')
106 109 else:
107 out.append("%(table)s = Table('%(table)s', meta," % \
108 {'table': tableName})
110 out.append("%(table)s = Table('%(table)s', %(meta)s," %
111 {'table': tableName, 'meta': metaName})
109 112 for col in table.columns:
110 113 out.append(" %s," % self.column_repr(col))
111 out.append(")")
114 out.append(")\n")
112 115 return out
113 116
114 117 def _get_tables(self,missingA=False,missingB=False,modified=False):
@@ -122,8 +125,14 b' class ModelGenerator(object):'
122 125 for name in names:
123 126 yield metadata.tables.get(name)
124 127
125 def toPython(self):
126 """Assume database is current and model is empty."""
128 def genBDefinition(self):
129 """Generates the source code for a definition of B.
130
131 Assumes a diff where A is empty.
132
133 Was: toPython. Assume database (B) is current and model (A) is empty.
134 """
135
127 136 out = []
128 137 if self.declarative:
129 138 out.append(DECLARATIVE_HEADER)
@@ -131,67 +140,89 b' class ModelGenerator(object):'
131 140 out.append(HEADER)
132 141 out.append("")
133 142 for table in self._get_tables(missingA=True):
134 out.extend(self.getTableDefn(table))
135 out.append("")
143 out.extend(self._getTableDefn(table))
136 144 return '\n'.join(out)
137 145
138 def toUpgradeDowngradePython(self, indent=' '):
139 ''' Assume model is most current and database is out-of-date. '''
140 decls = ['from rhodecode.lib.dbmigrate.migrate.changeset import schema',
141 'meta = MetaData()']
142 for table in self._get_tables(
143 missingA=True,missingB=True,modified=True
144 ):
145 decls.extend(self.getTableDefn(table))
146 def genB2AMigration(self, indent=' '):
147 '''Generate a migration from B to A.
148
149 Was: toUpgradeDowngradePython
150 Assume model (A) is most current and database (B) is out-of-date.
151 '''
152
153 decls = ['from migrate.changeset import schema',
154 'pre_meta = MetaData()',
155 'post_meta = MetaData()',
156 ]
157 upgradeCommands = ['pre_meta.bind = migrate_engine',
158 'post_meta.bind = migrate_engine']
159 downgradeCommands = list(upgradeCommands)
160
161 for tn in self.diff.tables_missing_from_A:
162 pre_table = self.diff.metadataB.tables[tn]
163 decls.extend(self._getTableDefn(pre_table, metaName='pre_meta'))
164 upgradeCommands.append(
165 "pre_meta.tables[%(table)r].drop()" % {'table': tn})
166 downgradeCommands.append(
167 "pre_meta.tables[%(table)r].create()" % {'table': tn})
146 168
147 upgradeCommands, downgradeCommands = [], []
148 for tableName in self.diff.tables_missing_from_A:
149 upgradeCommands.append("%(table)s.drop()" % {'table': tableName})
150 downgradeCommands.append("%(table)s.create()" % \
151 {'table': tableName})
152 for tableName in self.diff.tables_missing_from_B:
153 upgradeCommands.append("%(table)s.create()" % {'table': tableName})
154 downgradeCommands.append("%(table)s.drop()" % {'table': tableName})
169 for tn in self.diff.tables_missing_from_B:
170 post_table = self.diff.metadataA.tables[tn]
171 decls.extend(self._getTableDefn(post_table, metaName='post_meta'))
172 upgradeCommands.append(
173 "post_meta.tables[%(table)r].create()" % {'table': tn})
174 downgradeCommands.append(
175 "post_meta.tables[%(table)r].drop()" % {'table': tn})
155 176
156 for tableName in self.diff.tables_different:
157 dbTable = self.diff.metadataB.tables[tableName]
158 missingInDatabase, missingInModel, diffDecl = \
159 self.diff.colDiffs[tableName]
160 for col in missingInDatabase:
161 upgradeCommands.append('%s.columns[%r].create()' % (
162 modelTable, col.name))
163 downgradeCommands.append('%s.columns[%r].drop()' % (
164 modelTable, col.name))
165 for col in missingInModel:
166 upgradeCommands.append('%s.columns[%r].drop()' % (
167 modelTable, col.name))
168 downgradeCommands.append('%s.columns[%r].create()' % (
169 modelTable, col.name))
170 for modelCol, databaseCol, modelDecl, databaseDecl in diffDecl:
177 for (tn, td) in self.diff.tables_different.iteritems():
178 if td.columns_missing_from_A or td.columns_different:
179 pre_table = self.diff.metadataB.tables[tn]
180 decls.extend(self._getTableDefn(
181 pre_table, metaName='pre_meta'))
182 if td.columns_missing_from_B or td.columns_different:
183 post_table = self.diff.metadataA.tables[tn]
184 decls.extend(self._getTableDefn(
185 post_table, metaName='post_meta'))
186
187 for col in td.columns_missing_from_A:
188 upgradeCommands.append(
189 'pre_meta.tables[%r].columns[%r].drop()' % (tn, col))
190 downgradeCommands.append(
191 'pre_meta.tables[%r].columns[%r].create()' % (tn, col))
192 for col in td.columns_missing_from_B:
193 upgradeCommands.append(
194 'post_meta.tables[%r].columns[%r].create()' % (tn, col))
195 downgradeCommands.append(
196 'post_meta.tables[%r].columns[%r].drop()' % (tn, col))
197 for modelCol, databaseCol, modelDecl, databaseDecl in td.columns_different:
171 198 upgradeCommands.append(
172 199 'assert False, "Can\'t alter columns: %s:%s=>%s"' % (
173 modelTable, modelCol.name, databaseCol.name))
200 tn, modelCol.name, databaseCol.name))
174 201 downgradeCommands.append(
175 202 'assert False, "Can\'t alter columns: %s:%s=>%s"' % (
176 modelTable, modelCol.name, databaseCol.name))
177 pre_command = ' meta.bind = migrate_engine'
203 tn, modelCol.name, databaseCol.name))
178 204
179 205 return (
180 206 '\n'.join(decls),
181 '\n'.join([pre_command] + ['%s%s' % (indent, line) for line in upgradeCommands]),
182 '\n'.join([pre_command] + ['%s%s' % (indent, line) for line in downgradeCommands]))
207 '\n'.join('%s%s' % (indent, line) for line in upgradeCommands),
208 '\n'.join('%s%s' % (indent, line) for line in downgradeCommands))
183 209
184 210 def _db_can_handle_this_change(self,td):
211 """Check if the database can handle going from B to A."""
212
185 213 if (td.columns_missing_from_B
186 214 and not td.columns_missing_from_A
187 215 and not td.columns_different):
188 # Even sqlite can handle this.
216 # Even sqlite can handle column additions.
189 217 return True
190 218 else:
191 219 return not self.engine.url.drivername.startswith('sqlite')
192 220
193 def applyModel(self):
194 """Apply model to current database."""
221 def runB2A(self):
222 """Goes from B to A.
223
224 Was: applyModel. Apply model (A) to current database (B).
225 """
195 226
196 227 meta = sqlalchemy.MetaData(self.engine)
197 228
@@ -251,3 +282,4 b' class ModelGenerator(object):'
251 282 except:
252 283 trans.rollback()
253 284 raise
285
@@ -115,6 +115,7 b' class Repository(pathed.Pathed):'
115 115 options.setdefault('version_table', 'migrate_version')
116 116 options.setdefault('repository_id', name)
117 117 options.setdefault('required_dbs', [])
118 options.setdefault('use_timestamp_numbering', '0')
118 119
119 120 tmpl = open(os.path.join(tmpl_dir, cls._config)).read()
120 121 ret = TempitaTemplate(tmpl).substitute(options)
@@ -152,11 +153,14 b' class Repository(pathed.Pathed):'
152 153
153 154 def create_script(self, description, **k):
154 155 """API to :meth:`migrate.versioning.version.Collection.create_new_python_version`"""
156
157 k['use_timestamp_numbering'] = self.use_timestamp_numbering
155 158 self.versions.create_new_python_version(description, **k)
156 159
157 def create_script_sql(self, database, **k):
160 def create_script_sql(self, database, description, **k):
158 161 """API to :meth:`migrate.versioning.version.Collection.create_new_sql_version`"""
159 self.versions.create_new_sql_version(database, **k)
162 k['use_timestamp_numbering'] = self.use_timestamp_numbering
163 self.versions.create_new_sql_version(database, description, **k)
160 164
161 165 @property
162 166 def latest(self):
@@ -173,6 +177,13 b' class Repository(pathed.Pathed):'
173 177 """Returns repository id specified in config"""
174 178 return self.config.get('db_settings', 'repository_id')
175 179
180 @property
181 def use_timestamp_numbering(self):
182 """Returns use_timestamp_numbering specified in config"""
183 ts_numbering = self.config.get('db_settings', 'use_timestamp_numbering', raw=True)
184
185 return ts_numbering
186
176 187 def version(self, *p, **k):
177 188 """API to :attr:`migrate.versioning.version.Collection.version`"""
178 189 return self.versions.version(*p, **k)
@@ -11,6 +11,7 b' from sqlalchemy import exceptions as sa_'
11 11 from sqlalchemy.sql import bindparam
12 12
13 13 from rhodecode.lib.dbmigrate.migrate import exceptions
14 from rhodecode.lib.dbmigrate.migrate.changeset import SQLA_07
14 15 from rhodecode.lib.dbmigrate.migrate.versioning import genmodel, schemadiff
15 16 from rhodecode.lib.dbmigrate.migrate.versioning.repository import Repository
16 17 from rhodecode.lib.dbmigrate.migrate.versioning.util import load_model
@@ -57,6 +58,12 b' class ControlledSchema(object):'
57 58 """
58 59 Remove version control from a database.
59 60 """
61 if SQLA_07:
62 try:
63 self.table.drop()
64 except sa_exceptions.DatabaseError:
65 raise exceptions.DatabaseNotControlledError(str(self.table))
66 else:
60 67 try:
61 68 self.table.drop()
62 69 except (sa_exceptions.SQLError):
@@ -110,7 +117,7 b' class ControlledSchema(object):'
110 117 diff = schemadiff.getDiffOfModelAgainstDatabase(
111 118 model, self.engine, excludeTables=[self.repository.version_table]
112 119 )
113 genmodel.ModelGenerator(diff,self.engine).applyModel()
120 genmodel.ModelGenerator(diff,self.engine).runB2A()
114 121
115 122 self.update_repository_table(self.version, int(self.repository.latest))
116 123
@@ -210,4 +217,4 b' class ControlledSchema(object):'
210 217 diff = schemadiff.getDiffOfModelAgainstDatabase(
211 218 MetaData(), engine, excludeTables=[repository.version_table]
212 219 )
213 return genmodel.ModelGenerator(diff, engine, declarative).toPython()
220 return genmodel.ModelGenerator(diff, engine, declarative).genBDefinition()
@@ -61,12 +61,12 b' class PythonScript(base.BaseScript):'
61 61
62 62 # Compute differences.
63 63 diff = schemadiff.getDiffOfModelAgainstModel(
64 model,
64 65 oldmodel,
65 model,
66 66 excludeTables=[repository.version_table])
67 67 # TODO: diff can be False (there is no difference?)
68 68 decls, upgradeCommands, downgradeCommands = \
69 genmodel.ModelGenerator(diff,engine).toUpgradeDowngradePython()
69 genmodel.ModelGenerator(diff,engine).genB2AMigration()
70 70
71 71 # Store differences into file.
72 72 src = Template(opts.pop('templates_path', None)).get_script(opts.pop('templates_theme', None))
@@ -18,3 +18,8 b" version_table={{ locals().pop('version_t"
18 18 # be using to ensure your updates to that database work properly.
19 19 # This must be a list; example: ['postgres','sqlite']
20 20 required_dbs={{ locals().pop('required_dbs') }}
21
22 # When creating new change scripts, Migrate will stamp the new script with
23 # a version number. By default this is latest_version + 1. You can set this
24 # to 'true' to tell Migrate to use the UTC timestamp instead.
25 use_timestamp_numbering='false' No newline at end of file
@@ -8,6 +8,7 b' import logging'
8 8
9 9 from rhodecode.lib.dbmigrate.migrate import exceptions
10 10 from rhodecode.lib.dbmigrate.migrate.versioning import pathed, script
11 from datetime import datetime
11 12
12 13
13 14 log = logging.getLogger(__name__)
@@ -88,9 +89,17 b' class Collection(pathed.Pathed):'
88 89 """:returns: Latest version in Collection"""
89 90 return max([VerNum(0)] + self.versions.keys())
90 91
92 def _next_ver_num(self, use_timestamp_numbering):
93 print use_timestamp_numbering
94 if use_timestamp_numbering == True:
95 print "Creating new timestamp version!"
96 return VerNum(int(datetime.utcnow().strftime('%Y%m%d%H%M%S')))
97 else:
98 return self.latest + 1
99
91 100 def create_new_python_version(self, description, **k):
92 101 """Create Python files for new version"""
93 ver = self.latest + 1
102 ver = self._next_ver_num(k.pop('use_timestamp_numbering', False))
94 103 extra = str_to_filename(description)
95 104
96 105 if extra:
@@ -105,14 +114,22 b' class Collection(pathed.Pathed):'
105 114 script.PythonScript.create(filepath, **k)
106 115 self.versions[ver] = Version(ver, self.path, [filename])
107 116
108 def create_new_sql_version(self, database, **k):
117 def create_new_sql_version(self, database, description, **k):
109 118 """Create SQL files for new version"""
110 ver = self.latest + 1
119 ver = self._next_ver_num(k.pop('use_timestamp_numbering', False))
111 120 self.versions[ver] = Version(ver, self.path, [])
112 121
122 extra = str_to_filename(description)
123
124 if extra:
125 if extra == '_':
126 extra = ''
127 elif not extra.startswith('_'):
128 extra = '_%s' % extra
129
113 130 # Create new files.
114 131 for op in ('upgrade', 'downgrade'):
115 filename = '%03d_%s_%s.sql' % (ver, database, op)
132 filename = '%03d%s_%s_%s.sql' % (ver, extra, database, op)
116 133 filepath = self._version_path(filename)
117 134 script.SqlScript.create(filepath, **k)
118 135 self.versions[ver].add_script(filepath)
@@ -176,18 +193,26 b' class Version(object):'
176 193 elif path.endswith(Extensions.sql):
177 194 self._add_script_sql(path)
178 195
179 SQL_FILENAME = re.compile(r'^(\d+)_([^_]+)_([^_]+).sql')
196 SQL_FILENAME = re.compile(r'^.*\.sql')
180 197
181 198 def _add_script_sql(self, path):
182 199 basename = os.path.basename(path)
183 200 match = self.SQL_FILENAME.match(basename)
184 201
185 202 if match:
186 version, dbms, op = match.group(1), match.group(2), match.group(3)
203 basename = basename.replace('.sql', '')
204 parts = basename.split('_')
205 if len(parts) < 3:
206 raise exceptions.ScriptError(
207 "Invalid SQL script name %s " % basename + \
208 "(needs to be ###_description_database_operation.sql)")
209 version = parts[0]
210 op = parts[-1]
211 dbms = parts[-2]
187 212 else:
188 213 raise exceptions.ScriptError(
189 214 "Invalid SQL script name %s " % basename + \
190 "(needs to be ###_database_operation.sql)")
215 "(needs to be ###_description_database_operation.sql)")
191 216
192 217 # File the script into a dictionary
193 218 self.sql.setdefault(dbms, {})[op] = script.SqlScript(path)
@@ -80,6 +80,11 b' def upgrade(migrate_engine):'
80 80 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
81 81 enable_downloads.create(Repository().__table__)
82 82
83 #ADD column created_on
84 created_on = Column('created_on', DateTime(timezone=False), nullable=True,
85 unique=None, default=datetime.datetime.now)
86 created_on.create(Repository().__table__)
87
83 88 #ADD group_id column#
84 89 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'),
85 90 nullable=True, unique=False, default=None)
@@ -94,6 +99,15 b' def upgrade(migrate_engine):'
94 99 nullable=True, unique=False, default=None)
95 100
96 101 clone_uri.create(Repository().__table__)
102
103
104 #==========================================================================
105 # Upgrade of `user_followings` table
106 #==========================================================================
107
108 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
109 follows_from.create(Repository().__table__)
110
97 111 return
98 112
99 113
General Comments 0
You need to be logged in to leave comments. Login now