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