Show More
@@ -30,8 +30,24 b' class SQLiteCommon(object):' | |||||
30 |
|
30 | |||
31 | class SQLiteHelper(SQLiteCommon): |
|
31 | class SQLiteHelper(SQLiteCommon): | |
32 |
|
32 | |||
33 |
def _ |
|
33 | def _filter_columns(self, cols, table): | |
34 | """Retrieve information about existing unique constraints of the table |
|
34 | """Splits the string of columns and returns those only in the table. | |
|
35 | ||||
|
36 | :param cols: comma-delimited string of table columns | |||
|
37 | :param table: the table to check | |||
|
38 | :return: list of columns in the table | |||
|
39 | """ | |||
|
40 | columns = [] | |||
|
41 | for c in cols.split(","): | |||
|
42 | if c in table.columns: | |||
|
43 | # There was a bug in reflection of SQLite columns with | |||
|
44 | # reserved identifiers as names (SQLite can return them | |||
|
45 | # wrapped with double quotes), so strip double quotes. | |||
|
46 | columns.extend(c.strip(' "')) | |||
|
47 | return columns | |||
|
48 | ||||
|
49 | def _get_constraints(self, table): | |||
|
50 | """Retrieve information about existing constraints of the table | |||
35 |
|
51 | |||
36 | This feature is needed for recreate_table() to work properly. |
|
52 | This feature is needed for recreate_table() to work properly. | |
37 | """ |
|
53 | """ | |
@@ -49,19 +65,21 b' class SQLiteHelper(SQLiteCommon):' | |||||
49 | constraints = [] |
|
65 | constraints = [] | |
50 | for name, cols in re.findall(UNIQUE_PATTERN, data): |
|
66 | for name, cols in re.findall(UNIQUE_PATTERN, data): | |
51 | # Filter out any columns that were dropped from the table. |
|
67 | # Filter out any columns that were dropped from the table. | |
52 | columns = [] |
|
68 | columns = self._filter_columns(cols, table) | |
53 | for c in cols.split(","): |
|
|||
54 | if c in table.columns: |
|
|||
55 | # There was a bug in reflection of SQLite columns with |
|
|||
56 | # reserved identifiers as names (SQLite can return them |
|
|||
57 | # wrapped with double quotes), so strip double quotes. |
|
|||
58 | columns.extend(c.strip(' "')) |
|
|||
59 | if columns: |
|
69 | if columns: | |
60 | constraints.extend(UniqueConstraint(*columns, name=name)) |
|
70 | constraints.extend(UniqueConstraint(*columns, name=name)) | |
|
71 | ||||
|
72 | FKEY_PATTERN = "CONSTRAINT (\w+) FOREIGN KEY \(([^\)]+)\)" | |||
|
73 | for name, cols in re.findall(FKEY_PATTERN, data): | |||
|
74 | # Filter out any columns that were dropped from the table. | |||
|
75 | columns = self._filter_columns(cols, table) | |||
|
76 | if columns: | |||
|
77 | constraints.extend(ForeignKeyConstraint(*columns, name=name)) | |||
|
78 | ||||
61 | return constraints |
|
79 | return constraints | |
62 |
|
80 | |||
63 | def recreate_table(self, table, column=None, delta=None, |
|
81 | def recreate_table(self, table, column=None, delta=None, | |
64 |
omit_ |
|
82 | omit_constraints=None): | |
65 | table_name = self.preparer.format_table(table) |
|
83 | table_name = self.preparer.format_table(table) | |
66 |
|
84 | |||
67 | # we remove all indexes so as not to have |
|
85 | # we remove all indexes so as not to have | |
@@ -69,24 +87,27 b' class SQLiteHelper(SQLiteCommon):' | |||||
69 | for index in table.indexes: |
|
87 | for index in table.indexes: | |
70 | index.drop() |
|
88 | index.drop() | |
71 |
|
89 | |||
72 |
# reflect existing |
|
90 | # reflect existing constraints | |
73 |
for |
|
91 | for constraint in self._get_constraints(table): | |
74 |
table.append_constraint( |
|
92 | table.append_constraint(constraint) | |
75 |
# omit given |
|
93 | # omit given constraints when creating a new table if required | |
76 | table.constraints = set([ |
|
94 | table.constraints = set([ | |
77 | cons for cons in table.constraints |
|
95 | cons for cons in table.constraints | |
78 |
if omit_ |
|
96 | if omit_constraints is None or cons.name not in omit_constraints | |
79 | ]) |
|
97 | ]) | |
80 | tup = sqlite3.sqlite_version_info |
|
98 | ||
81 | if tup[0] > 3 or (tup[0] == 3 and tup[1] >= 26): |
|
99 | # Use "PRAGMA legacy_alter_table = ON" with sqlite >= 3.26 when | |
|
100 | # using "ALTER TABLE RENAME TO migration_tmp" to maintain legacy | |||
|
101 | # behavior. See: https://www.sqlite.org/src/info/ae9638e9c0ad0c36 | |||
|
102 | if self.connection.engine.dialect.server_version_info >= (3, 26): | |||
82 | self.append('PRAGMA legacy_alter_table = ON') |
|
103 | self.append('PRAGMA legacy_alter_table = ON') | |
83 | self.execute() |
|
104 | self.execute() | |
84 |
|
||||
85 | self.append('ALTER TABLE %s RENAME TO migration_tmp' % table_name) |
|
105 | self.append('ALTER TABLE %s RENAME TO migration_tmp' % table_name) | |
86 | self.execute() |
|
106 | self.execute() | |
87 | if tup[0] > 3 or (tup[0] == 3 and tup[1] >= 26): |
|
107 | if self.connection.engine.dialect.server_version_info >= (3, 26): | |
88 | self.append('PRAGMA legacy_alter_table = OFF') |
|
108 | self.append('PRAGMA legacy_alter_table = OFF') | |
89 | self.execute() |
|
109 | self.execute() | |
|
110 | ||||
90 | insertion_string = self._modify_table(table, column, delta) |
|
111 | insertion_string = self._modify_table(table, column, delta) | |
91 |
|
112 | |||
92 | table.create(bind=self.connection) |
|
113 | table.create(bind=self.connection) | |
@@ -102,7 +123,6 b' class SQLiteHelper(SQLiteCommon):' | |||||
102 | else: |
|
123 | else: | |
103 | column = delta |
|
124 | column = delta | |
104 | table = self._to_table(column.table) |
|
125 | table = self._to_table(column.table) | |
105 |
|
||||
106 | self.recreate_table(table,column,delta) |
|
126 | self.recreate_table(table,column,delta) | |
107 |
|
127 | |||
108 | class SQLiteColumnGenerator(SQLiteSchemaGenerator, |
|
128 | class SQLiteColumnGenerator(SQLiteSchemaGenerator, | |
@@ -190,13 +210,14 b' class SQLiteConstraintDropper(ansisql.AN' | |||||
190 | self.execute() |
|
210 | self.execute() | |
191 |
|
211 | |||
192 | def visit_migrate_foreign_key_constraint(self, *p, **k): |
|
212 | def visit_migrate_foreign_key_constraint(self, *p, **k): | |
193 | self._not_supported('ALTER TABLE DROP CONSTRAINT') |
|
213 | #self._not_supported('ALTER TABLE DROP CONSTRAINT') | |
|
214 | self.recreate_table(p[0].table, omit_constraints=[p[0].name]) | |||
194 |
|
215 | |||
195 | def visit_migrate_check_constraint(self, *p, **k): |
|
216 | def visit_migrate_check_constraint(self, *p, **k): | |
196 | self._not_supported('ALTER TABLE DROP CONSTRAINT') |
|
217 | self._not_supported('ALTER TABLE DROP CONSTRAINT') | |
197 |
|
218 | |||
198 | def visit_migrate_unique_constraint(self, *p, **k): |
|
219 | def visit_migrate_unique_constraint(self, *p, **k): | |
199 |
self.recreate_table(p[0].table, omit_ |
|
220 | self.recreate_table(p[0].table, omit_constraints=[p[0].name]) | |
200 |
|
221 | |||
201 |
|
222 | |||
202 | # TODO: technically primary key is a NOT NULL + UNIQUE constraint, should add NOT NULL to index |
|
223 | # TODO: technically primary key is a NOT NULL + UNIQUE constraint, should add NOT NULL to index |
@@ -367,8 +367,14 b' class ColumnDelta(DictMixin, sqlalchemy.' | |||||
367 | self.process_column(self.result_column) |
|
367 | self.process_column(self.result_column) | |
368 |
|
368 | |||
369 | # create an instance of class type if not yet |
|
369 | # create an instance of class type if not yet | |
370 | if 'type' in diffs and callable(self.result_column.type): |
|
370 | if 'type' in diffs: | |
|
371 | if callable(self.result_column.type): | |||
371 | self.result_column.type = self.result_column.type() |
|
372 | self.result_column.type = self.result_column.type() | |
|
373 | if self.result_column.autoincrement and \ | |||
|
374 | not issubclass( | |||
|
375 | self.result_column.type._type_affinity, | |||
|
376 | sqlalchemy.Integer): | |||
|
377 | self.result_column.autoincrement = False | |||
372 |
|
378 | |||
373 | # add column to the table |
|
379 | # add column to the table | |
374 | if self.table is not None and self.alter_metadata: |
|
380 | if self.table is not None and self.alter_metadata: |
General Comments 0
You need to be logged in to leave comments.
Login now