##// END OF EJS Templates
db-migrate: backport some changes for py3 compat.
marcink -
r4343:2ba995a1 default
parent child Browse files
Show More
@@ -1,30 +1,31 b''
1 """
1 """
2 This module extends SQLAlchemy and provides additional DDL [#]_
2 This module extends SQLAlchemy and provides additional DDL [#]_
3 support.
3 support.
4
4
5 .. [#] SQL Data Definition Language
5 .. [#] SQL Data Definition Language
6 """
6 """
7 import re
7 import re
8 import warnings
8 import warnings
9
9
10 import sqlalchemy
10 import sqlalchemy
11 from sqlalchemy import __version__ as _sa_version
11 from sqlalchemy import __version__ as _sa_version
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)) for x in _sa_version.split("."))
16 for x in _sa_version.split("."))
17 SQLA_07 = _sa_version >= (0, 7)
16 SQLA_07 = _sa_version >= (0, 7)
18 SQLA_08 = _sa_version >= (0, 8)
17 SQLA_08 = _sa_version >= (0, 8)
18 SQLA_09 = _sa_version >= (0, 9)
19 SQLA_10 = _sa_version >= (1, 0)
19
20
20 del re
21 del re
21 del _sa_version
22 del _sa_version
22
23
23 from rhodecode.lib.dbmigrate.migrate.changeset.schema import *
24 from rhodecode.lib.dbmigrate.migrate.changeset.schema import *
24 from rhodecode.lib.dbmigrate.migrate.changeset.constraint import *
25 from rhodecode.lib.dbmigrate.migrate.changeset.constraint import *
25
26
26 sqlalchemy.schema.Table.__bases__ += (ChangesetTable,)
27 sqlalchemy.schema.Table.__bases__ += (ChangesetTable, )
27 sqlalchemy.schema.Column.__bases__ += (ChangesetColumn,)
28 sqlalchemy.schema.Column.__bases__ += (ChangesetColumn, )
28 sqlalchemy.schema.Index.__bases__ += (ChangesetIndex,)
29 sqlalchemy.schema.Index.__bases__ += (ChangesetIndex, )
29
30
30 sqlalchemy.schema.DefaultClause.__bases__ += (ChangesetDefaultClause,)
31 sqlalchemy.schema.DefaultClause.__bases__ += (ChangesetDefaultClause, )
@@ -1,315 +1,314 b''
1 """
1 """
2 Extensions to SQLAlchemy for altering existing tables.
2 Extensions to SQLAlchemy for altering existing tables.
3
3
4 At the moment, this isn't so much based off of ANSI as much as
4 At the moment, this isn't so much based off of ANSI as much as
5 things that just happen to work with multiple databases.
5 things that just happen to work with multiple databases.
6 """
6 """
7 import StringIO
7 import StringIO
8
8
9 import sqlalchemy as sa
9 import sqlalchemy as sa
10 from sqlalchemy.schema import SchemaVisitor
10 from sqlalchemy.schema import SchemaVisitor
11 from sqlalchemy.engine.default import DefaultDialect
11 from sqlalchemy.engine.default import DefaultDialect
12 from sqlalchemy.sql import ClauseElement
12 from sqlalchemy.sql import ClauseElement
13 from sqlalchemy.schema import (ForeignKeyConstraint,
13 from sqlalchemy.schema import (ForeignKeyConstraint,
14 PrimaryKeyConstraint,
14 PrimaryKeyConstraint,
15 CheckConstraint,
15 CheckConstraint,
16 UniqueConstraint,
16 UniqueConstraint,
17 Index)
17 Index)
18
18
19 import sqlalchemy.sql.compiler
19 import sqlalchemy.sql.compiler
20 from rhodecode.lib.dbmigrate.migrate import exceptions
20 from rhodecode.lib.dbmigrate.migrate import exceptions
21 from rhodecode.lib.dbmigrate.migrate.changeset import constraint
21 from rhodecode.lib.dbmigrate.migrate.changeset import constraint
22 from rhodecode.lib.dbmigrate.migrate.changeset import util
22 from rhodecode.lib.dbmigrate.migrate.changeset import util
23
23
24 from sqlalchemy.schema import AddConstraint, DropConstraint
24 from sqlalchemy.schema import AddConstraint, DropConstraint
25 from sqlalchemy.sql.compiler import DDLCompiler
25 from sqlalchemy.sql.compiler import DDLCompiler
26 SchemaGenerator = SchemaDropper = DDLCompiler
26 SchemaGenerator = SchemaDropper = DDLCompiler
27
27
28
28
29 class AlterTableVisitor(SchemaVisitor):
29 class AlterTableVisitor(SchemaVisitor):
30 """Common operations for ``ALTER TABLE`` statements."""
30 """Common operations for ``ALTER TABLE`` statements."""
31
31
32 # engine.Compiler looks for .statement
32 # engine.Compiler looks for .statement
33 # when it spawns off a new compiler
33 # when it spawns off a new compiler
34 statement = ClauseElement()
34 statement = ClauseElement()
35
35
36 def append(self, s):
36 def append(self, s):
37 """Append content to the SchemaIterator's query buffer."""
37 """Append content to the SchemaIterator's query buffer."""
38
38
39 self.buffer.write(s)
39 self.buffer.write(s)
40
40
41 def execute(self):
41 def execute(self):
42 """Execute the contents of the SchemaIterator's buffer."""
42 """Execute the contents of the SchemaIterator's buffer."""
43 try:
43 try:
44 return self.connection.execute(self.buffer.getvalue())
44 return self.connection.execute(self.buffer.getvalue())
45 finally:
45 finally:
46 self.buffer.seek(0)
46 self.buffer.seek(0)
47 self.buffer.truncate()
47 self.buffer.truncate()
48
48
49 def __init__(self, dialect, connection, **kw):
49 def __init__(self, dialect, connection, **kw):
50 self.connection = connection
50 self.connection = connection
51 self.buffer = StringIO.StringIO()
51 self.buffer = StringIO.StringIO()
52 self.preparer = dialect.identifier_preparer
52 self.preparer = dialect.identifier_preparer
53 self.dialect = dialect
53 self.dialect = dialect
54
54
55 def traverse_single(self, elem):
55 def traverse_single(self, elem):
56 ret = super(AlterTableVisitor, self).traverse_single(elem)
56 ret = super(AlterTableVisitor, self).traverse_single(elem)
57 if ret:
57 if ret:
58 # adapt to 0.6 which uses a string-returning
58 # adapt to 0.6 which uses a string-returning
59 # object
59 # object
60 self.append(" %s" % ret)
60 self.append(" %s" % ret)
61
61
62 def _to_table(self, param):
62 def _to_table(self, param):
63 """Returns the table object for the given param object."""
63 """Returns the table object for the given param object."""
64 if isinstance(param, (sa.Column, sa.Index, sa.schema.Constraint)):
64 if isinstance(param, (sa.Column, sa.Index, sa.schema.Constraint)):
65 ret = param.table
65 ret = param.table
66 else:
66 else:
67 ret = param
67 ret = param
68 return ret
68 return ret
69
69
70 def start_alter_table(self, param):
70 def start_alter_table(self, param):
71 """Returns the start of an ``ALTER TABLE`` SQL-Statement.
71 """Returns the start of an ``ALTER TABLE`` SQL-Statement.
72
72
73 Use the param object to determine the table name and use it
73 Use the param object to determine the table name and use it
74 for building the SQL statement.
74 for building the SQL statement.
75
75
76 :param param: object to determine the table from
76 :param param: object to determine the table from
77 :type param: :class:`sqlalchemy.Column`, :class:`sqlalchemy.Index`,
77 :type param: :class:`sqlalchemy.Column`, :class:`sqlalchemy.Index`,
78 :class:`sqlalchemy.schema.Constraint`, :class:`sqlalchemy.Table`,
78 :class:`sqlalchemy.schema.Constraint`, :class:`sqlalchemy.Table`,
79 or string (table name)
79 or string (table name)
80 """
80 """
81 table = self._to_table(param)
81 table = self._to_table(param)
82 self.append('\nALTER TABLE %s ' % self.preparer.format_table(table))
82 self.append('\nALTER TABLE %s ' % self.preparer.format_table(table))
83 return table
83 return table
84
84
85
85
86 class ANSIColumnGenerator(AlterTableVisitor, SchemaGenerator):
86 class ANSIColumnGenerator(AlterTableVisitor, SchemaGenerator):
87 """Extends ansisql generator for column creation (alter table add col)"""
87 """Extends ansisql generator for column creation (alter table add col)"""
88
88
89 def visit_column(self, column):
89 def visit_column(self, column):
90 """Create a column (table already exists).
90 """Create a column (table already exists).
91
91
92 :param column: column object
92 :param column: column object
93 :type column: :class:`sqlalchemy.Column` instance
93 :type column: :class:`sqlalchemy.Column` instance
94 """
94 """
95 if column.default is not None:
95 if column.default is not None:
96 self.traverse_single(column.default)
96 self.traverse_single(column.default)
97
97
98 table = self.start_alter_table(column)
98 table = self.start_alter_table(column)
99 self.append("ADD ")
99 self.append("ADD ")
100
101 self.append(self.get_column_specification(column))
100 self.append(self.get_column_specification(column))
102
101
103 for cons in column.constraints:
102 for cons in column.constraints:
104 self.traverse_single(cons)
103 self.traverse_single(cons)
105 self.execute()
104 self.execute()
106
105
107 # ALTER TABLE STATEMENTS
106 # ALTER TABLE STATEMENTS
108
107
109 # add indexes and unique constraints
108 # add indexes and unique constraints
110 if column.index_name:
109 if column.index_name:
111 Index(column.index_name,column).create()
110 Index(column.index_name,column).create()
112 elif column.unique_name:
111 elif column.unique_name:
113 constraint.UniqueConstraint(column,
112 constraint.UniqueConstraint(column,
114 name=column.unique_name).create()
113 name=column.unique_name).create()
115
114
116 # SA bounds FK constraints to table, add manually
115 # SA bounds FK constraints to table, add manually
117 for fk in column.foreign_keys:
116 for fk in column.foreign_keys:
118 self.add_foreignkey(fk.constraint)
117 self.add_foreignkey(fk.constraint)
119
118
120 # add primary key constraint if needed
119 # add primary key constraint if needed
121 if column.primary_key_name:
120 if column.primary_key_name:
122 cons = constraint.PrimaryKeyConstraint(column,
121 cons = constraint.PrimaryKeyConstraint(column,
123 name=column.primary_key_name)
122 name=column.primary_key_name)
124 cons.create()
123 cons.create()
125
124
126 def add_foreignkey(self, fk):
125 def add_foreignkey(self, fk):
127 self.connection.execute(AddConstraint(fk))
126 self.connection.execute(AddConstraint(fk))
128
127
129 class ANSIColumnDropper(AlterTableVisitor, SchemaDropper):
128 class ANSIColumnDropper(AlterTableVisitor, SchemaDropper):
130 """Extends ANSI SQL dropper for column dropping (``ALTER TABLE
129 """Extends ANSI SQL dropper for column dropping (``ALTER TABLE
131 DROP COLUMN``).
130 DROP COLUMN``).
132 """
131 """
133
132
134 def visit_column(self, column):
133 def visit_column(self, column):
135 """Drop a column from its table.
134 """Drop a column from its table.
136
135
137 :param column: the column object
136 :param column: the column object
138 :type column: :class:`sqlalchemy.Column`
137 :type column: :class:`sqlalchemy.Column`
139 """
138 """
140 table = self.start_alter_table(column)
139 table = self.start_alter_table(column)
141 self.append('DROP COLUMN %s' % self.preparer.format_column(column))
140 self.append('DROP COLUMN %s' % self.preparer.format_column(column))
142 self.execute()
141 self.execute()
143
142
144
143
145 class ANSISchemaChanger(AlterTableVisitor, SchemaGenerator):
144 class ANSISchemaChanger(AlterTableVisitor, SchemaGenerator):
146 """Manages changes to existing schema elements.
145 """Manages changes to existing schema elements.
147
146
148 Note that columns are schema elements; ``ALTER TABLE ADD COLUMN``
147 Note that columns are schema elements; ``ALTER TABLE ADD COLUMN``
149 is in SchemaGenerator.
148 is in SchemaGenerator.
150
149
151 All items may be renamed. Columns can also have many of their properties -
150 All items may be renamed. Columns can also have many of their properties -
152 type, for example - changed.
151 type, for example - changed.
153
152
154 Each function is passed a tuple, containing (object, name); where
153 Each function is passed a tuple, containing (object, name); where
155 object is a type of object you'd expect for that function
154 object is a type of object you'd expect for that function
156 (ie. table for visit_table) and name is the object's new
155 (ie. table for visit_table) and name is the object's new
157 name. NONE means the name is unchanged.
156 name. NONE means the name is unchanged.
158 """
157 """
159
158
160 def visit_table(self, table):
159 def visit_table(self, table):
161 """Rename a table. Other ops aren't supported."""
160 """Rename a table. Other ops aren't supported."""
162 self.start_alter_table(table)
161 self.start_alter_table(table)
163 q = util.safe_quote(table)
162 q = util.safe_quote(table)
164 self.append("RENAME TO %s" % self.preparer.quote(table.new_name, q))
163 self.append("RENAME TO %s" % self.preparer.quote(table.new_name, q))
165 self.execute()
164 self.execute()
166
165
167 def visit_index(self, index):
166 def visit_index(self, index):
168 """Rename an index"""
167 """Rename an index"""
169 if hasattr(self, '_validate_identifier'):
168 if hasattr(self, '_validate_identifier'):
170 # SA <= 0.6.3
169 # SA <= 0.6.3
171 self.append("ALTER INDEX %s RENAME TO %s" % (
170 self.append("ALTER INDEX %s RENAME TO %s" % (
172 self.preparer.quote(
171 self.preparer.quote(
173 self._validate_identifier(
172 self._validate_identifier(
174 index.name, True), index.quote),
173 index.name, True), index.quote),
175 self.preparer.quote(
174 self.preparer.quote(
176 self._validate_identifier(
175 self._validate_identifier(
177 index.new_name, True), index.quote)))
176 index.new_name, True), index.quote)))
178 elif hasattr(self, '_index_identifier'):
177 elif hasattr(self, '_index_identifier'):
179 # SA >= 0.6.5, < 0.8
178 # SA >= 0.6.5, < 0.8
180 self.append("ALTER INDEX %s RENAME TO %s" % (
179 self.append("ALTER INDEX %s RENAME TO %s" % (
181 self.preparer.quote(
180 self.preparer.quote(
182 self._index_identifier(
181 self._index_identifier(
183 index.name), index.quote),
182 index.name), index.quote),
184 self.preparer.quote(
183 self.preparer.quote(
185 self._index_identifier(
184 self._index_identifier(
186 index.new_name), index.quote)))
185 index.new_name), index.quote)))
187 else:
186 else:
188 # SA >= 0.8
187 # SA >= 0.8
189 class NewName(object):
188 class NewName(object):
190 """Map obj.name -> obj.new_name"""
189 """Map obj.name -> obj.new_name"""
191 def __init__(self, index):
190 def __init__(self, index):
192 self.name = index.new_name
191 self.name = index.new_name
193 self._obj = index
192 self._obj = index
194
193
195 def __getattr__(self, attr):
194 def __getattr__(self, attr):
196 if attr == 'name':
195 if attr == 'name':
197 return getattr(self, attr)
196 return getattr(self, attr)
198 return getattr(self._obj, attr)
197 return getattr(self._obj, attr)
199
198
200 self.append("ALTER INDEX %s RENAME TO %s" % (
199 self.append("ALTER INDEX %s RENAME TO %s" % (
201 self._prepared_index_name(index),
200 self._prepared_index_name(index),
202 self._prepared_index_name(NewName(index))))
201 self._prepared_index_name(NewName(index))))
203
202
204 self.execute()
203 self.execute()
205
204
206 def visit_column(self, delta):
205 def visit_column(self, delta):
207 """Rename/change a column."""
206 """Rename/change a column."""
208 # ALTER COLUMN is implemented as several ALTER statements
207 # ALTER COLUMN is implemented as several ALTER statements
209 keys = delta.keys()
208 keys = delta.keys()
210 if 'type' in keys:
209 if 'type' in keys:
211 self._run_subvisit(delta, self._visit_column_type)
210 self._run_subvisit(delta, self._visit_column_type)
212 if 'nullable' in keys:
211 if 'nullable' in keys:
213 self._run_subvisit(delta, self._visit_column_nullable)
212 self._run_subvisit(delta, self._visit_column_nullable)
214 if 'server_default' in keys:
213 if 'server_default' in keys:
215 # Skip 'default': only handle server-side defaults, others
214 # Skip 'default': only handle server-side defaults, others
216 # are managed by the app, not the db.
215 # are managed by the app, not the db.
217 self._run_subvisit(delta, self._visit_column_default)
216 self._run_subvisit(delta, self._visit_column_default)
218 if 'name' in keys:
217 if 'name' in keys:
219 self._run_subvisit(delta, self._visit_column_name, start_alter=False)
218 self._run_subvisit(delta, self._visit_column_name, start_alter=False)
220
219
221 def _run_subvisit(self, delta, func, start_alter=True):
220 def _run_subvisit(self, delta, func, start_alter=True):
222 """Runs visit method based on what needs to be changed on column"""
221 """Runs visit method based on what needs to be changed on column"""
223 table = self._to_table(delta.table)
222 table = self._to_table(delta.table)
224 col_name = delta.current_name
223 col_name = delta.current_name
225 if start_alter:
224 if start_alter:
226 self.start_alter_column(table, col_name)
225 self.start_alter_column(table, col_name)
227 ret = func(table, delta.result_column, delta)
226 ret = func(table, delta.result_column, delta)
228 self.execute()
227 self.execute()
229
228
230 def start_alter_column(self, table, col_name):
229 def start_alter_column(self, table, col_name):
231 """Starts ALTER COLUMN"""
230 """Starts ALTER COLUMN"""
232 self.start_alter_table(table)
231 self.start_alter_table(table)
233 q = util.safe_quote(table)
232 q = util.safe_quote(table)
234 self.append("ALTER COLUMN %s " % self.preparer.quote(col_name, q))
233 self.append("ALTER COLUMN %s " % self.preparer.quote(col_name, q))
235
234
236 def _visit_column_nullable(self, table, column, delta):
235 def _visit_column_nullable(self, table, column, delta):
237 nullable = delta['nullable']
236 nullable = delta['nullable']
238 if nullable:
237 if nullable:
239 self.append("DROP NOT NULL")
238 self.append("DROP NOT NULL")
240 else:
239 else:
241 self.append("SET NOT NULL")
240 self.append("SET NOT NULL")
242
241
243 def _visit_column_default(self, table, column, delta):
242 def _visit_column_default(self, table, column, delta):
244 default_text = self.get_column_default_string(column)
243 default_text = self.get_column_default_string(column)
245 if default_text is not None:
244 if default_text is not None:
246 self.append("SET DEFAULT %s" % default_text)
245 self.append("SET DEFAULT %s" % default_text)
247 else:
246 else:
248 self.append("DROP DEFAULT")
247 self.append("DROP DEFAULT")
249
248
250 def _visit_column_type(self, table, column, delta):
249 def _visit_column_type(self, table, column, delta):
251 type_ = delta['type']
250 type_ = delta['type']
252 type_text = str(type_.compile(dialect=self.dialect))
251 type_text = str(type_.compile(dialect=self.dialect))
253 self.append("TYPE %s" % type_text)
252 self.append("TYPE %s" % type_text)
254
253
255 def _visit_column_name(self, table, column, delta):
254 def _visit_column_name(self, table, column, delta):
256 self.start_alter_table(table)
255 self.start_alter_table(table)
257 q = util.safe_quote(table)
256 q = util.safe_quote(table)
258 col_name = self.preparer.quote(delta.current_name, q)
257 col_name = self.preparer.quote(delta.current_name, q)
259 new_name = self.preparer.format_column(delta.result_column)
258 new_name = self.preparer.format_column(delta.result_column)
260 self.append('RENAME COLUMN %s TO %s' % (col_name, new_name))
259 self.append('RENAME COLUMN %s TO %s' % (col_name, new_name))
261
260
262
261
263 class ANSIConstraintCommon(AlterTableVisitor):
262 class ANSIConstraintCommon(AlterTableVisitor):
264 """
263 """
265 Migrate's constraints require a separate creation function from
264 Migrate's constraints require a separate creation function from
266 SA's: Migrate's constraints are created independently of a table;
265 SA's: Migrate's constraints are created independently of a table;
267 SA's are created at the same time as the table.
266 SA's are created at the same time as the table.
268 """
267 """
269
268
270 def get_constraint_name(self, cons):
269 def get_constraint_name(self, cons):
271 """Gets a name for the given constraint.
270 """Gets a name for the given constraint.
272
271
273 If the name is already set it will be used otherwise the
272 If the name is already set it will be used otherwise the
274 constraint's :meth:`autoname <migrate.changeset.constraint.ConstraintChangeset.autoname>`
273 constraint's :meth:`autoname <migrate.changeset.constraint.ConstraintChangeset.autoname>`
275 method is used.
274 method is used.
276
275
277 :param cons: constraint object
276 :param cons: constraint object
278 """
277 """
279 if cons.name is not None:
278 if cons.name is not None:
280 ret = cons.name
279 ret = cons.name
281 else:
280 else:
282 ret = cons.name = cons.autoname()
281 ret = cons.name = cons.autoname()
283 return self.preparer.quote(ret, cons.quote)
282 return self.preparer.quote(ret, cons.quote)
284
283
285 def visit_migrate_primary_key_constraint(self, *p, **k):
284 def visit_migrate_primary_key_constraint(self, *p, **k):
286 self._visit_constraint(*p, **k)
285 self._visit_constraint(*p, **k)
287
286
288 def visit_migrate_foreign_key_constraint(self, *p, **k):
287 def visit_migrate_foreign_key_constraint(self, *p, **k):
289 self._visit_constraint(*p, **k)
288 self._visit_constraint(*p, **k)
290
289
291 def visit_migrate_check_constraint(self, *p, **k):
290 def visit_migrate_check_constraint(self, *p, **k):
292 self._visit_constraint(*p, **k)
291 self._visit_constraint(*p, **k)
293
292
294 def visit_migrate_unique_constraint(self, *p, **k):
293 def visit_migrate_unique_constraint(self, *p, **k):
295 self._visit_constraint(*p, **k)
294 self._visit_constraint(*p, **k)
296
295
297 class ANSIConstraintGenerator(ANSIConstraintCommon, SchemaGenerator):
296 class ANSIConstraintGenerator(ANSIConstraintCommon, SchemaGenerator):
298 def _visit_constraint(self, constraint):
297 def _visit_constraint(self, constraint):
299 constraint.name = self.get_constraint_name(constraint)
298 constraint.name = self.get_constraint_name(constraint)
300 self.append(self.process(AddConstraint(constraint)))
299 self.append(self.process(AddConstraint(constraint)))
301 self.execute()
300 self.execute()
302
301
303 class ANSIConstraintDropper(ANSIConstraintCommon, SchemaDropper):
302 class ANSIConstraintDropper(ANSIConstraintCommon, SchemaDropper):
304 def _visit_constraint(self, constraint):
303 def _visit_constraint(self, constraint):
305 constraint.name = self.get_constraint_name(constraint)
304 constraint.name = self.get_constraint_name(constraint)
306 self.append(self.process(DropConstraint(constraint, cascade=constraint.cascade)))
305 self.append(self.process(DropConstraint(constraint, cascade=constraint.cascade)))
307 self.execute()
306 self.execute()
308
307
309
308
310 class ANSIDialect(DefaultDialect):
309 class ANSIDialect(DefaultDialect):
311 columngenerator = ANSIColumnGenerator
310 columngenerator = ANSIColumnGenerator
312 columndropper = ANSIColumnDropper
311 columndropper = ANSIColumnDropper
313 schemachanger = ANSISchemaChanger
312 schemachanger = ANSISchemaChanger
314 constraintgenerator = ANSIConstraintGenerator
313 constraintgenerator = ANSIConstraintGenerator
315 constraintdropper = ANSIConstraintDropper
314 constraintdropper = ANSIConstraintDropper
@@ -1,200 +1,200 b''
1 """
1 """
2 This module defines standalone schema constraint classes.
2 This module defines standalone schema constraint classes.
3 """
3 """
4 from sqlalchemy import schema
4 from sqlalchemy import schema
5
5
6 from rhodecode.lib.dbmigrate.migrate.exceptions import *
6 from rhodecode.lib.dbmigrate.migrate.exceptions import *
7
7
8
8
9 class ConstraintChangeset(object):
9 class ConstraintChangeset(object):
10 """Base class for Constraint classes."""
10 """Base class for Constraint classes."""
11
11
12 def _normalize_columns(self, cols, table_name=False):
12 def _normalize_columns(self, cols, table_name=False):
13 """Given: column objects or names; return col names and
13 """Given: column objects or names; return col names and
14 (maybe) a table"""
14 (maybe) a table"""
15 colnames = []
15 colnames = []
16 table = None
16 table = None
17 for col in cols:
17 for col in cols:
18 if isinstance(col, schema.Column):
18 if isinstance(col, schema.Column):
19 if col.table is not None and table is None:
19 if col.table is not None and table is None:
20 table = col.table
20 table = col.table
21 if table_name:
21 if table_name:
22 col = '.'.join((col.table.name, col.name))
22 col = '.'.join((col.table.name, col.name))
23 else:
23 else:
24 col = col.name
24 col = col.name
25 colnames.append(col)
25 colnames.append(col)
26 return colnames, table
26 return colnames, table
27
27
28 def __do_imports(self, visitor_name, *a, **kw):
28 def __do_imports(self, visitor_name, *a, **kw):
29 engine = kw.pop('engine', self.table.bind)
29 engine = kw.pop('engine', self.table.bind)
30 from rhodecode.lib.dbmigrate.migrate.changeset.databases.visitor import (
30 from rhodecode.lib.dbmigrate.migrate.changeset.databases.visitor import (
31 get_engine_visitor, run_single_visitor)
31 get_engine_visitor, run_single_visitor)
32 visitorcallable = get_engine_visitor(engine, visitor_name)
32 visitorcallable = get_engine_visitor(engine, visitor_name)
33 run_single_visitor(engine, visitorcallable, self, *a, **kw)
33 run_single_visitor(engine, visitorcallable, self, *a, **kw)
34
34
35 def create(self, *a, **kw):
35 def create(self, *a, **kw):
36 """Create the constraint in the database.
36 """Create the constraint in the database.
37
37
38 :param engine: the database engine to use. If this is \
38 :param engine: the database engine to use. If this is \
39 :keyword:`None` the instance's engine will be used
39 :keyword:`None` the instance's engine will be used
40 :type engine: :class:`sqlalchemy.engine.base.Engine`
40 :type engine: :class:`sqlalchemy.engine.base.Engine`
41 :param connection: reuse connection istead of creating new one.
41 :param connection: reuse connection istead of creating new one.
42 :type connection: :class:`sqlalchemy.engine.base.Connection` instance
42 :type connection: :class:`sqlalchemy.engine.base.Connection` instance
43 """
43 """
44 # TODO: set the parent here instead of in __init__
44 # TODO: set the parent here instead of in __init__
45 self.__do_imports('constraintgenerator', *a, **kw)
45 self.__do_imports('constraintgenerator', *a, **kw)
46
46
47 def drop(self, *a, **kw):
47 def drop(self, *a, **kw):
48 """Drop the constraint from the database.
48 """Drop the constraint from the database.
49
49
50 :param engine: the database engine to use. If this is
50 :param engine: the database engine to use. If this is
51 :keyword:`None` the instance's engine will be used
51 :keyword:`None` the instance's engine will be used
52 :param cascade: Issue CASCADE drop if database supports it
52 :param cascade: Issue CASCADE drop if database supports it
53 :type engine: :class:`sqlalchemy.engine.base.Engine`
53 :type engine: :class:`sqlalchemy.engine.base.Engine`
54 :type cascade: bool
54 :type cascade: bool
55 :param connection: reuse connection istead of creating new one.
55 :param connection: reuse connection istead of creating new one.
56 :type connection: :class:`sqlalchemy.engine.base.Connection` instance
56 :type connection: :class:`sqlalchemy.engine.base.Connection` instance
57 :returns: Instance with cleared columns
57 :returns: Instance with cleared columns
58 """
58 """
59 self.cascade = kw.pop('cascade', False)
59 self.cascade = kw.pop('cascade', False)
60 self.__do_imports('constraintdropper', *a, **kw)
60 self.__do_imports('constraintdropper', *a, **kw)
61 # the spirit of Constraint objects is that they
61 # the spirit of Constraint objects is that they
62 # are immutable (just like in a DB. they're only ADDed
62 # are immutable (just like in a DB. they're only ADDed
63 # or DROPped).
63 # or DROPped).
64 #self.columns.clear()
64 #self.columns.clear()
65 return self
65 return self
66
66
67
67
68 class PrimaryKeyConstraint(ConstraintChangeset, schema.PrimaryKeyConstraint):
68 class PrimaryKeyConstraint(ConstraintChangeset, schema.PrimaryKeyConstraint):
69 """Construct PrimaryKeyConstraint
69 """Construct PrimaryKeyConstraint
70
70
71 Migrate's additional parameters:
71 Migrate's additional parameters:
72
72
73 :param cols: Columns in constraint.
73 :param cols: Columns in constraint.
74 :param table: If columns are passed as strings, this kw is required
74 :param table: If columns are passed as strings, this kw is required
75 :type table: Table instance
75 :type table: Table instance
76 :type cols: strings or Column instances
76 :type cols: strings or Column instances
77 """
77 """
78
78
79 __migrate_visit_name__ = 'migrate_primary_key_constraint'
79 __migrate_visit_name__ = 'migrate_primary_key_constraint'
80
80
81 def __init__(self, *cols, **kwargs):
81 def __init__(self, *cols, **kwargs):
82 colnames, table = self._normalize_columns(cols)
82 colnames, table = self._normalize_columns(cols)
83 table = kwargs.pop('table', table)
83 table = kwargs.pop('table', table)
84 super(PrimaryKeyConstraint, self).__init__(*colnames, **kwargs)
84 super(PrimaryKeyConstraint, self).__init__(*colnames, **kwargs)
85 if table is not None:
85 if table is not None:
86 self._set_parent(table)
86 self._set_parent(table)
87
87
88 def autoname(self):
88 def autoname(self):
89 """Mimic the database's automatic constraint names"""
89 """Mimic the database's automatic constraint names"""
90 return "%s_pkey" % self.table.name
90 return "%s_pkey" % self.table.name
91
91
92
92
93 class ForeignKeyConstraint(ConstraintChangeset, schema.ForeignKeyConstraint):
93 class ForeignKeyConstraint(ConstraintChangeset, schema.ForeignKeyConstraint):
94 """Construct ForeignKeyConstraint
94 """Construct ForeignKeyConstraint
95
95
96 Migrate's additional parameters:
96 Migrate's additional parameters:
97
97
98 :param columns: Columns in constraint
98 :param columns: Columns in constraint
99 :param refcolumns: Columns that this FK reffers to in another table.
99 :param refcolumns: Columns that this FK reffers to in another table.
100 :param table: If columns are passed as strings, this kw is required
100 :param table: If columns are passed as strings, this kw is required
101 :type table: Table instance
101 :type table: Table instance
102 :type columns: list of strings or Column instances
102 :type columns: list of strings or Column instances
103 :type refcolumns: list of strings or Column instances
103 :type refcolumns: list of strings or Column instances
104 """
104 """
105
105
106 __migrate_visit_name__ = 'migrate_foreign_key_constraint'
106 __migrate_visit_name__ = 'migrate_foreign_key_constraint'
107
107
108 def __init__(self, columns, refcolumns, *args, **kwargs):
108 def __init__(self, columns, refcolumns, *args, **kwargs):
109 colnames, table = self._normalize_columns(columns)
109 colnames, table = self._normalize_columns(columns)
110 table = kwargs.pop('table', table)
110 table = kwargs.pop('table', table)
111 refcolnames, reftable = self._normalize_columns(refcolumns,
111 refcolnames, reftable = self._normalize_columns(refcolumns,
112 table_name=True)
112 table_name=True)
113 super(ForeignKeyConstraint, self).__init__(
113 super(ForeignKeyConstraint, self).__init__(
114 colnames, refcolnames, *args,**kwargs
114 colnames, refcolnames, *args, **kwargs
115 )
115 )
116 if table is not None:
116 if table is not None:
117 self._set_parent(table)
117 self._set_parent(table)
118
118
119 @property
119 @property
120 def referenced(self):
120 def referenced(self):
121 return [e.column for e in self.elements]
121 return [e.column for e in self.elements]
122
122
123 @property
123 @property
124 def reftable(self):
124 def reftable(self):
125 return self.referenced[0].table
125 return self.referenced[0].table
126
126
127 def autoname(self):
127 def autoname(self):
128 """Mimic the database's automatic constraint names"""
128 """Mimic the database's automatic constraint names"""
129 if hasattr(self.columns, 'keys'):
129 if hasattr(self.columns, 'keys'):
130 # SA <= 0.5
130 # SA <= 0.5
131 firstcol = self.columns[self.columns.keys()[0]]
131 firstcol = self.columns[self.columns.keys()[0]]
132 ret = "%(table)s_%(firstcolumn)s_fkey" % {
132 ret = "%(table)s_%(firstcolumn)s_fkey" % {
133 'table': firstcol.table.name,
133 'table': firstcol.table.name,
134 'firstcolumn': firstcol.name,}
134 'firstcolumn': firstcol.name,}
135 else:
135 else:
136 # SA >= 0.6
136 # SA >= 0.6
137 ret = "%(table)s_%(firstcolumn)s_fkey" % {
137 ret = "%(table)s_%(firstcolumn)s_fkey" % {
138 'table': self.table.name,
138 'table': self.table.name,
139 'firstcolumn': self.columns[0],}
139 'firstcolumn': self.columns[0],}
140 return ret
140 return ret
141
141
142
142
143 class CheckConstraint(ConstraintChangeset, schema.CheckConstraint):
143 class CheckConstraint(ConstraintChangeset, schema.CheckConstraint):
144 """Construct CheckConstraint
144 """Construct CheckConstraint
145
145
146 Migrate's additional parameters:
146 Migrate's additional parameters:
147
147
148 :param sqltext: Plain SQL text to check condition
148 :param sqltext: Plain SQL text to check condition
149 :param columns: If not name is applied, you must supply this kw\
149 :param columns: If not name is applied, you must supply this kw\
150 to autoname constraint
150 to autoname constraint
151 :param table: If columns are passed as strings, this kw is required
151 :param table: If columns are passed as strings, this kw is required
152 :type table: Table instance
152 :type table: Table instance
153 :type columns: list of Columns instances
153 :type columns: list of Columns instances
154 :type sqltext: string
154 :type sqltext: string
155 """
155 """
156
156
157 __migrate_visit_name__ = 'migrate_check_constraint'
157 __migrate_visit_name__ = 'migrate_check_constraint'
158
158
159 def __init__(self, sqltext, *args, **kwargs):
159 def __init__(self, sqltext, *args, **kwargs):
160 cols = kwargs.pop('columns', [])
160 cols = kwargs.pop('columns', [])
161 if not cols and not kwargs.get('name', False):
161 if not cols and not kwargs.get('name', False):
162 raise InvalidConstraintError('You must either set "name"'
162 raise InvalidConstraintError('You must either set "name"'
163 'parameter or "columns" to autogenarate it.')
163 'parameter or "columns" to autogenarate it.')
164 colnames, table = self._normalize_columns(cols)
164 colnames, table = self._normalize_columns(cols)
165 table = kwargs.pop('table', table)
165 table = kwargs.pop('table', table)
166 schema.CheckConstraint.__init__(self, sqltext, *args, **kwargs)
166 schema.CheckConstraint.__init__(self, sqltext, *args, **kwargs)
167 if table is not None:
167 if table is not None:
168 self._set_parent(table)
168 self._set_parent(table)
169 self.colnames = colnames
169 self.colnames = colnames
170
170
171 def autoname(self):
171 def autoname(self):
172 return "%(table)s_%(cols)s_check" % \
172 return "%(table)s_%(cols)s_check" % \
173 {'table': self.table.name, 'cols': "_".join(self.colnames)}
173 {'table': self.table.name, 'cols': "_".join(self.colnames)}
174
174
175
175
176 class UniqueConstraint(ConstraintChangeset, schema.UniqueConstraint):
176 class UniqueConstraint(ConstraintChangeset, schema.UniqueConstraint):
177 """Construct UniqueConstraint
177 """Construct UniqueConstraint
178
178
179 Migrate's additional parameters:
179 Migrate's additional parameters:
180
180
181 :param cols: Columns in constraint.
181 :param cols: Columns in constraint.
182 :param table: If columns are passed as strings, this kw is required
182 :param table: If columns are passed as strings, this kw is required
183 :type table: Table instance
183 :type table: Table instance
184 :type cols: strings or Column instances
184 :type cols: strings or Column instances
185
185
186 .. versionadded:: 0.6.0
186 .. versionadded:: 0.6.0
187 """
187 """
188
188
189 __migrate_visit_name__ = 'migrate_unique_constraint'
189 __migrate_visit_name__ = 'migrate_unique_constraint'
190
190
191 def __init__(self, *cols, **kwargs):
191 def __init__(self, *cols, **kwargs):
192 self.colnames, table = self._normalize_columns(cols)
192 self.colnames, table = self._normalize_columns(cols)
193 table = kwargs.pop('table', table)
193 table = kwargs.pop('table', table)
194 super(UniqueConstraint, self).__init__(*self.colnames, **kwargs)
194 super(UniqueConstraint, self).__init__(*self.colnames, **kwargs)
195 if table is not None:
195 if table is not None:
196 self._set_parent(table)
196 self._set_parent(table)
197
197
198 def autoname(self):
198 def autoname(self):
199 """Mimic the database's automatic constraint names"""
199 """Mimic the database's automatic constraint names"""
200 return "%s_%s_key" % (self.table.name, '_'.join(self.colnames))
200 return "%s_%s_key" % (self.table.name, '_'.join(self.colnames))
@@ -1,205 +1,209 b''
1 """
1 """
2 `SQLite`_ database specific implementations of changeset classes.
2 `SQLite`_ database specific implementations of changeset classes.
3
3
4 .. _`SQLite`: http://www.sqlite.org/
4 .. _`SQLite`: http://www.sqlite.org/
5 """
5 """
6 from UserDict import DictMixin
6 try: # Python 3
7 from collections.abc import MutableMapping as DictMixin
8 except ImportError: # Python 2
9 from UserDict import DictMixin
7 from copy import copy
10 from copy import copy
8 import re
11 import re
9
12
10 from sqlalchemy.databases import sqlite as sa_base
13 from sqlalchemy.databases import sqlite as sa_base
14 from sqlalchemy.schema import ForeignKeyConstraint
11 from sqlalchemy.schema import UniqueConstraint
15 from sqlalchemy.schema import UniqueConstraint
12
16
13 from rhodecode.lib.dbmigrate.migrate import exceptions
17 from rhodecode.lib.dbmigrate.migrate import exceptions
14 from rhodecode.lib.dbmigrate.migrate.changeset import ansisql
18 from rhodecode.lib.dbmigrate.migrate.changeset import ansisql
15 import sqlite3
19 import sqlite3
16
20
17 SQLiteSchemaGenerator = sa_base.SQLiteDDLCompiler
21 SQLiteSchemaGenerator = sa_base.SQLiteDDLCompiler
18
22
19
23
20 class SQLiteCommon(object):
24 class SQLiteCommon(object):
21
25
22 def _not_supported(self, op):
26 def _not_supported(self, op):
23 raise exceptions.NotSupportedError("SQLite does not support "
27 raise exceptions.NotSupportedError("SQLite does not support "
24 "%s; see http://www.sqlite.org/lang_altertable.html" % op)
28 "%s; see http://www.sqlite.org/lang_altertable.html" % op)
25
29
26
30
27 class SQLiteHelper(SQLiteCommon):
31 class SQLiteHelper(SQLiteCommon):
28
32
29 def _get_unique_constraints(self, table):
33 def _get_unique_constraints(self, table):
30 """Retrieve information about existing unique constraints of the table
34 """Retrieve information about existing unique constraints of the table
31
35
32 This feature is needed for recreate_table() to work properly.
36 This feature is needed for recreate_table() to work properly.
33 """
37 """
34
38
35 data = table.metadata.bind.execute(
39 data = table.metadata.bind.execute(
36 """SELECT sql
40 """SELECT sql
37 FROM sqlite_master
41 FROM sqlite_master
38 WHERE
42 WHERE
39 type='table' AND
43 type='table' AND
40 name=:table_name""",
44 name=:table_name""",
41 table_name=table.name
45 table_name=table.name
42 ).fetchone()[0]
46 ).fetchone()[0]
43
47
44 UNIQUE_PATTERN = "CONSTRAINT (\w+) UNIQUE \(([^\)]+)\)"
48 UNIQUE_PATTERN = "CONSTRAINT (\w+) UNIQUE \(([^\)]+)\)"
45 constraints = []
49 constraints = []
46 for name, cols in re.findall(UNIQUE_PATTERN, data):
50 for name, cols in re.findall(UNIQUE_PATTERN, data):
47 # Filter out any columns that were dropped from the table.
51 # Filter out any columns that were dropped from the table.
48 columns = []
52 columns = []
49 for c in cols.split(","):
53 for c in cols.split(","):
50 if c in table.columns:
54 if c in table.columns:
51 # There was a bug in reflection of SQLite columns with
55 # There was a bug in reflection of SQLite columns with
52 # reserved identifiers as names (SQLite can return them
56 # reserved identifiers as names (SQLite can return them
53 # wrapped with double quotes), so strip double quotes.
57 # wrapped with double quotes), so strip double quotes.
54 columns.extend(c.strip(' "'))
58 columns.extend(c.strip(' "'))
55 if columns:
59 if columns:
56 constraints.extend(UniqueConstraint(*columns, name=name))
60 constraints.extend(UniqueConstraint(*columns, name=name))
57 return constraints
61 return constraints
58
62
59 def recreate_table(self, table, column=None, delta=None,
63 def recreate_table(self, table, column=None, delta=None,
60 omit_uniques=None):
64 omit_uniques=None):
61 table_name = self.preparer.format_table(table)
65 table_name = self.preparer.format_table(table)
62
66
63 # we remove all indexes so as not to have
67 # we remove all indexes so as not to have
64 # problems during copy and re-create
68 # problems during copy and re-create
65 for index in table.indexes:
69 for index in table.indexes:
66 index.drop()
70 index.drop()
67
71
68 # reflect existing unique constraints
72 # reflect existing unique constraints
69 for uc in self._get_unique_constraints(table):
73 for uc in self._get_unique_constraints(table):
70 table.append_constraint(uc)
74 table.append_constraint(uc)
71 # omit given unique constraints when creating a new table if required
75 # omit given unique constraints when creating a new table if required
72 table.constraints = set([
76 table.constraints = set([
73 cons for cons in table.constraints
77 cons for cons in table.constraints
74 if omit_uniques is None or cons.name not in omit_uniques
78 if omit_uniques is None or cons.name not in omit_uniques
75 ])
79 ])
76 tup = sqlite3.sqlite_version_info
80 tup = sqlite3.sqlite_version_info
77 if tup[0] > 3 or (tup[0] == 3 and tup[1] >= 26):
81 if tup[0] > 3 or (tup[0] == 3 and tup[1] >= 26):
78 self.append('PRAGMA legacy_alter_table = ON')
82 self.append('PRAGMA legacy_alter_table = ON')
79 self.execute()
83 self.execute()
80
84
81 self.append('ALTER TABLE %s RENAME TO migration_tmp' % table_name)
85 self.append('ALTER TABLE %s RENAME TO migration_tmp' % table_name)
82 self.execute()
86 self.execute()
83 if tup[0] > 3 or (tup[0] == 3 and tup[1] >= 26):
87 if tup[0] > 3 or (tup[0] == 3 and tup[1] >= 26):
84 self.append('PRAGMA legacy_alter_table = OFF')
88 self.append('PRAGMA legacy_alter_table = OFF')
85 self.execute()
89 self.execute()
86 insertion_string = self._modify_table(table, column, delta)
90 insertion_string = self._modify_table(table, column, delta)
87
91
88 table.create(bind=self.connection)
92 table.create(bind=self.connection)
89 self.append(insertion_string % {'table_name': table_name})
93 self.append(insertion_string % {'table_name': table_name})
90 self.execute()
94 self.execute()
91 self.append('DROP TABLE migration_tmp')
95 self.append('DROP TABLE migration_tmp')
92 self.execute()
96 self.execute()
93
97
94 def visit_column(self, delta):
98 def visit_column(self, delta):
95 if isinstance(delta, DictMixin):
99 if isinstance(delta, DictMixin):
96 column = delta.result_column
100 column = delta.result_column
97 table = self._to_table(delta.table)
101 table = self._to_table(delta.table)
98 else:
102 else:
99 column = delta
103 column = delta
100 table = self._to_table(column.table)
104 table = self._to_table(column.table)
101
105
102 self.recreate_table(table,column,delta)
106 self.recreate_table(table,column,delta)
103
107
104 class SQLiteColumnGenerator(SQLiteSchemaGenerator,
108 class SQLiteColumnGenerator(SQLiteSchemaGenerator,
105 ansisql.ANSIColumnGenerator,
109 ansisql.ANSIColumnGenerator,
106 # at the end so we get the normal
110 # at the end so we get the normal
107 # visit_column by default
111 # visit_column by default
108 SQLiteHelper,
112 SQLiteHelper,
109 SQLiteCommon
113 SQLiteCommon
110 ):
114 ):
111 """SQLite ColumnGenerator"""
115 """SQLite ColumnGenerator"""
112
116
113 def _modify_table(self, table, column, delta):
117 def _modify_table(self, table, column, delta):
114 columns = ' ,'.join(map(
118 columns = ' ,'.join(map(
115 self.preparer.format_column,
119 self.preparer.format_column,
116 [c for c in table.columns if c.name!=column.name]))
120 [c for c in table.columns if c.name!=column.name]))
117 return ('INSERT INTO %%(table_name)s (%(cols)s) '
121 return ('INSERT INTO %%(table_name)s (%(cols)s) '
118 'SELECT %(cols)s from migration_tmp')%{'cols':columns}
122 'SELECT %(cols)s from migration_tmp')%{'cols':columns}
119
123
120 def visit_column(self,column):
124 def visit_column(self,column):
121 if column.foreign_keys:
125 if column.foreign_keys:
122 SQLiteHelper.visit_column(self,column)
126 SQLiteHelper.visit_column(self,column)
123 else:
127 else:
124 super(SQLiteColumnGenerator,self).visit_column(column)
128 super(SQLiteColumnGenerator,self).visit_column(column)
125
129
126 class SQLiteColumnDropper(SQLiteHelper, ansisql.ANSIColumnDropper):
130 class SQLiteColumnDropper(SQLiteHelper, ansisql.ANSIColumnDropper):
127 """SQLite ColumnDropper"""
131 """SQLite ColumnDropper"""
128
132
129 def _modify_table(self, table, column, delta):
133 def _modify_table(self, table, column, delta):
130
134
131 columns = ' ,'.join(map(self.preparer.format_column, table.columns))
135 columns = ' ,'.join(map(self.preparer.format_column, table.columns))
132 return 'INSERT INTO %(table_name)s SELECT ' + columns + \
136 return 'INSERT INTO %(table_name)s SELECT ' + columns + \
133 ' from migration_tmp'
137 ' from migration_tmp'
134
138
135 def visit_column(self,column):
139 def visit_column(self,column):
136 # For SQLite, we *have* to remove the column here so the table
140 # For SQLite, we *have* to remove the column here so the table
137 # is re-created properly.
141 # is re-created properly.
138 column.remove_from_table(column.table,unset_table=False)
142 column.remove_from_table(column.table,unset_table=False)
139 super(SQLiteColumnDropper,self).visit_column(column)
143 super(SQLiteColumnDropper,self).visit_column(column)
140
144
141
145
142 class SQLiteSchemaChanger(SQLiteHelper, ansisql.ANSISchemaChanger):
146 class SQLiteSchemaChanger(SQLiteHelper, ansisql.ANSISchemaChanger):
143 """SQLite SchemaChanger"""
147 """SQLite SchemaChanger"""
144
148
145 def _modify_table(self, table, column, delta):
149 def _modify_table(self, table, column, delta):
146 return 'INSERT INTO %(table_name)s SELECT * from migration_tmp'
150 return 'INSERT INTO %(table_name)s SELECT * from migration_tmp'
147
151
148 def visit_index(self, index):
152 def visit_index(self, index):
149 """Does not support ALTER INDEX"""
153 """Does not support ALTER INDEX"""
150 self._not_supported('ALTER INDEX')
154 self._not_supported('ALTER INDEX')
151
155
152
156
153 class SQLiteConstraintGenerator(ansisql.ANSIConstraintGenerator, SQLiteHelper, SQLiteCommon):
157 class SQLiteConstraintGenerator(ansisql.ANSIConstraintGenerator, SQLiteHelper, SQLiteCommon):
154
158
155 def visit_migrate_primary_key_constraint(self, constraint):
159 def visit_migrate_primary_key_constraint(self, constraint):
156 tmpl = "CREATE UNIQUE INDEX %s ON %s ( %s )"
160 tmpl = "CREATE UNIQUE INDEX %s ON %s ( %s )"
157 cols = ', '.join(map(self.preparer.format_column, constraint.columns))
161 cols = ', '.join(map(self.preparer.format_column, constraint.columns))
158 tname = self.preparer.format_table(constraint.table)
162 tname = self.preparer.format_table(constraint.table)
159 name = self.get_constraint_name(constraint)
163 name = self.get_constraint_name(constraint)
160 msg = tmpl % (name, tname, cols)
164 msg = tmpl % (name, tname, cols)
161 self.append(msg)
165 self.append(msg)
162 self.execute()
166 self.execute()
163
167
164 def _modify_table(self, table, column, delta):
168 def _modify_table(self, table, column, delta):
165 return 'INSERT INTO %(table_name)s SELECT * from migration_tmp'
169 return 'INSERT INTO %(table_name)s SELECT * from migration_tmp'
166
170
167 def visit_migrate_foreign_key_constraint(self, *p, **k):
171 def visit_migrate_foreign_key_constraint(self, *p, **k):
168 self.recreate_table(p[0].table)
172 self.recreate_table(p[0].table)
169
173
170 def visit_migrate_unique_constraint(self, *p, **k):
174 def visit_migrate_unique_constraint(self, *p, **k):
171 self.recreate_table(p[0].table)
175 self.recreate_table(p[0].table)
172
176
173
177
174 class SQLiteConstraintDropper(ansisql.ANSIColumnDropper,
178 class SQLiteConstraintDropper(ansisql.ANSIColumnDropper,
175 SQLiteHelper,
179 SQLiteHelper,
176 ansisql.ANSIConstraintCommon):
180 ansisql.ANSIConstraintCommon):
177
181
178 def _modify_table(self, table, column, delta):
182 def _modify_table(self, table, column, delta):
179 return 'INSERT INTO %(table_name)s SELECT * from migration_tmp'
183 return 'INSERT INTO %(table_name)s SELECT * from migration_tmp'
180
184
181 def visit_migrate_primary_key_constraint(self, constraint):
185 def visit_migrate_primary_key_constraint(self, constraint):
182 tmpl = "DROP INDEX %s "
186 tmpl = "DROP INDEX %s "
183 name = self.get_constraint_name(constraint)
187 name = self.get_constraint_name(constraint)
184 msg = tmpl % (name)
188 msg = tmpl % (name)
185 self.append(msg)
189 self.append(msg)
186 self.execute()
190 self.execute()
187
191
188 def visit_migrate_foreign_key_constraint(self, *p, **k):
192 def visit_migrate_foreign_key_constraint(self, *p, **k):
189 self._not_supported('ALTER TABLE DROP CONSTRAINT')
193 self._not_supported('ALTER TABLE DROP CONSTRAINT')
190
194
191 def visit_migrate_check_constraint(self, *p, **k):
195 def visit_migrate_check_constraint(self, *p, **k):
192 self._not_supported('ALTER TABLE DROP CONSTRAINT')
196 self._not_supported('ALTER TABLE DROP CONSTRAINT')
193
197
194 def visit_migrate_unique_constraint(self, *p, **k):
198 def visit_migrate_unique_constraint(self, *p, **k):
195 self.recreate_table(p[0].table, omit_uniques=[p[0].name])
199 self.recreate_table(p[0].table, omit_uniques=[p[0].name])
196
200
197
201
198 # TODO: technically primary key is a NOT NULL + UNIQUE constraint, should add NOT NULL to index
202 # TODO: technically primary key is a NOT NULL + UNIQUE constraint, should add NOT NULL to index
199
203
200 class SQLiteDialect(ansisql.ANSIDialect):
204 class SQLiteDialect(ansisql.ANSIDialect):
201 columngenerator = SQLiteColumnGenerator
205 columngenerator = SQLiteColumnGenerator
202 columndropper = SQLiteColumnDropper
206 columndropper = SQLiteColumnDropper
203 schemachanger = SQLiteSchemaChanger
207 schemachanger = SQLiteSchemaChanger
204 constraintgenerator = SQLiteConstraintGenerator
208 constraintgenerator = SQLiteConstraintGenerator
205 constraintdropper = SQLiteConstraintDropper
209 constraintdropper = SQLiteConstraintDropper
@@ -1,666 +1,669 b''
1 """
1 """
2 Schema module providing common schema operations.
2 Schema module providing common schema operations.
3 """
3 """
4 import abc
5 try: # Python 3
6 from collections.abc import MutableMapping as DictMixin
7 except ImportError: # Python 2
8 from UserDict import DictMixin
4 import warnings
9 import warnings
5
10
6 from UserDict import DictMixin
7
8 import sqlalchemy
11 import sqlalchemy
9
12
10 from sqlalchemy.schema import ForeignKeyConstraint
13 from sqlalchemy.schema import ForeignKeyConstraint
11 from sqlalchemy.schema import UniqueConstraint
14 from sqlalchemy.schema import UniqueConstraint
12 from pyramid import compat
15 from pyramid import compat
13
16
14 from rhodecode.lib.dbmigrate.migrate.exceptions import *
17 from rhodecode.lib.dbmigrate.migrate.exceptions import *
15 from rhodecode.lib.dbmigrate.migrate.changeset import SQLA_07, SQLA_08
18 from rhodecode.lib.dbmigrate.migrate.changeset import SQLA_07, SQLA_08
16 from rhodecode.lib.dbmigrate.migrate.changeset import util
19 from rhodecode.lib.dbmigrate.migrate.changeset import util
17 from rhodecode.lib.dbmigrate.migrate.changeset.databases.visitor import (
20 from rhodecode.lib.dbmigrate.migrate.changeset.databases.visitor import (
18 get_engine_visitor, run_single_visitor)
21 get_engine_visitor, run_single_visitor)
19
22
20
23
21 __all__ = [
24 __all__ = [
22 'create_column',
25 'create_column',
23 'drop_column',
26 'drop_column',
24 'alter_column',
27 'alter_column',
25 'rename_table',
28 'rename_table',
26 'rename_index',
29 'rename_index',
27 'ChangesetTable',
30 'ChangesetTable',
28 'ChangesetColumn',
31 'ChangesetColumn',
29 'ChangesetIndex',
32 'ChangesetIndex',
30 'ChangesetDefaultClause',
33 'ChangesetDefaultClause',
31 'ColumnDelta',
34 'ColumnDelta',
32 ]
35 ]
33
36
34 def create_column(column, table=None, *p, **kw):
37 def create_column(column, table=None, *p, **kw):
35 """Create a column, given the table.
38 """Create a column, given the table.
36
39
37 API to :meth:`ChangesetColumn.create`.
40 API to :meth:`ChangesetColumn.create`.
38 """
41 """
39 if table is not None:
42 if table is not None:
40 return table.create_column(column, *p, **kw)
43 return table.create_column(column, *p, **kw)
41 return column.create(*p, **kw)
44 return column.create(*p, **kw)
42
45
43
46
44 def drop_column(column, table=None, *p, **kw):
47 def drop_column(column, table=None, *p, **kw):
45 """Drop a column, given the table.
48 """Drop a column, given the table.
46
49
47 API to :meth:`ChangesetColumn.drop`.
50 API to :meth:`ChangesetColumn.drop`.
48 """
51 """
49 if table is not None:
52 if table is not None:
50 return table.drop_column(column, *p, **kw)
53 return table.drop_column(column, *p, **kw)
51 return column.drop(*p, **kw)
54 return column.drop(*p, **kw)
52
55
53
56
54 def rename_table(table, name, engine=None, **kw):
57 def rename_table(table, name, engine=None, **kw):
55 """Rename a table.
58 """Rename a table.
56
59
57 If Table instance is given, engine is not used.
60 If Table instance is given, engine is not used.
58
61
59 API to :meth:`ChangesetTable.rename`.
62 API to :meth:`ChangesetTable.rename`.
60
63
61 :param table: Table to be renamed.
64 :param table: Table to be renamed.
62 :param name: New name for Table.
65 :param name: New name for Table.
63 :param engine: Engine instance.
66 :param engine: Engine instance.
64 :type table: string or Table instance
67 :type table: string or Table instance
65 :type name: string
68 :type name: string
66 :type engine: obj
69 :type engine: obj
67 """
70 """
68 table = _to_table(table, engine)
71 table = _to_table(table, engine)
69 table.rename(name, **kw)
72 table.rename(name, **kw)
70
73
71
74
72 def rename_index(index, name, table=None, engine=None, **kw):
75 def rename_index(index, name, table=None, engine=None, **kw):
73 """Rename an index.
76 """Rename an index.
74
77
75 If Index instance is given,
78 If Index instance is given,
76 table and engine are not used.
79 table and engine are not used.
77
80
78 API to :meth:`ChangesetIndex.rename`.
81 API to :meth:`ChangesetIndex.rename`.
79
82
80 :param index: Index to be renamed.
83 :param index: Index to be renamed.
81 :param name: New name for index.
84 :param name: New name for index.
82 :param table: Table to which Index is reffered.
85 :param table: Table to which Index is reffered.
83 :param engine: Engine instance.
86 :param engine: Engine instance.
84 :type index: string or Index instance
87 :type index: string or Index instance
85 :type name: string
88 :type name: string
86 :type table: string or Table instance
89 :type table: string or Table instance
87 :type engine: obj
90 :type engine: obj
88 """
91 """
89 index = _to_index(index, table, engine)
92 index = _to_index(index, table, engine)
90 index.rename(name, **kw)
93 index.rename(name, **kw)
91
94
92
95
93 def alter_column(*p, **k):
96 def alter_column(*p, **k):
94 """Alter a column.
97 """Alter a column.
95
98
96 This is a helper function that creates a :class:`ColumnDelta` and
99 This is a helper function that creates a :class:`ColumnDelta` and
97 runs it.
100 runs it.
98
101
99 :argument column:
102 :argument column:
100 The name of the column to be altered or a
103 The name of the column to be altered or a
101 :class:`ChangesetColumn` column representing it.
104 :class:`ChangesetColumn` column representing it.
102
105
103 :param table:
106 :param table:
104 A :class:`~sqlalchemy.schema.Table` or table name to
107 A :class:`~sqlalchemy.schema.Table` or table name to
105 for the table where the column will be changed.
108 for the table where the column will be changed.
106
109
107 :param engine:
110 :param engine:
108 The :class:`~sqlalchemy.engine.base.Engine` to use for table
111 The :class:`~sqlalchemy.engine.base.Engine` to use for table
109 reflection and schema alterations.
112 reflection and schema alterations.
110
113
111 :returns: A :class:`ColumnDelta` instance representing the change.
114 :returns: A :class:`ColumnDelta` instance representing the change.
112
115
113
116
114 """
117 """
115
118
116 if 'table' not in k and isinstance(p[0], sqlalchemy.Column):
119 if 'table' not in k and isinstance(p[0], sqlalchemy.Column):
117 k['table'] = p[0].table
120 k['table'] = p[0].table
118 if 'engine' not in k:
121 if 'engine' not in k:
119 k['engine'] = k['table'].bind
122 k['engine'] = k['table'].bind
120
123
121 # deprecation
124 # deprecation
122 if len(p) >= 2 and isinstance(p[1], sqlalchemy.Column):
125 if len(p) >= 2 and isinstance(p[1], sqlalchemy.Column):
123 warnings.warn(
126 warnings.warn(
124 "Passing a Column object to alter_column is deprecated."
127 "Passing a Column object to alter_column is deprecated."
125 " Just pass in keyword parameters instead.",
128 " Just pass in keyword parameters instead.",
126 MigrateDeprecationWarning
129 MigrateDeprecationWarning
127 )
130 )
128 engine = k['engine']
131 engine = k['engine']
129
132
130 # enough tests seem to break when metadata is always altered
133 # enough tests seem to break when metadata is always altered
131 # that this crutch has to be left in until they can be sorted
134 # that this crutch has to be left in until they can be sorted
132 # out
135 # out
133 k['alter_metadata']=True
136 k['alter_metadata']=True
134
137
135 delta = ColumnDelta(*p, **k)
138 delta = ColumnDelta(*p, **k)
136
139
137 visitorcallable = get_engine_visitor(engine, 'schemachanger')
140 visitorcallable = get_engine_visitor(engine, 'schemachanger')
138 engine._run_visitor(visitorcallable, delta)
141 engine._run_visitor(visitorcallable, delta)
139
142
140 return delta
143 return delta
141
144
142
145
143 def _to_table(table, engine=None):
146 def _to_table(table, engine=None):
144 """Return if instance of Table, else construct new with metadata"""
147 """Return if instance of Table, else construct new with metadata"""
145 if isinstance(table, sqlalchemy.Table):
148 if isinstance(table, sqlalchemy.Table):
146 return table
149 return table
147
150
148 # Given: table name, maybe an engine
151 # Given: table name, maybe an engine
149 meta = sqlalchemy.MetaData()
152 meta = sqlalchemy.MetaData()
150 if engine is not None:
153 if engine is not None:
151 meta.bind = engine
154 meta.bind = engine
152 return sqlalchemy.Table(table, meta)
155 return sqlalchemy.Table(table, meta)
153
156
154
157
155 def _to_index(index, table=None, engine=None):
158 def _to_index(index, table=None, engine=None):
156 """Return if instance of Index, else construct new with metadata"""
159 """Return if instance of Index, else construct new with metadata"""
157 if isinstance(index, sqlalchemy.Index):
160 if isinstance(index, sqlalchemy.Index):
158 return index
161 return index
159
162
160 # Given: index name; table name required
163 # Given: index name; table name required
161 table = _to_table(table, engine)
164 table = _to_table(table, engine)
162 ret = sqlalchemy.Index(index)
165 ret = sqlalchemy.Index(index)
163 ret.table = table
166 ret.table = table
164 return ret
167 return ret
165
168
166
169
167 class ColumnDelta(DictMixin, sqlalchemy.schema.SchemaItem):
170 class ColumnDelta(DictMixin, sqlalchemy.schema.SchemaItem):
168 """Extracts the differences between two columns/column-parameters
171 """Extracts the differences between two columns/column-parameters
169
172
170 May receive parameters arranged in several different ways:
173 May receive parameters arranged in several different ways:
171
174
172 * **current_column, new_column, \*p, \*\*kw**
175 * **current_column, new_column, \*p, \*\*kw**
173 Additional parameters can be specified to override column
176 Additional parameters can be specified to override column
174 differences.
177 differences.
175
178
176 * **current_column, \*p, \*\*kw**
179 * **current_column, \*p, \*\*kw**
177 Additional parameters alter current_column. Table name is extracted
180 Additional parameters alter current_column. Table name is extracted
178 from current_column object.
181 from current_column object.
179 Name is changed to current_column.name from current_name,
182 Name is changed to current_column.name from current_name,
180 if current_name is specified.
183 if current_name is specified.
181
184
182 * **current_col_name, \*p, \*\*kw**
185 * **current_col_name, \*p, \*\*kw**
183 Table kw must specified.
186 Table kw must specified.
184
187
185 :param table: Table at which current Column should be bound to.\
188 :param table: Table at which current Column should be bound to.\
186 If table name is given, reflection will be used.
189 If table name is given, reflection will be used.
187 :type table: string or Table instance
190 :type table: string or Table instance
188
191
189 :param metadata: A :class:`MetaData` instance to store
192 :param metadata: A :class:`MetaData` instance to store
190 reflected table names
193 reflected table names
191
194
192 :param engine: When reflecting tables, either engine or metadata must \
195 :param engine: When reflecting tables, either engine or metadata must \
193 be specified to acquire engine object.
196 be specified to acquire engine object.
194 :type engine: :class:`Engine` instance
197 :type engine: :class:`Engine` instance
195 :returns: :class:`ColumnDelta` instance provides interface for altered attributes to \
198 :returns: :class:`ColumnDelta` instance provides interface for altered attributes to \
196 `result_column` through :func:`dict` alike object.
199 `result_column` through :func:`dict` alike object.
197
200
198 * :class:`ColumnDelta`.result_column is altered column with new attributes
201 * :class:`ColumnDelta`.result_column is altered column with new attributes
199
202
200 * :class:`ColumnDelta`.current_name is current name of column in db
203 * :class:`ColumnDelta`.current_name is current name of column in db
201
204
202
205
203 """
206 """
204
207
205 # Column attributes that can be altered
208 # Column attributes that can be altered
206 diff_keys = ('name', 'type', 'primary_key', 'nullable',
209 diff_keys = ('name', 'type', 'primary_key', 'nullable',
207 'server_onupdate', 'server_default', 'autoincrement')
210 'server_onupdate', 'server_default', 'autoincrement')
208 diffs = dict()
211 diffs = dict()
209 __visit_name__ = 'column'
212 __visit_name__ = 'column'
210
213
211 def __init__(self, *p, **kw):
214 def __init__(self, *p, **kw):
212 # 'alter_metadata' is not a public api. It exists purely
215 # 'alter_metadata' is not a public api. It exists purely
213 # as a crutch until the tests that fail when 'alter_metadata'
216 # as a crutch until the tests that fail when 'alter_metadata'
214 # behaviour always happens can be sorted out
217 # behaviour always happens can be sorted out
215 self.alter_metadata = kw.pop("alter_metadata", False)
218 self.alter_metadata = kw.pop("alter_metadata", False)
216
219
217 self.meta = kw.pop("metadata", None)
220 self.meta = kw.pop("metadata", None)
218 self.engine = kw.pop("engine", None)
221 self.engine = kw.pop("engine", None)
219
222
220 # Things are initialized differently depending on how many column
223 # Things are initialized differently depending on how many column
221 # parameters are given. Figure out how many and call the appropriate
224 # parameters are given. Figure out how many and call the appropriate
222 # method.
225 # method.
223 if len(p) >= 1 and isinstance(p[0], sqlalchemy.Column):
226 if len(p) >= 1 and isinstance(p[0], sqlalchemy.Column):
224 # At least one column specified
227 # At least one column specified
225 if len(p) >= 2 and isinstance(p[1], sqlalchemy.Column):
228 if len(p) >= 2 and isinstance(p[1], sqlalchemy.Column):
226 # Two columns specified
229 # Two columns specified
227 diffs = self.compare_2_columns(*p, **kw)
230 diffs = self.compare_2_columns(*p, **kw)
228 else:
231 else:
229 # Exactly one column specified
232 # Exactly one column specified
230 diffs = self.compare_1_column(*p, **kw)
233 diffs = self.compare_1_column(*p, **kw)
231 else:
234 else:
232 # Zero columns specified
235 # Zero columns specified
233 if not len(p) or not isinstance(p[0], compat.string_types):
236 if not len(p) or not isinstance(p[0], compat.string_types):
234 raise ValueError("First argument must be column name")
237 raise ValueError("First argument must be column name")
235 diffs = self.compare_parameters(*p, **kw)
238 diffs = self.compare_parameters(*p, **kw)
236
239
237 self.apply_diffs(diffs)
240 self.apply_diffs(diffs)
238
241
239 def __repr__(self):
242 def __repr__(self):
240 return '<ColumnDelta altermetadata=%r, %s>' % (
243 return '<ColumnDelta altermetadata=%r, %s>' % (
241 self.alter_metadata,
244 self.alter_metadata,
242 super(ColumnDelta, self).__repr__()
245 super(ColumnDelta, self).__repr__()
243 )
246 )
244
247
245 def __getitem__(self, key):
248 def __getitem__(self, key):
246 if key not in self.keys():
249 if key not in self.keys():
247 raise KeyError("No such diff key, available: %s" % self.diffs )
250 raise KeyError("No such diff key, available: %s" % self.diffs )
248 return getattr(self.result_column, key)
251 return getattr(self.result_column, key)
249
252
250 def __setitem__(self, key, value):
253 def __setitem__(self, key, value):
251 if key not in self.keys():
254 if key not in self.keys():
252 raise KeyError("No such diff key, available: %s" % self.diffs )
255 raise KeyError("No such diff key, available: %s" % self.diffs )
253 setattr(self.result_column, key, value)
256 setattr(self.result_column, key, value)
254
257
255 def __delitem__(self, key):
258 def __delitem__(self, key):
256 raise NotImplementedError
259 raise NotImplementedError
257
260
258 def __len__(self):
261 def __len__(self):
259 raise NotImplementedError
262 raise NotImplementedError
260
263
261 def __iter__(self):
264 def __iter__(self):
262 raise NotImplementedError
265 raise NotImplementedError
263
266
264 def keys(self):
267 def keys(self):
265 return self.diffs.keys()
268 return self.diffs.keys()
266
269
267 def compare_parameters(self, current_name, *p, **k):
270 def compare_parameters(self, current_name, *p, **k):
268 """Compares Column objects with reflection"""
271 """Compares Column objects with reflection"""
269 self.table = k.pop('table')
272 self.table = k.pop('table')
270 self.result_column = self._table.c.get(current_name)
273 self.result_column = self._table.c.get(current_name)
271 if len(p):
274 if len(p):
272 k = self._extract_parameters(p, k, self.result_column)
275 k = self._extract_parameters(p, k, self.result_column)
273 return k
276 return k
274
277
275 def compare_1_column(self, col, *p, **k):
278 def compare_1_column(self, col, *p, **k):
276 """Compares one Column object"""
279 """Compares one Column object"""
277 self.table = k.pop('table', None)
280 self.table = k.pop('table', None)
278 if self.table is None:
281 if self.table is None:
279 self.table = col.table
282 self.table = col.table
280 self.result_column = col
283 self.result_column = col
281 if len(p):
284 if len(p):
282 k = self._extract_parameters(p, k, self.result_column)
285 k = self._extract_parameters(p, k, self.result_column)
283 return k
286 return k
284
287
285 def compare_2_columns(self, old_col, new_col, *p, **k):
288 def compare_2_columns(self, old_col, new_col, *p, **k):
286 """Compares two Column objects"""
289 """Compares two Column objects"""
287 self.process_column(new_col)
290 self.process_column(new_col)
288 self.table = k.pop('table', None)
291 self.table = k.pop('table', None)
289 # we cannot use bool() on table in SA06
292 # we cannot use bool() on table in SA06
290 if self.table is None:
293 if self.table is None:
291 self.table = old_col.table
294 self.table = old_col.table
292 if self.table is None:
295 if self.table is None:
293 new_col.table
296 new_col.table
294 self.result_column = old_col
297 self.result_column = old_col
295
298
296 # set differences
299 # set differences
297 # leave out some stuff for later comp
300 # leave out some stuff for later comp
298 for key in (set(self.diff_keys) - set(('type',))):
301 for key in (set(self.diff_keys) - set(('type',))):
299 val = getattr(new_col, key, None)
302 val = getattr(new_col, key, None)
300 if getattr(self.result_column, key, None) != val:
303 if getattr(self.result_column, key, None) != val:
301 k.setdefault(key, val)
304 k.setdefault(key, val)
302
305
303 # inspect types
306 # inspect types
304 if not self.are_column_types_eq(self.result_column.type, new_col.type):
307 if not self.are_column_types_eq(self.result_column.type, new_col.type):
305 k.setdefault('type', new_col.type)
308 k.setdefault('type', new_col.type)
306
309
307 if len(p):
310 if len(p):
308 k = self._extract_parameters(p, k, self.result_column)
311 k = self._extract_parameters(p, k, self.result_column)
309 return k
312 return k
310
313
311 def apply_diffs(self, diffs):
314 def apply_diffs(self, diffs):
312 """Populate dict and column object with new values"""
315 """Populate dict and column object with new values"""
313 self.diffs = diffs
316 self.diffs = diffs
314 for key in self.diff_keys:
317 for key in self.diff_keys:
315 if key in diffs:
318 if key in diffs:
316 setattr(self.result_column, key, diffs[key])
319 setattr(self.result_column, key, diffs[key])
317
320
318 self.process_column(self.result_column)
321 self.process_column(self.result_column)
319
322
320 # create an instance of class type if not yet
323 # create an instance of class type if not yet
321 if 'type' in diffs and callable(self.result_column.type):
324 if 'type' in diffs and callable(self.result_column.type):
322 self.result_column.type = self.result_column.type()
325 self.result_column.type = self.result_column.type()
323
326
324 # add column to the table
327 # add column to the table
325 if self.table is not None and self.alter_metadata:
328 if self.table is not None and self.alter_metadata:
326 self.result_column.add_to_table(self.table)
329 self.result_column.add_to_table(self.table)
327
330
328 def are_column_types_eq(self, old_type, new_type):
331 def are_column_types_eq(self, old_type, new_type):
329 """Compares two types to be equal"""
332 """Compares two types to be equal"""
330 ret = old_type.__class__ == new_type.__class__
333 ret = old_type.__class__ == new_type.__class__
331
334
332 # String length is a special case
335 # String length is a special case
333 if ret and isinstance(new_type, sqlalchemy.types.String):
336 if ret and isinstance(new_type, sqlalchemy.types.String):
334 ret = (getattr(old_type, 'length', None) == \
337 ret = (getattr(old_type, 'length', None) == \
335 getattr(new_type, 'length', None))
338 getattr(new_type, 'length', None))
336 return ret
339 return ret
337
340
338 def _extract_parameters(self, p, k, column):
341 def _extract_parameters(self, p, k, column):
339 """Extracts data from p and modifies diffs"""
342 """Extracts data from p and modifies diffs"""
340 p = list(p)
343 p = list(p)
341 while len(p):
344 while len(p):
342 if isinstance(p[0], compat.string_types):
345 if isinstance(p[0], compat.string_types):
343 k.setdefault('name', p.pop(0))
346 k.setdefault('name', p.pop(0))
344 elif isinstance(p[0], sqlalchemy.types.TypeEngine):
347 elif isinstance(p[0], sqlalchemy.types.TypeEngine):
345 k.setdefault('type', p.pop(0))
348 k.setdefault('type', p.pop(0))
346 elif callable(p[0]):
349 elif callable(p[0]):
347 p[0] = p[0]()
350 p[0] = p[0]()
348 else:
351 else:
349 break
352 break
350
353
351 if len(p):
354 if len(p):
352 new_col = column.copy_fixed()
355 new_col = column.copy_fixed()
353 new_col._init_items(*p)
356 new_col._init_items(*p)
354 k = self.compare_2_columns(column, new_col, **k)
357 k = self.compare_2_columns(column, new_col, **k)
355 return k
358 return k
356
359
357 def process_column(self, column):
360 def process_column(self, column):
358 """Processes default values for column"""
361 """Processes default values for column"""
359 # XXX: this is a snippet from SA processing of positional parameters
362 # XXX: this is a snippet from SA processing of positional parameters
360 toinit = list()
363 toinit = list()
361
364
362 if column.server_default is not None:
365 if column.server_default is not None:
363 if isinstance(column.server_default, sqlalchemy.FetchedValue):
366 if isinstance(column.server_default, sqlalchemy.FetchedValue):
364 toinit.append(column.server_default)
367 toinit.append(column.server_default)
365 else:
368 else:
366 toinit.append(sqlalchemy.DefaultClause(column.server_default))
369 toinit.append(sqlalchemy.DefaultClause(column.server_default))
367 if column.server_onupdate is not None:
370 if column.server_onupdate is not None:
368 if isinstance(column.server_onupdate, FetchedValue):
371 if isinstance(column.server_onupdate, FetchedValue):
369 toinit.append(column.server_default)
372 toinit.append(column.server_default)
370 else:
373 else:
371 toinit.append(sqlalchemy.DefaultClause(column.server_onupdate,
374 toinit.append(sqlalchemy.DefaultClause(column.server_onupdate,
372 for_update=True))
375 for_update=True))
373 if toinit:
376 if toinit:
374 column._init_items(*toinit)
377 column._init_items(*toinit)
375
378
376 def _get_table(self):
379 def _get_table(self):
377 return getattr(self, '_table', None)
380 return getattr(self, '_table', None)
378
381
379 def _set_table(self, table):
382 def _set_table(self, table):
380 if isinstance(table, compat.string_types):
383 if isinstance(table, compat.string_types):
381 if self.alter_metadata:
384 if self.alter_metadata:
382 if not self.meta:
385 if not self.meta:
383 raise ValueError("metadata must be specified for table"
386 raise ValueError("metadata must be specified for table"
384 " reflection when using alter_metadata")
387 " reflection when using alter_metadata")
385 meta = self.meta
388 meta = self.meta
386 if self.engine:
389 if self.engine:
387 meta.bind = self.engine
390 meta.bind = self.engine
388 else:
391 else:
389 if not self.engine and not self.meta:
392 if not self.engine and not self.meta:
390 raise ValueError("engine or metadata must be specified"
393 raise ValueError("engine or metadata must be specified"
391 " to reflect tables")
394 " to reflect tables")
392 if not self.engine:
395 if not self.engine:
393 self.engine = self.meta.bind
396 self.engine = self.meta.bind
394 meta = sqlalchemy.MetaData(bind=self.engine)
397 meta = sqlalchemy.MetaData(bind=self.engine)
395 self._table = sqlalchemy.Table(table, meta, autoload=True)
398 self._table = sqlalchemy.Table(table, meta, autoload=True)
396 elif isinstance(table, sqlalchemy.Table):
399 elif isinstance(table, sqlalchemy.Table):
397 self._table = table
400 self._table = table
398 if not self.alter_metadata:
401 if not self.alter_metadata:
399 self._table.meta = sqlalchemy.MetaData(bind=self._table.bind)
402 self._table.meta = sqlalchemy.MetaData(bind=self._table.bind)
400 def _get_result_column(self):
403 def _get_result_column(self):
401 return getattr(self, '_result_column', None)
404 return getattr(self, '_result_column', None)
402
405
403 def _set_result_column(self, column):
406 def _set_result_column(self, column):
404 """Set Column to Table based on alter_metadata evaluation."""
407 """Set Column to Table based on alter_metadata evaluation."""
405 self.process_column(column)
408 self.process_column(column)
406 if not hasattr(self, 'current_name'):
409 if not hasattr(self, 'current_name'):
407 self.current_name = column.name
410 self.current_name = column.name
408 if self.alter_metadata:
411 if self.alter_metadata:
409 self._result_column = column
412 self._result_column = column
410 else:
413 else:
411 self._result_column = column.copy_fixed()
414 self._result_column = column.copy_fixed()
412
415
413 table = property(_get_table, _set_table)
416 table = property(_get_table, _set_table)
414 result_column = property(_get_result_column, _set_result_column)
417 result_column = property(_get_result_column, _set_result_column)
415
418
416
419
417 class ChangesetTable(object):
420 class ChangesetTable(object):
418 """Changeset extensions to SQLAlchemy tables."""
421 """Changeset extensions to SQLAlchemy tables."""
419
422
420 def create_column(self, column, *p, **kw):
423 def create_column(self, column, *p, **kw):
421 """Creates a column.
424 """Creates a column.
422
425
423 The column parameter may be a column definition or the name of
426 The column parameter may be a column definition or the name of
424 a column in this table.
427 a column in this table.
425
428
426 API to :meth:`ChangesetColumn.create`
429 API to :meth:`ChangesetColumn.create`
427
430
428 :param column: Column to be created
431 :param column: Column to be created
429 :type column: Column instance or string
432 :type column: Column instance or string
430 """
433 """
431 if not isinstance(column, sqlalchemy.Column):
434 if not isinstance(column, sqlalchemy.Column):
432 # It's a column name
435 # It's a column name
433 column = getattr(self.c, str(column))
436 column = getattr(self.c, str(column))
434 column.create(table=self, *p, **kw)
437 column.create(table=self, *p, **kw)
435
438
436 def drop_column(self, column, *p, **kw):
439 def drop_column(self, column, *p, **kw):
437 """Drop a column, given its name or definition.
440 """Drop a column, given its name or definition.
438
441
439 API to :meth:`ChangesetColumn.drop`
442 API to :meth:`ChangesetColumn.drop`
440
443
441 :param column: Column to be droped
444 :param column: Column to be droped
442 :type column: Column instance or string
445 :type column: Column instance or string
443 """
446 """
444 if not isinstance(column, sqlalchemy.Column):
447 if not isinstance(column, sqlalchemy.Column):
445 # It's a column name
448 # It's a column name
446 try:
449 try:
447 column = getattr(self.c, str(column))
450 column = getattr(self.c, str(column))
448 except AttributeError:
451 except AttributeError:
449 # That column isn't part of the table. We don't need
452 # That column isn't part of the table. We don't need
450 # its entire definition to drop the column, just its
453 # its entire definition to drop the column, just its
451 # name, so create a dummy column with the same name.
454 # name, so create a dummy column with the same name.
452 column = sqlalchemy.Column(str(column), sqlalchemy.Integer())
455 column = sqlalchemy.Column(str(column), sqlalchemy.Integer())
453 column.drop(table=self, *p, **kw)
456 column.drop(table=self, *p, **kw)
454
457
455 def rename(self, name, connection=None, **kwargs):
458 def rename(self, name, connection=None, **kwargs):
456 """Rename this table.
459 """Rename this table.
457
460
458 :param name: New name of the table.
461 :param name: New name of the table.
459 :type name: string
462 :type name: string
460 :param connection: reuse connection istead of creating new one.
463 :param connection: reuse connection istead of creating new one.
461 :type connection: :class:`sqlalchemy.engine.base.Connection` instance
464 :type connection: :class:`sqlalchemy.engine.base.Connection` instance
462 """
465 """
463 engine = self.bind
466 engine = self.bind
464 self.new_name = name
467 self.new_name = name
465 visitorcallable = get_engine_visitor(engine, 'schemachanger')
468 visitorcallable = get_engine_visitor(engine, 'schemachanger')
466 run_single_visitor(engine, visitorcallable, self, connection, **kwargs)
469 run_single_visitor(engine, visitorcallable, self, connection, **kwargs)
467
470
468 # Fix metadata registration
471 # Fix metadata registration
469 self.name = name
472 self.name = name
470 self.deregister()
473 self.deregister()
471 self._set_parent(self.metadata)
474 self._set_parent(self.metadata)
472
475
473 def _meta_key(self):
476 def _meta_key(self):
474 """Get the meta key for this table."""
477 """Get the meta key for this table."""
475 return sqlalchemy.schema._get_table_key(self.name, self.schema)
478 return sqlalchemy.schema._get_table_key(self.name, self.schema)
476
479
477 def deregister(self):
480 def deregister(self):
478 """Remove this table from its metadata"""
481 """Remove this table from its metadata"""
479 if SQLA_07:
482 if SQLA_07:
480 self.metadata._remove_table(self.name, self.schema)
483 self.metadata._remove_table(self.name, self.schema)
481 else:
484 else:
482 key = self._meta_key()
485 key = self._meta_key()
483 meta = self.metadata
486 meta = self.metadata
484 if key in meta.tables:
487 if key in meta.tables:
485 del meta.tables[key]
488 del meta.tables[key]
486
489
487
490
488 class ChangesetColumn(object):
491 class ChangesetColumn(object):
489 """Changeset extensions to SQLAlchemy columns."""
492 """Changeset extensions to SQLAlchemy columns."""
490
493
491 def alter(self, *p, **k):
494 def alter(self, *p, **k):
492 """Makes a call to :func:`alter_column` for the column this
495 """Makes a call to :func:`alter_column` for the column this
493 method is called on.
496 method is called on.
494 """
497 """
495 if 'table' not in k:
498 if 'table' not in k:
496 k['table'] = self.table
499 k['table'] = self.table
497 if 'engine' not in k:
500 if 'engine' not in k:
498 k['engine'] = k['table'].bind
501 k['engine'] = k['table'].bind
499 return alter_column(self, *p, **k)
502 return alter_column(self, *p, **k)
500
503
501 def create(self, table=None, index_name=None, unique_name=None,
504 def create(self, table=None, index_name=None, unique_name=None,
502 primary_key_name=None, populate_default=True, connection=None, **kwargs):
505 primary_key_name=None, populate_default=True, connection=None, **kwargs):
503 """Create this column in the database.
506 """Create this column in the database.
504
507
505 Assumes the given table exists. ``ALTER TABLE ADD COLUMN``,
508 Assumes the given table exists. ``ALTER TABLE ADD COLUMN``,
506 for most databases.
509 for most databases.
507
510
508 :param table: Table instance to create on.
511 :param table: Table instance to create on.
509 :param index_name: Creates :class:`ChangesetIndex` on this column.
512 :param index_name: Creates :class:`ChangesetIndex` on this column.
510 :param unique_name: Creates :class:\
513 :param unique_name: Creates :class:\
511 `~migrate.changeset.constraint.UniqueConstraint` on this column.
514 `~migrate.changeset.constraint.UniqueConstraint` on this column.
512 :param primary_key_name: Creates :class:\
515 :param primary_key_name: Creates :class:\
513 `~migrate.changeset.constraint.PrimaryKeyConstraint` on this column.
516 `~migrate.changeset.constraint.PrimaryKeyConstraint` on this column.
514 :param populate_default: If True, created column will be \
517 :param populate_default: If True, created column will be \
515 populated with defaults
518 populated with defaults
516 :param connection: reuse connection istead of creating new one.
519 :param connection: reuse connection istead of creating new one.
517 :type table: Table instance
520 :type table: Table instance
518 :type index_name: string
521 :type index_name: string
519 :type unique_name: string
522 :type unique_name: string
520 :type primary_key_name: string
523 :type primary_key_name: string
521 :type populate_default: bool
524 :type populate_default: bool
522 :type connection: :class:`sqlalchemy.engine.base.Connection` instance
525 :type connection: :class:`sqlalchemy.engine.base.Connection` instance
523
526
524 :returns: self
527 :returns: self
525 """
528 """
526 self.populate_default = populate_default
529 self.populate_default = populate_default
527 self.index_name = index_name
530 self.index_name = index_name
528 self.unique_name = unique_name
531 self.unique_name = unique_name
529 self.primary_key_name = primary_key_name
532 self.primary_key_name = primary_key_name
530 for cons in ('index_name', 'unique_name', 'primary_key_name'):
533 for cons in ('index_name', 'unique_name', 'primary_key_name'):
531 self._check_sanity_constraints(cons)
534 self._check_sanity_constraints(cons)
532
535
533 self.add_to_table(table)
536 self.add_to_table(table)
534 engine = self.table.bind
537 engine = self.table.bind
535 visitorcallable = get_engine_visitor(engine, 'columngenerator')
538 visitorcallable = get_engine_visitor(engine, 'columngenerator')
536 engine._run_visitor(visitorcallable, self, connection, **kwargs)
539 engine._run_visitor(visitorcallable, self, connection, **kwargs)
537
540
538 # TODO: reuse existing connection
541 # TODO: reuse existing connection
539 if self.populate_default and self.default is not None:
542 if self.populate_default and self.default is not None:
540 stmt = table.update().values({self: engine._execute_default(self.default)})
543 stmt = table.update().values({self: engine._execute_default(self.default)})
541 engine.execute(stmt)
544 engine.execute(stmt)
542
545
543 return self
546 return self
544
547
545 def drop(self, table=None, connection=None, **kwargs):
548 def drop(self, table=None, connection=None, **kwargs):
546 """Drop this column from the database, leaving its table intact.
549 """Drop this column from the database, leaving its table intact.
547
550
548 ``ALTER TABLE DROP COLUMN``, for most databases.
551 ``ALTER TABLE DROP COLUMN``, for most databases.
549
552
550 :param connection: reuse connection istead of creating new one.
553 :param connection: reuse connection istead of creating new one.
551 :type connection: :class:`sqlalchemy.engine.base.Connection` instance
554 :type connection: :class:`sqlalchemy.engine.base.Connection` instance
552 """
555 """
553 if table is not None:
556 if table is not None:
554 self.table = table
557 self.table = table
555 engine = self.table.bind
558 engine = self.table.bind
556 visitorcallable = get_engine_visitor(engine, 'columndropper')
559 visitorcallable = get_engine_visitor(engine, 'columndropper')
557 engine._run_visitor(visitorcallable, self, connection, **kwargs)
560 engine._run_visitor(visitorcallable, self, connection, **kwargs)
558 self.remove_from_table(self.table, unset_table=False)
561 self.remove_from_table(self.table, unset_table=False)
559 self.table = None
562 self.table = None
560 return self
563 return self
561
564
562 def add_to_table(self, table):
565 def add_to_table(self, table):
563 if table is not None and self.table is None:
566 if table is not None and self.table is None:
564 if SQLA_07:
567 if SQLA_07:
565 table.append_column(self)
568 table.append_column(self)
566 else:
569 else:
567 self._set_parent(table)
570 self._set_parent(table)
568
571
569 def _col_name_in_constraint(self,cons,name):
572 def _col_name_in_constraint(self,cons,name):
570 return False
573 return False
571
574
572 def remove_from_table(self, table, unset_table=True):
575 def remove_from_table(self, table, unset_table=True):
573 # TODO: remove primary keys, constraints, etc
576 # TODO: remove primary keys, constraints, etc
574 if unset_table:
577 if unset_table:
575 self.table = None
578 self.table = None
576
579
577 to_drop = set()
580 to_drop = set()
578 for index in table.indexes:
581 for index in table.indexes:
579 columns = []
582 columns = []
580 for col in index.columns:
583 for col in index.columns:
581 if col.name!=self.name:
584 if col.name!=self.name:
582 columns.append(col)
585 columns.append(col)
583 if columns:
586 if columns:
584 index.columns = columns
587 index.columns = columns
585 if SQLA_08:
588 if SQLA_08:
586 index.expressions = columns
589 index.expressions = columns
587 else:
590 else:
588 to_drop.add(index)
591 to_drop.add(index)
589 table.indexes = table.indexes - to_drop
592 table.indexes = table.indexes - to_drop
590
593
591 to_drop = set()
594 to_drop = set()
592 for cons in table.constraints:
595 for cons in table.constraints:
593 # TODO: deal with other types of constraint
596 # TODO: deal with other types of constraint
594 if isinstance(cons,(ForeignKeyConstraint,
597 if isinstance(cons,(ForeignKeyConstraint,
595 UniqueConstraint)):
598 UniqueConstraint)):
596 for col_name in cons.columns:
599 for col_name in cons.columns:
597 if not isinstance(col_name, compat.string_types):
600 if not isinstance(col_name, compat.string_types):
598 col_name = col_name.name
601 col_name = col_name.name
599 if self.name==col_name:
602 if self.name==col_name:
600 to_drop.add(cons)
603 to_drop.add(cons)
601 table.constraints = table.constraints - to_drop
604 table.constraints = table.constraints - to_drop
602
605
603 if table.c.contains_column(self):
606 if table.c.contains_column(self):
604 if SQLA_07:
607 if SQLA_07:
605 table._columns.remove(self)
608 table._columns.remove(self)
606 else:
609 else:
607 table.c.remove(self)
610 table.c.remove(self)
608
611
609 # TODO: this is fixed in 0.6
612 # TODO: this is fixed in 0.6
610 def copy_fixed(self, **kw):
613 def copy_fixed(self, **kw):
611 """Create a copy of this ``Column``, with all attributes."""
614 """Create a copy of this ``Column``, with all attributes."""
612 q = util.safe_quote(self)
615 q = util.safe_quote(self)
613 return sqlalchemy.Column(self.name, self.type, self.default,
616 return sqlalchemy.Column(self.name, self.type, self.default,
614 key=self.key,
617 key=self.key,
615 primary_key=self.primary_key,
618 primary_key=self.primary_key,
616 nullable=self.nullable,
619 nullable=self.nullable,
617 quote=q,
620 quote=q,
618 index=self.index,
621 index=self.index,
619 unique=self.unique,
622 unique=self.unique,
620 onupdate=self.onupdate,
623 onupdate=self.onupdate,
621 autoincrement=self.autoincrement,
624 autoincrement=self.autoincrement,
622 server_default=self.server_default,
625 server_default=self.server_default,
623 server_onupdate=self.server_onupdate,
626 server_onupdate=self.server_onupdate,
624 *[c.copy(**kw) for c in self.constraints])
627 *[c.copy(**kw) for c in self.constraints])
625
628
626 def _check_sanity_constraints(self, name):
629 def _check_sanity_constraints(self, name):
627 """Check if constraints names are correct"""
630 """Check if constraints names are correct"""
628 obj = getattr(self, name)
631 obj = getattr(self, name)
629 if (getattr(self, name[:-5]) and not obj):
632 if (getattr(self, name[:-5]) and not obj):
630 raise InvalidConstraintError("Column.create() accepts index_name,"
633 raise InvalidConstraintError("Column.create() accepts index_name,"
631 " primary_key_name and unique_name to generate constraints")
634 " primary_key_name and unique_name to generate constraints")
632 if not isinstance(obj, compat.string_types) and obj is not None:
635 if not isinstance(obj, compat.string_types) and obj is not None:
633 raise InvalidConstraintError(
636 raise InvalidConstraintError(
634 "%s argument for column must be constraint name" % name)
637 "%s argument for column must be constraint name" % name)
635
638
636
639
637 class ChangesetIndex(object):
640 class ChangesetIndex(object):
638 """Changeset extensions to SQLAlchemy Indexes."""
641 """Changeset extensions to SQLAlchemy Indexes."""
639
642
640 __visit_name__ = 'index'
643 __visit_name__ = 'index'
641
644
642 def rename(self, name, connection=None, **kwargs):
645 def rename(self, name, connection=None, **kwargs):
643 """Change the name of an index.
646 """Change the name of an index.
644
647
645 :param name: New name of the Index.
648 :param name: New name of the Index.
646 :type name: string
649 :type name: string
647 :param connection: reuse connection istead of creating new one.
650 :param connection: reuse connection istead of creating new one.
648 :type connection: :class:`sqlalchemy.engine.base.Connection` instance
651 :type connection: :class:`sqlalchemy.engine.base.Connection` instance
649 """
652 """
650 engine = self.table.bind
653 engine = self.table.bind
651 self.new_name = name
654 self.new_name = name
652 visitorcallable = get_engine_visitor(engine, 'schemachanger')
655 visitorcallable = get_engine_visitor(engine, 'schemachanger')
653 engine._run_visitor(visitorcallable, self, connection, **kwargs)
656 engine._run_visitor(visitorcallable, self, connection, **kwargs)
654 self.name = name
657 self.name = name
655
658
656
659
657 class ChangesetDefaultClause(object):
660 class ChangesetDefaultClause(object):
658 """Implements comparison between :class:`DefaultClause` instances"""
661 """Implements comparison between :class:`DefaultClause` instances"""
659
662
660 def __eq__(self, other):
663 def __eq__(self, other):
661 if isinstance(other, self.__class__):
664 if isinstance(other, self.__class__):
662 if self.arg == other.arg:
665 if self.arg == other.arg:
663 return True
666 return True
664
667
665 def __ne__(self, other):
668 def __ne__(self, other):
666 return not self.__eq__(other)
669 return not self.__eq__(other)
@@ -1,10 +1,21 b''
1 """
1 """
2 Safe quoting method
2 Safe quoting method
3 """
3 """
4 from rhodecode.lib.dbmigrate.migrate.changeset import SQLA_10
5
6
7 def fk_column_names(constraint):
8 if SQLA_10:
9 return [
10 constraint.columns[key].name for key in constraint.column_keys]
11 else:
12 return [
13 element.parent.name for element in constraint.elements]
14
4
15
5 def safe_quote(obj):
16 def safe_quote(obj):
6 # this is the SQLA 0.9 approach
17 # this is the SQLA 0.9 approach
7 if hasattr(obj, 'name') and hasattr(obj.name, 'quote'):
18 if hasattr(obj, 'name') and hasattr(obj.name, 'quote'):
8 return obj.name.quote
19 return obj.name.quote
9 else:
20 else:
10 return obj.quote
21 return obj.quote
@@ -1,87 +1,91 b''
1 """
1 """
2 Provide exception classes for :mod:`migrate`
2 Provide exception classes for :mod:`migrate`
3 """
3 """
4
4
5
5
6 class Error(Exception):
6 class Error(Exception):
7 """Error base class."""
7 """Error base class."""
8
8
9
9
10 class ApiError(Error):
10 class ApiError(Error):
11 """Base class for API errors."""
11 """Base class for API errors."""
12
12
13
13
14 class KnownError(ApiError):
14 class KnownError(ApiError):
15 """A known error condition."""
15 """A known error condition."""
16
16
17
17
18 class UsageError(ApiError):
18 class UsageError(ApiError):
19 """A known error condition where help should be displayed."""
19 """A known error condition where help should be displayed."""
20
20
21
21
22 class ControlledSchemaError(Error):
22 class ControlledSchemaError(Error):
23 """Base class for controlled schema errors."""
23 """Base class for controlled schema errors."""
24
24
25
25
26 class InvalidVersionError(ControlledSchemaError):
26 class InvalidVersionError(ControlledSchemaError):
27 """Invalid version number."""
27 """Invalid version number."""
28
28
29
29
30 class VersionNotFoundError(KeyError):
31 """Specified version is not present."""
32
33
30 class DatabaseNotControlledError(ControlledSchemaError):
34 class DatabaseNotControlledError(ControlledSchemaError):
31 """Database should be under version control, but it's not."""
35 """Database should be under version control, but it's not."""
32
36
33
37
34 class DatabaseAlreadyControlledError(ControlledSchemaError):
38 class DatabaseAlreadyControlledError(ControlledSchemaError):
35 """Database shouldn't be under version control, but it is"""
39 """Database shouldn't be under version control, but it is"""
36
40
37
41
38 class WrongRepositoryError(ControlledSchemaError):
42 class WrongRepositoryError(ControlledSchemaError):
39 """This database is under version control by another repository."""
43 """This database is under version control by another repository."""
40
44
41
45
42 class NoSuchTableError(ControlledSchemaError):
46 class NoSuchTableError(ControlledSchemaError):
43 """The table does not exist."""
47 """The table does not exist."""
44
48
45
49
46 class PathError(Error):
50 class PathError(Error):
47 """Base class for path errors."""
51 """Base class for path errors."""
48
52
49
53
50 class PathNotFoundError(PathError):
54 class PathNotFoundError(PathError):
51 """A path with no file was required; found a file."""
55 """A path with no file was required; found a file."""
52
56
53
57
54 class PathFoundError(PathError):
58 class PathFoundError(PathError):
55 """A path with a file was required; found no file."""
59 """A path with a file was required; found no file."""
56
60
57
61
58 class RepositoryError(Error):
62 class RepositoryError(Error):
59 """Base class for repository errors."""
63 """Base class for repository errors."""
60
64
61
65
62 class InvalidRepositoryError(RepositoryError):
66 class InvalidRepositoryError(RepositoryError):
63 """Invalid repository error."""
67 """Invalid repository error."""
64
68
65
69
66 class ScriptError(Error):
70 class ScriptError(Error):
67 """Base class for script errors."""
71 """Base class for script errors."""
68
72
69
73
70 class InvalidScriptError(ScriptError):
74 class InvalidScriptError(ScriptError):
71 """Invalid script error."""
75 """Invalid script error."""
72
76
73
77
74 class InvalidVersionError(Error):
78 class InvalidVersionError(Error):
75 """Invalid version error."""
79 """Invalid version error."""
76
80
77 # migrate.changeset
81 # migrate.changeset
78
82
79 class NotSupportedError(Error):
83 class NotSupportedError(Error):
80 """Not supported error"""
84 """Not supported error"""
81
85
82
86
83 class InvalidConstraintError(Error):
87 class InvalidConstraintError(Error):
84 """Invalid constraint error"""
88 """Invalid constraint error"""
85
89
86 class MigrateDeprecationWarning(DeprecationWarning):
90 class MigrateDeprecationWarning(DeprecationWarning):
87 """Warning for deprecated features in Migrate"""
91 """Warning for deprecated features in Migrate"""
General Comments 0
You need to be logged in to leave comments. Login now