|
0
|
1 |
import sys |
|
|
2 |
import time |
|
|
3 |
try: |
|
|
4 |
set |
|
|
5 |
except NameError: |
|
|
6 |
# Python 2.3 compat |
|
|
7 |
from sets import Set as set |
|
|
8 |
|
|
|
9 |
from django.conf import settings |
|
|
10 |
from django.core.management import call_command |
|
|
11 |
|
|
|
12 |
# The prefix to put on the default database name when creating |
|
|
13 |
# the test database. |
|
|
14 |
TEST_DATABASE_PREFIX = 'test_' |
|
|
15 |
|
|
|
16 |
class BaseDatabaseCreation(object): |
|
|
17 |
""" |
|
|
18 |
This class encapsulates all backend-specific differences that pertain to |
|
|
19 |
database *creation*, such as the column types to use for particular Django |
|
|
20 |
Fields, the SQL used to create and destroy tables, and the creation and |
|
|
21 |
destruction of test databases. |
|
|
22 |
""" |
|
|
23 |
data_types = {} |
|
|
24 |
|
|
|
25 |
def __init__(self, connection): |
|
|
26 |
self.connection = connection |
|
|
27 |
|
|
|
28 |
def _digest(self, *args): |
|
|
29 |
""" |
|
|
30 |
Generates a 32-bit digest of a set of arguments that can be used to |
|
|
31 |
shorten identifying names. |
|
|
32 |
""" |
|
|
33 |
return '%x' % (abs(hash(args)) % 4294967296L) # 2**32 |
|
|
34 |
|
|
|
35 |
def sql_create_model(self, model, style, known_models=set()): |
|
|
36 |
""" |
|
|
37 |
Returns the SQL required to create a single model, as a tuple of: |
|
|
38 |
(list_of_sql, pending_references_dict) |
|
|
39 |
""" |
|
|
40 |
from django.db import models |
|
|
41 |
|
|
|
42 |
opts = model._meta |
|
|
43 |
if not opts.managed or opts.proxy: |
|
|
44 |
return [], {} |
|
|
45 |
final_output = [] |
|
|
46 |
table_output = [] |
|
|
47 |
pending_references = {} |
|
|
48 |
qn = self.connection.ops.quote_name |
|
|
49 |
for f in opts.local_fields: |
|
|
50 |
col_type = f.db_type() |
|
|
51 |
tablespace = f.db_tablespace or opts.db_tablespace |
|
|
52 |
if col_type is None: |
|
|
53 |
# Skip ManyToManyFields, because they're not represented as |
|
|
54 |
# database columns in this table. |
|
|
55 |
continue |
|
|
56 |
# Make the definition (e.g. 'foo VARCHAR(30)') for this field. |
|
|
57 |
field_output = [style.SQL_FIELD(qn(f.column)), |
|
|
58 |
style.SQL_COLTYPE(col_type)] |
|
|
59 |
if not f.null: |
|
|
60 |
field_output.append(style.SQL_KEYWORD('NOT NULL')) |
|
|
61 |
if f.primary_key: |
|
|
62 |
field_output.append(style.SQL_KEYWORD('PRIMARY KEY')) |
|
|
63 |
elif f.unique: |
|
|
64 |
field_output.append(style.SQL_KEYWORD('UNIQUE')) |
|
|
65 |
if tablespace and f.unique: |
|
|
66 |
# We must specify the index tablespace inline, because we |
|
|
67 |
# won't be generating a CREATE INDEX statement for this field. |
|
|
68 |
field_output.append(self.connection.ops.tablespace_sql(tablespace, inline=True)) |
|
|
69 |
if f.rel: |
|
|
70 |
ref_output, pending = self.sql_for_inline_foreign_key_references(f, known_models, style) |
|
|
71 |
if pending: |
|
|
72 |
pr = pending_references.setdefault(f.rel.to, []).append((model, f)) |
|
|
73 |
else: |
|
|
74 |
field_output.extend(ref_output) |
|
|
75 |
table_output.append(' '.join(field_output)) |
|
|
76 |
if opts.order_with_respect_to: |
|
|
77 |
table_output.append(style.SQL_FIELD(qn('_order')) + ' ' + \ |
|
|
78 |
style.SQL_COLTYPE(models.IntegerField().db_type())) |
|
|
79 |
for field_constraints in opts.unique_together: |
|
|
80 |
table_output.append(style.SQL_KEYWORD('UNIQUE') + ' (%s)' % \ |
|
|
81 |
", ".join([style.SQL_FIELD(qn(opts.get_field(f).column)) for f in field_constraints])) |
|
|
82 |
|
|
|
83 |
full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(qn(opts.db_table)) + ' ('] |
|
|
84 |
for i, line in enumerate(table_output): # Combine and add commas. |
|
|
85 |
full_statement.append(' %s%s' % (line, i < len(table_output)-1 and ',' or '')) |
|
|
86 |
full_statement.append(')') |
|
|
87 |
if opts.db_tablespace: |
|
|
88 |
full_statement.append(self.connection.ops.tablespace_sql(opts.db_tablespace)) |
|
|
89 |
full_statement.append(';') |
|
|
90 |
final_output.append('\n'.join(full_statement)) |
|
|
91 |
|
|
|
92 |
if opts.has_auto_field: |
|
|
93 |
# Add any extra SQL needed to support auto-incrementing primary keys. |
|
|
94 |
auto_column = opts.auto_field.db_column or opts.auto_field.name |
|
|
95 |
autoinc_sql = self.connection.ops.autoinc_sql(opts.db_table, auto_column) |
|
|
96 |
if autoinc_sql: |
|
|
97 |
for stmt in autoinc_sql: |
|
|
98 |
final_output.append(stmt) |
|
|
99 |
|
|
|
100 |
return final_output, pending_references |
|
|
101 |
|
|
|
102 |
def sql_for_inline_foreign_key_references(self, field, known_models, style): |
|
|
103 |
"Return the SQL snippet defining the foreign key reference for a field" |
|
|
104 |
qn = self.connection.ops.quote_name |
|
|
105 |
if field.rel.to in known_models: |
|
|
106 |
output = [style.SQL_KEYWORD('REFERENCES') + ' ' + \ |
|
|
107 |
style.SQL_TABLE(qn(field.rel.to._meta.db_table)) + ' (' + \ |
|
|
108 |
style.SQL_FIELD(qn(field.rel.to._meta.get_field(field.rel.field_name).column)) + ')' + |
|
|
109 |
self.connection.ops.deferrable_sql() |
|
|
110 |
] |
|
|
111 |
pending = False |
|
|
112 |
else: |
|
|
113 |
# We haven't yet created the table to which this field |
|
|
114 |
# is related, so save it for later. |
|
|
115 |
output = [] |
|
|
116 |
pending = True |
|
|
117 |
|
|
|
118 |
return output, pending |
|
|
119 |
|
|
|
120 |
def sql_for_pending_references(self, model, style, pending_references): |
|
|
121 |
"Returns any ALTER TABLE statements to add constraints after the fact." |
|
|
122 |
from django.db.backends.util import truncate_name |
|
|
123 |
|
|
|
124 |
if not model._meta.managed or model._meta.proxy: |
|
|
125 |
return [] |
|
|
126 |
qn = self.connection.ops.quote_name |
|
|
127 |
final_output = [] |
|
|
128 |
opts = model._meta |
|
|
129 |
if model in pending_references: |
|
|
130 |
for rel_class, f in pending_references[model]: |
|
|
131 |
rel_opts = rel_class._meta |
|
|
132 |
r_table = rel_opts.db_table |
|
|
133 |
r_col = f.column |
|
|
134 |
table = opts.db_table |
|
|
135 |
col = opts.get_field(f.rel.field_name).column |
|
|
136 |
# For MySQL, r_name must be unique in the first 64 characters. |
|
|
137 |
# So we are careful with character usage here. |
|
|
138 |
r_name = '%s_refs_%s_%s' % (r_col, col, self._digest(r_table, table)) |
|
|
139 |
final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \ |
|
|
140 |
(qn(r_table), qn(truncate_name(r_name, self.connection.ops.max_name_length())), |
|
|
141 |
qn(r_col), qn(table), qn(col), |
|
|
142 |
self.connection.ops.deferrable_sql())) |
|
|
143 |
del pending_references[model] |
|
|
144 |
return final_output |
|
|
145 |
|
|
|
146 |
def sql_for_many_to_many(self, model, style): |
|
|
147 |
"Return the CREATE TABLE statments for all the many-to-many tables defined on a model" |
|
|
148 |
output = [] |
|
|
149 |
for f in model._meta.local_many_to_many: |
|
|
150 |
if model._meta.managed or f.rel.to._meta.managed: |
|
|
151 |
output.extend(self.sql_for_many_to_many_field(model, f, style)) |
|
|
152 |
return output |
|
|
153 |
|
|
|
154 |
def sql_for_many_to_many_field(self, model, f, style): |
|
|
155 |
"Return the CREATE TABLE statements for a single m2m field" |
|
|
156 |
from django.db import models |
|
|
157 |
from django.db.backends.util import truncate_name |
|
|
158 |
|
|
|
159 |
output = [] |
|
|
160 |
if f.creates_table: |
|
|
161 |
opts = model._meta |
|
|
162 |
qn = self.connection.ops.quote_name |
|
|
163 |
tablespace = f.db_tablespace or opts.db_tablespace |
|
|
164 |
if tablespace: |
|
|
165 |
sql = self.connection.ops.tablespace_sql(tablespace, inline=True) |
|
|
166 |
if sql: |
|
|
167 |
tablespace_sql = ' ' + sql |
|
|
168 |
else: |
|
|
169 |
tablespace_sql = '' |
|
|
170 |
else: |
|
|
171 |
tablespace_sql = '' |
|
|
172 |
table_output = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + \ |
|
|
173 |
style.SQL_TABLE(qn(f.m2m_db_table())) + ' ('] |
|
|
174 |
table_output.append(' %s %s %s%s,' % |
|
|
175 |
(style.SQL_FIELD(qn('id')), |
|
|
176 |
style.SQL_COLTYPE(models.AutoField(primary_key=True).db_type()), |
|
|
177 |
style.SQL_KEYWORD('NOT NULL PRIMARY KEY'), |
|
|
178 |
tablespace_sql)) |
|
|
179 |
|
|
|
180 |
deferred = [] |
|
|
181 |
inline_output, deferred = self.sql_for_inline_many_to_many_references(model, f, style) |
|
|
182 |
table_output.extend(inline_output) |
|
|
183 |
|
|
|
184 |
table_output.append(' %s (%s, %s)%s' % |
|
|
185 |
(style.SQL_KEYWORD('UNIQUE'), |
|
|
186 |
style.SQL_FIELD(qn(f.m2m_column_name())), |
|
|
187 |
style.SQL_FIELD(qn(f.m2m_reverse_name())), |
|
|
188 |
tablespace_sql)) |
|
|
189 |
table_output.append(')') |
|
|
190 |
if opts.db_tablespace: |
|
|
191 |
# f.db_tablespace is only for indices, so ignore its value here. |
|
|
192 |
table_output.append(self.connection.ops.tablespace_sql(opts.db_tablespace)) |
|
|
193 |
table_output.append(';') |
|
|
194 |
output.append('\n'.join(table_output)) |
|
|
195 |
|
|
|
196 |
for r_table, r_col, table, col in deferred: |
|
|
197 |
r_name = '%s_refs_%s_%s' % (r_col, col, self._digest(r_table, table)) |
|
|
198 |
output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % |
|
|
199 |
(qn(r_table), |
|
|
200 |
qn(truncate_name(r_name, self.connection.ops.max_name_length())), |
|
|
201 |
qn(r_col), qn(table), qn(col), |
|
|
202 |
self.connection.ops.deferrable_sql())) |
|
|
203 |
|
|
|
204 |
# Add any extra SQL needed to support auto-incrementing PKs |
|
|
205 |
autoinc_sql = self.connection.ops.autoinc_sql(f.m2m_db_table(), 'id') |
|
|
206 |
if autoinc_sql: |
|
|
207 |
for stmt in autoinc_sql: |
|
|
208 |
output.append(stmt) |
|
|
209 |
return output |
|
|
210 |
|
|
|
211 |
def sql_for_inline_many_to_many_references(self, model, field, style): |
|
|
212 |
"Create the references to other tables required by a many-to-many table" |
|
|
213 |
from django.db import models |
|
|
214 |
opts = model._meta |
|
|
215 |
qn = self.connection.ops.quote_name |
|
|
216 |
|
|
|
217 |
table_output = [ |
|
|
218 |
' %s %s %s %s (%s)%s,' % |
|
|
219 |
(style.SQL_FIELD(qn(field.m2m_column_name())), |
|
|
220 |
style.SQL_COLTYPE(models.ForeignKey(model).db_type()), |
|
|
221 |
style.SQL_KEYWORD('NOT NULL REFERENCES'), |
|
|
222 |
style.SQL_TABLE(qn(opts.db_table)), |
|
|
223 |
style.SQL_FIELD(qn(opts.pk.column)), |
|
|
224 |
self.connection.ops.deferrable_sql()), |
|
|
225 |
' %s %s %s %s (%s)%s,' % |
|
|
226 |
(style.SQL_FIELD(qn(field.m2m_reverse_name())), |
|
|
227 |
style.SQL_COLTYPE(models.ForeignKey(field.rel.to).db_type()), |
|
|
228 |
style.SQL_KEYWORD('NOT NULL REFERENCES'), |
|
|
229 |
style.SQL_TABLE(qn(field.rel.to._meta.db_table)), |
|
|
230 |
style.SQL_FIELD(qn(field.rel.to._meta.pk.column)), |
|
|
231 |
self.connection.ops.deferrable_sql()) |
|
|
232 |
] |
|
|
233 |
deferred = [] |
|
|
234 |
|
|
|
235 |
return table_output, deferred |
|
|
236 |
|
|
|
237 |
def sql_indexes_for_model(self, model, style): |
|
|
238 |
"Returns the CREATE INDEX SQL statements for a single model" |
|
|
239 |
if not model._meta.managed or model._meta.proxy: |
|
|
240 |
return [] |
|
|
241 |
output = [] |
|
|
242 |
for f in model._meta.local_fields: |
|
|
243 |
output.extend(self.sql_indexes_for_field(model, f, style)) |
|
|
244 |
return output |
|
|
245 |
|
|
|
246 |
def sql_indexes_for_field(self, model, f, style): |
|
|
247 |
"Return the CREATE INDEX SQL statements for a single model field" |
|
|
248 |
if f.db_index and not f.unique: |
|
|
249 |
qn = self.connection.ops.quote_name |
|
|
250 |
tablespace = f.db_tablespace or model._meta.db_tablespace |
|
|
251 |
if tablespace: |
|
|
252 |
sql = self.connection.ops.tablespace_sql(tablespace) |
|
|
253 |
if sql: |
|
|
254 |
tablespace_sql = ' ' + sql |
|
|
255 |
else: |
|
|
256 |
tablespace_sql = '' |
|
|
257 |
else: |
|
|
258 |
tablespace_sql = '' |
|
|
259 |
output = [style.SQL_KEYWORD('CREATE INDEX') + ' ' + |
|
|
260 |
style.SQL_TABLE(qn('%s_%s' % (model._meta.db_table, f.column))) + ' ' + |
|
|
261 |
style.SQL_KEYWORD('ON') + ' ' + |
|
|
262 |
style.SQL_TABLE(qn(model._meta.db_table)) + ' ' + |
|
|
263 |
"(%s)" % style.SQL_FIELD(qn(f.column)) + |
|
|
264 |
"%s;" % tablespace_sql] |
|
|
265 |
else: |
|
|
266 |
output = [] |
|
|
267 |
return output |
|
|
268 |
|
|
|
269 |
def sql_destroy_model(self, model, references_to_delete, style): |
|
|
270 |
"Return the DROP TABLE and restraint dropping statements for a single model" |
|
|
271 |
if not model._meta.managed or model._meta.proxy: |
|
|
272 |
return [] |
|
|
273 |
# Drop the table now |
|
|
274 |
qn = self.connection.ops.quote_name |
|
|
275 |
output = ['%s %s;' % (style.SQL_KEYWORD('DROP TABLE'), |
|
|
276 |
style.SQL_TABLE(qn(model._meta.db_table)))] |
|
|
277 |
if model in references_to_delete: |
|
|
278 |
output.extend(self.sql_remove_table_constraints(model, references_to_delete, style)) |
|
|
279 |
|
|
|
280 |
if model._meta.has_auto_field: |
|
|
281 |
ds = self.connection.ops.drop_sequence_sql(model._meta.db_table) |
|
|
282 |
if ds: |
|
|
283 |
output.append(ds) |
|
|
284 |
return output |
|
|
285 |
|
|
|
286 |
def sql_remove_table_constraints(self, model, references_to_delete, style): |
|
|
287 |
from django.db.backends.util import truncate_name |
|
|
288 |
|
|
|
289 |
if not model._meta.managed or model._meta.proxy: |
|
|
290 |
return [] |
|
|
291 |
output = [] |
|
|
292 |
qn = self.connection.ops.quote_name |
|
|
293 |
for rel_class, f in references_to_delete[model]: |
|
|
294 |
table = rel_class._meta.db_table |
|
|
295 |
col = f.column |
|
|
296 |
r_table = model._meta.db_table |
|
|
297 |
r_col = model._meta.get_field(f.rel.field_name).column |
|
|
298 |
r_name = '%s_refs_%s_%s' % (col, r_col, self._digest(table, r_table)) |
|
|
299 |
output.append('%s %s %s %s;' % \ |
|
|
300 |
(style.SQL_KEYWORD('ALTER TABLE'), |
|
|
301 |
style.SQL_TABLE(qn(table)), |
|
|
302 |
style.SQL_KEYWORD(self.connection.ops.drop_foreignkey_sql()), |
|
|
303 |
style.SQL_FIELD(truncate_name(r_name, self.connection.ops.max_name_length())))) |
|
|
304 |
del references_to_delete[model] |
|
|
305 |
return output |
|
|
306 |
|
|
|
307 |
def sql_destroy_many_to_many(self, model, f, style): |
|
|
308 |
"Returns the DROP TABLE statements for a single m2m field" |
|
|
309 |
qn = self.connection.ops.quote_name |
|
|
310 |
output = [] |
|
|
311 |
if f.creates_table: |
|
|
312 |
output.append("%s %s;" % (style.SQL_KEYWORD('DROP TABLE'), |
|
|
313 |
style.SQL_TABLE(qn(f.m2m_db_table())))) |
|
|
314 |
ds = self.connection.ops.drop_sequence_sql("%s_%s" % (model._meta.db_table, f.column)) |
|
|
315 |
if ds: |
|
|
316 |
output.append(ds) |
|
|
317 |
return output |
|
|
318 |
|
|
|
319 |
def create_test_db(self, verbosity=1, autoclobber=False): |
|
|
320 |
""" |
|
|
321 |
Creates a test database, prompting the user for confirmation if the |
|
|
322 |
database already exists. Returns the name of the test database created. |
|
|
323 |
""" |
|
|
324 |
if verbosity >= 1: |
|
|
325 |
print "Creating test database..." |
|
|
326 |
|
|
|
327 |
test_database_name = self._create_test_db(verbosity, autoclobber) |
|
|
328 |
|
|
|
329 |
self.connection.close() |
|
|
330 |
settings.DATABASE_NAME = test_database_name |
|
|
331 |
self.connection.settings_dict["DATABASE_NAME"] = test_database_name |
|
|
332 |
can_rollback = self._rollback_works() |
|
|
333 |
settings.DATABASE_SUPPORTS_TRANSACTIONS = can_rollback |
|
|
334 |
self.connection.settings_dict["DATABASE_SUPPORTS_TRANSACTIONS"] = can_rollback |
|
|
335 |
|
|
|
336 |
call_command('syncdb', verbosity=verbosity, interactive=False) |
|
|
337 |
|
|
|
338 |
if settings.CACHE_BACKEND.startswith('db://'): |
|
|
339 |
from django.core.cache import parse_backend_uri |
|
|
340 |
_, cache_name, _ = parse_backend_uri(settings.CACHE_BACKEND) |
|
|
341 |
call_command('createcachetable', cache_name) |
|
|
342 |
|
|
|
343 |
# Get a cursor (even though we don't need one yet). This has |
|
|
344 |
# the side effect of initializing the test database. |
|
|
345 |
cursor = self.connection.cursor() |
|
|
346 |
|
|
|
347 |
return test_database_name |
|
|
348 |
|
|
|
349 |
def _create_test_db(self, verbosity, autoclobber): |
|
|
350 |
"Internal implementation - creates the test db tables." |
|
|
351 |
suffix = self.sql_table_creation_suffix() |
|
|
352 |
|
|
|
353 |
if settings.TEST_DATABASE_NAME: |
|
|
354 |
test_database_name = settings.TEST_DATABASE_NAME |
|
|
355 |
else: |
|
|
356 |
test_database_name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME |
|
|
357 |
|
|
|
358 |
qn = self.connection.ops.quote_name |
|
|
359 |
|
|
|
360 |
# Create the test database and connect to it. We need to autocommit |
|
|
361 |
# if the database supports it because PostgreSQL doesn't allow |
|
|
362 |
# CREATE/DROP DATABASE statements within transactions. |
|
|
363 |
cursor = self.connection.cursor() |
|
|
364 |
self.set_autocommit() |
|
|
365 |
try: |
|
|
366 |
cursor.execute("CREATE DATABASE %s %s" % (qn(test_database_name), suffix)) |
|
|
367 |
except Exception, e: |
|
|
368 |
sys.stderr.write("Got an error creating the test database: %s\n" % e) |
|
|
369 |
if not autoclobber: |
|
|
370 |
confirm = raw_input("Type 'yes' if you would like to try deleting the test database '%s', or 'no' to cancel: " % test_database_name) |
|
|
371 |
if autoclobber or confirm == 'yes': |
|
|
372 |
try: |
|
|
373 |
if verbosity >= 1: |
|
|
374 |
print "Destroying old test database..." |
|
|
375 |
cursor.execute("DROP DATABASE %s" % qn(test_database_name)) |
|
|
376 |
if verbosity >= 1: |
|
|
377 |
print "Creating test database..." |
|
|
378 |
cursor.execute("CREATE DATABASE %s %s" % (qn(test_database_name), suffix)) |
|
|
379 |
except Exception, e: |
|
|
380 |
sys.stderr.write("Got an error recreating the test database: %s\n" % e) |
|
|
381 |
sys.exit(2) |
|
|
382 |
else: |
|
|
383 |
print "Tests cancelled." |
|
|
384 |
sys.exit(1) |
|
|
385 |
|
|
|
386 |
return test_database_name |
|
|
387 |
|
|
|
388 |
def _rollback_works(self): |
|
|
389 |
cursor = self.connection.cursor() |
|
|
390 |
cursor.execute('CREATE TABLE ROLLBACK_TEST (X INT)') |
|
|
391 |
self.connection._commit() |
|
|
392 |
cursor.execute('INSERT INTO ROLLBACK_TEST (X) VALUES (8)') |
|
|
393 |
self.connection._rollback() |
|
|
394 |
cursor.execute('SELECT COUNT(X) FROM ROLLBACK_TEST') |
|
|
395 |
count, = cursor.fetchone() |
|
|
396 |
cursor.execute('DROP TABLE ROLLBACK_TEST') |
|
|
397 |
self.connection._commit() |
|
|
398 |
return count == 0 |
|
|
399 |
|
|
|
400 |
def destroy_test_db(self, old_database_name, verbosity=1): |
|
|
401 |
""" |
|
|
402 |
Destroy a test database, prompting the user for confirmation if the |
|
|
403 |
database already exists. Returns the name of the test database created. |
|
|
404 |
""" |
|
|
405 |
if verbosity >= 1: |
|
|
406 |
print "Destroying test database..." |
|
|
407 |
self.connection.close() |
|
|
408 |
test_database_name = settings.DATABASE_NAME |
|
|
409 |
settings.DATABASE_NAME = old_database_name |
|
|
410 |
self.connection.settings_dict["DATABASE_NAME"] = old_database_name |
|
|
411 |
|
|
|
412 |
self._destroy_test_db(test_database_name, verbosity) |
|
|
413 |
|
|
|
414 |
def _destroy_test_db(self, test_database_name, verbosity): |
|
|
415 |
"Internal implementation - remove the test db tables." |
|
|
416 |
# Remove the test database to clean up after |
|
|
417 |
# ourselves. Connect to the previous database (not the test database) |
|
|
418 |
# to do so, because it's not allowed to delete a database while being |
|
|
419 |
# connected to it. |
|
|
420 |
cursor = self.connection.cursor() |
|
|
421 |
self.set_autocommit() |
|
|
422 |
time.sleep(1) # To avoid "database is being accessed by other users" errors. |
|
|
423 |
cursor.execute("DROP DATABASE %s" % self.connection.ops.quote_name(test_database_name)) |
|
|
424 |
self.connection.close() |
|
|
425 |
|
|
|
426 |
def set_autocommit(self): |
|
|
427 |
"Make sure a connection is in autocommit mode." |
|
|
428 |
if hasattr(self.connection.connection, "autocommit"): |
|
|
429 |
if callable(self.connection.connection.autocommit): |
|
|
430 |
self.connection.connection.autocommit(True) |
|
|
431 |
else: |
|
|
432 |
self.connection.connection.autocommit = True |
|
|
433 |
elif hasattr(self.connection.connection, "set_isolation_level"): |
|
|
434 |
self.connection.connection.set_isolation_level(0) |
|
|
435 |
|
|
|
436 |
def sql_table_creation_suffix(self): |
|
|
437 |
"SQL to append to the end of the test table creation statements" |
|
|
438 |
return '' |
|
|
439 |
|