##// END OF EJS Templates
migrations: fixed db migrate for latest sqlite version.
marcink -
r4342:cf6cf2b9 default
parent child Browse files
Show More
@@ -1,199 +1,205 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 from UserDict import DictMixin
7 from copy import copy
7 from copy import copy
8 import re
8 import re
9
9
10 from sqlalchemy.databases import sqlite as sa_base
10 from sqlalchemy.databases import sqlite as sa_base
11 from sqlalchemy.schema import UniqueConstraint
11 from sqlalchemy.schema import UniqueConstraint
12
12
13 from rhodecode.lib.dbmigrate.migrate import exceptions
13 from rhodecode.lib.dbmigrate.migrate import exceptions
14 from rhodecode.lib.dbmigrate.migrate.changeset import ansisql
14 from rhodecode.lib.dbmigrate.migrate.changeset import ansisql
15
15 import sqlite3
16
16
17 SQLiteSchemaGenerator = sa_base.SQLiteDDLCompiler
17 SQLiteSchemaGenerator = sa_base.SQLiteDDLCompiler
18
18
19
19
20 class SQLiteCommon(object):
20 class SQLiteCommon(object):
21
21
22 def _not_supported(self, op):
22 def _not_supported(self, op):
23 raise exceptions.NotSupportedError("SQLite does not support "
23 raise exceptions.NotSupportedError("SQLite does not support "
24 "%s; see http://www.sqlite.org/lang_altertable.html" % op)
24 "%s; see http://www.sqlite.org/lang_altertable.html" % op)
25
25
26
26
27 class SQLiteHelper(SQLiteCommon):
27 class SQLiteHelper(SQLiteCommon):
28
28
29 def _get_unique_constraints(self, table):
29 def _get_unique_constraints(self, table):
30 """Retrieve information about existing unique constraints of the table
30 """Retrieve information about existing unique constraints of the table
31
31
32 This feature is needed for recreate_table() to work properly.
32 This feature is needed for recreate_table() to work properly.
33 """
33 """
34
34
35 data = table.metadata.bind.execute(
35 data = table.metadata.bind.execute(
36 """SELECT sql
36 """SELECT sql
37 FROM sqlite_master
37 FROM sqlite_master
38 WHERE
38 WHERE
39 type='table' AND
39 type='table' AND
40 name=:table_name""",
40 name=:table_name""",
41 table_name=table.name
41 table_name=table.name
42 ).fetchone()[0]
42 ).fetchone()[0]
43
43
44 UNIQUE_PATTERN = "CONSTRAINT (\w+) UNIQUE \(([^\)]+)\)"
44 UNIQUE_PATTERN = "CONSTRAINT (\w+) UNIQUE \(([^\)]+)\)"
45 constraints = []
45 constraints = []
46 for name, cols in re.findall(UNIQUE_PATTERN, data):
46 for name, cols in re.findall(UNIQUE_PATTERN, data):
47 # Filter out any columns that were dropped from the table.
47 # Filter out any columns that were dropped from the table.
48 columns = []
48 columns = []
49 for c in cols.split(","):
49 for c in cols.split(","):
50 if c in table.columns:
50 if c in table.columns:
51 # There was a bug in reflection of SQLite columns with
51 # There was a bug in reflection of SQLite columns with
52 # reserved identifiers as names (SQLite can return them
52 # reserved identifiers as names (SQLite can return them
53 # wrapped with double quotes), so strip double quotes.
53 # wrapped with double quotes), so strip double quotes.
54 columns.extend(c.strip(' "'))
54 columns.extend(c.strip(' "'))
55 if columns:
55 if columns:
56 constraints.extend(UniqueConstraint(*columns, name=name))
56 constraints.extend(UniqueConstraint(*columns, name=name))
57 return constraints
57 return constraints
58
58
59 def recreate_table(self, table, column=None, delta=None,
59 def recreate_table(self, table, column=None, delta=None,
60 omit_uniques=None):
60 omit_uniques=None):
61 table_name = self.preparer.format_table(table)
61 table_name = self.preparer.format_table(table)
62
62
63 # we remove all indexes so as not to have
63 # we remove all indexes so as not to have
64 # problems during copy and re-create
64 # problems during copy and re-create
65 for index in table.indexes:
65 for index in table.indexes:
66 index.drop()
66 index.drop()
67
67
68 # reflect existing unique constraints
68 # reflect existing unique constraints
69 for uc in self._get_unique_constraints(table):
69 for uc in self._get_unique_constraints(table):
70 table.append_constraint(uc)
70 table.append_constraint(uc)
71 # omit given unique constraints when creating a new table if required
71 # omit given unique constraints when creating a new table if required
72 table.constraints = set([
72 table.constraints = set([
73 cons for cons in table.constraints
73 cons for cons in table.constraints
74 if omit_uniques is None or cons.name not in omit_uniques
74 if omit_uniques is None or cons.name not in omit_uniques
75 ])
75 ])
76 tup = sqlite3.sqlite_version_info
77 if tup[0] > 3 or (tup[0] == 3 and tup[1] >= 26):
78 self.append('PRAGMA legacy_alter_table = ON')
79 self.execute()
76
80
77 self.append('ALTER TABLE %s RENAME TO migration_tmp' % table_name)
81 self.append('ALTER TABLE %s RENAME TO migration_tmp' % table_name)
78 self.execute()
82 self.execute()
79
83 if tup[0] > 3 or (tup[0] == 3 and tup[1] >= 26):
84 self.append('PRAGMA legacy_alter_table = OFF')
85 self.execute()
80 insertion_string = self._modify_table(table, column, delta)
86 insertion_string = self._modify_table(table, column, delta)
81
87
82 table.create(bind=self.connection)
88 table.create(bind=self.connection)
83 self.append(insertion_string % {'table_name': table_name})
89 self.append(insertion_string % {'table_name': table_name})
84 self.execute()
90 self.execute()
85 self.append('DROP TABLE migration_tmp')
91 self.append('DROP TABLE migration_tmp')
86 self.execute()
92 self.execute()
87
93
88 def visit_column(self, delta):
94 def visit_column(self, delta):
89 if isinstance(delta, DictMixin):
95 if isinstance(delta, DictMixin):
90 column = delta.result_column
96 column = delta.result_column
91 table = self._to_table(delta.table)
97 table = self._to_table(delta.table)
92 else:
98 else:
93 column = delta
99 column = delta
94 table = self._to_table(column.table)
100 table = self._to_table(column.table)
95
101
96 self.recreate_table(table,column,delta)
102 self.recreate_table(table,column,delta)
97
103
98 class SQLiteColumnGenerator(SQLiteSchemaGenerator,
104 class SQLiteColumnGenerator(SQLiteSchemaGenerator,
99 ansisql.ANSIColumnGenerator,
105 ansisql.ANSIColumnGenerator,
100 # at the end so we get the normal
106 # at the end so we get the normal
101 # visit_column by default
107 # visit_column by default
102 SQLiteHelper,
108 SQLiteHelper,
103 SQLiteCommon
109 SQLiteCommon
104 ):
110 ):
105 """SQLite ColumnGenerator"""
111 """SQLite ColumnGenerator"""
106
112
107 def _modify_table(self, table, column, delta):
113 def _modify_table(self, table, column, delta):
108 columns = ' ,'.join(map(
114 columns = ' ,'.join(map(
109 self.preparer.format_column,
115 self.preparer.format_column,
110 [c for c in table.columns if c.name!=column.name]))
116 [c for c in table.columns if c.name!=column.name]))
111 return ('INSERT INTO %%(table_name)s (%(cols)s) '
117 return ('INSERT INTO %%(table_name)s (%(cols)s) '
112 'SELECT %(cols)s from migration_tmp')%{'cols':columns}
118 'SELECT %(cols)s from migration_tmp')%{'cols':columns}
113
119
114 def visit_column(self,column):
120 def visit_column(self,column):
115 if column.foreign_keys:
121 if column.foreign_keys:
116 SQLiteHelper.visit_column(self,column)
122 SQLiteHelper.visit_column(self,column)
117 else:
123 else:
118 super(SQLiteColumnGenerator,self).visit_column(column)
124 super(SQLiteColumnGenerator,self).visit_column(column)
119
125
120 class SQLiteColumnDropper(SQLiteHelper, ansisql.ANSIColumnDropper):
126 class SQLiteColumnDropper(SQLiteHelper, ansisql.ANSIColumnDropper):
121 """SQLite ColumnDropper"""
127 """SQLite ColumnDropper"""
122
128
123 def _modify_table(self, table, column, delta):
129 def _modify_table(self, table, column, delta):
124
130
125 columns = ' ,'.join(map(self.preparer.format_column, table.columns))
131 columns = ' ,'.join(map(self.preparer.format_column, table.columns))
126 return 'INSERT INTO %(table_name)s SELECT ' + columns + \
132 return 'INSERT INTO %(table_name)s SELECT ' + columns + \
127 ' from migration_tmp'
133 ' from migration_tmp'
128
134
129 def visit_column(self,column):
135 def visit_column(self,column):
130 # For SQLite, we *have* to remove the column here so the table
136 # For SQLite, we *have* to remove the column here so the table
131 # is re-created properly.
137 # is re-created properly.
132 column.remove_from_table(column.table,unset_table=False)
138 column.remove_from_table(column.table,unset_table=False)
133 super(SQLiteColumnDropper,self).visit_column(column)
139 super(SQLiteColumnDropper,self).visit_column(column)
134
140
135
141
136 class SQLiteSchemaChanger(SQLiteHelper, ansisql.ANSISchemaChanger):
142 class SQLiteSchemaChanger(SQLiteHelper, ansisql.ANSISchemaChanger):
137 """SQLite SchemaChanger"""
143 """SQLite SchemaChanger"""
138
144
139 def _modify_table(self, table, column, delta):
145 def _modify_table(self, table, column, delta):
140 return 'INSERT INTO %(table_name)s SELECT * from migration_tmp'
146 return 'INSERT INTO %(table_name)s SELECT * from migration_tmp'
141
147
142 def visit_index(self, index):
148 def visit_index(self, index):
143 """Does not support ALTER INDEX"""
149 """Does not support ALTER INDEX"""
144 self._not_supported('ALTER INDEX')
150 self._not_supported('ALTER INDEX')
145
151
146
152
147 class SQLiteConstraintGenerator(ansisql.ANSIConstraintGenerator, SQLiteHelper, SQLiteCommon):
153 class SQLiteConstraintGenerator(ansisql.ANSIConstraintGenerator, SQLiteHelper, SQLiteCommon):
148
154
149 def visit_migrate_primary_key_constraint(self, constraint):
155 def visit_migrate_primary_key_constraint(self, constraint):
150 tmpl = "CREATE UNIQUE INDEX %s ON %s ( %s )"
156 tmpl = "CREATE UNIQUE INDEX %s ON %s ( %s )"
151 cols = ', '.join(map(self.preparer.format_column, constraint.columns))
157 cols = ', '.join(map(self.preparer.format_column, constraint.columns))
152 tname = self.preparer.format_table(constraint.table)
158 tname = self.preparer.format_table(constraint.table)
153 name = self.get_constraint_name(constraint)
159 name = self.get_constraint_name(constraint)
154 msg = tmpl % (name, tname, cols)
160 msg = tmpl % (name, tname, cols)
155 self.append(msg)
161 self.append(msg)
156 self.execute()
162 self.execute()
157
163
158 def _modify_table(self, table, column, delta):
164 def _modify_table(self, table, column, delta):
159 return 'INSERT INTO %(table_name)s SELECT * from migration_tmp'
165 return 'INSERT INTO %(table_name)s SELECT * from migration_tmp'
160
166
161 def visit_migrate_foreign_key_constraint(self, *p, **k):
167 def visit_migrate_foreign_key_constraint(self, *p, **k):
162 self.recreate_table(p[0].table)
168 self.recreate_table(p[0].table)
163
169
164 def visit_migrate_unique_constraint(self, *p, **k):
170 def visit_migrate_unique_constraint(self, *p, **k):
165 self.recreate_table(p[0].table)
171 self.recreate_table(p[0].table)
166
172
167
173
168 class SQLiteConstraintDropper(ansisql.ANSIColumnDropper,
174 class SQLiteConstraintDropper(ansisql.ANSIColumnDropper,
169 SQLiteHelper,
175 SQLiteHelper,
170 ansisql.ANSIConstraintCommon):
176 ansisql.ANSIConstraintCommon):
171
177
172 def _modify_table(self, table, column, delta):
178 def _modify_table(self, table, column, delta):
173 return 'INSERT INTO %(table_name)s SELECT * from migration_tmp'
179 return 'INSERT INTO %(table_name)s SELECT * from migration_tmp'
174
180
175 def visit_migrate_primary_key_constraint(self, constraint):
181 def visit_migrate_primary_key_constraint(self, constraint):
176 tmpl = "DROP INDEX %s "
182 tmpl = "DROP INDEX %s "
177 name = self.get_constraint_name(constraint)
183 name = self.get_constraint_name(constraint)
178 msg = tmpl % (name)
184 msg = tmpl % (name)
179 self.append(msg)
185 self.append(msg)
180 self.execute()
186 self.execute()
181
187
182 def visit_migrate_foreign_key_constraint(self, *p, **k):
188 def visit_migrate_foreign_key_constraint(self, *p, **k):
183 self._not_supported('ALTER TABLE DROP CONSTRAINT')
189 self._not_supported('ALTER TABLE DROP CONSTRAINT')
184
190
185 def visit_migrate_check_constraint(self, *p, **k):
191 def visit_migrate_check_constraint(self, *p, **k):
186 self._not_supported('ALTER TABLE DROP CONSTRAINT')
192 self._not_supported('ALTER TABLE DROP CONSTRAINT')
187
193
188 def visit_migrate_unique_constraint(self, *p, **k):
194 def visit_migrate_unique_constraint(self, *p, **k):
189 self.recreate_table(p[0].table, omit_uniques=[p[0].name])
195 self.recreate_table(p[0].table, omit_uniques=[p[0].name])
190
196
191
197
192 # TODO: technically primary key is a NOT NULL + UNIQUE constraint, should add NOT NULL to index
198 # TODO: technically primary key is a NOT NULL + UNIQUE constraint, should add NOT NULL to index
193
199
194 class SQLiteDialect(ansisql.ANSIDialect):
200 class SQLiteDialect(ansisql.ANSIDialect):
195 columngenerator = SQLiteColumnGenerator
201 columngenerator = SQLiteColumnGenerator
196 columndropper = SQLiteColumnDropper
202 columndropper = SQLiteColumnDropper
197 schemachanger = SQLiteSchemaChanger
203 schemachanger = SQLiteSchemaChanger
198 constraintgenerator = SQLiteConstraintGenerator
204 constraintgenerator = SQLiteConstraintGenerator
199 constraintdropper = SQLiteConstraintDropper
205 constraintdropper = SQLiteConstraintDropper
General Comments 0
You need to be logged in to leave comments. Login now