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