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