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