7
from sets import Set as set
9
from django.conf import settings
10
from django.core.management import call_command
12
# The prefix to put on the default database name when creating
14
TEST_DATABASE_PREFIX = 'test_'
16
class BaseDatabaseCreation(object):
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.
25
def __init__(self, connection):
26
self.connection = connection
28
def sql_create_model(self, model, style, known_models=set()):
30
Returns the SQL required to create a single model, as a tuple of:
31
(list_of_sql, pending_references_dict)
33
from django.db import models
38
pending_references = {}
39
qn = self.connection.ops.quote_name
40
for f in opts.local_fields:
41
col_type = f.db_type()
42
tablespace = f.db_tablespace or opts.db_tablespace
44
# Skip ManyToManyFields, because they're not represented as
45
# database columns in this table.
47
# Make the definition (e.g. 'foo VARCHAR(30)') for this field.
48
field_output = [style.SQL_FIELD(qn(f.column)),
49
style.SQL_COLTYPE(col_type)]
50
field_output.append(style.SQL_KEYWORD('%sNULL' % (not f.null and 'NOT ' or '')))
52
field_output.append(style.SQL_KEYWORD('PRIMARY KEY'))
54
field_output.append(style.SQL_KEYWORD('UNIQUE'))
55
if tablespace and f.unique:
56
# We must specify the index tablespace inline, because we
57
# won't be generating a CREATE INDEX statement for this field.
58
field_output.append(self.connection.ops.tablespace_sql(tablespace, inline=True))
60
ref_output, pending = self.sql_for_inline_foreign_key_references(f, known_models, style)
62
pr = pending_references.setdefault(f.rel.to, []).append((model, f))
64
field_output.extend(ref_output)
65
table_output.append(' '.join(field_output))
66
if opts.order_with_respect_to:
67
table_output.append(style.SQL_FIELD(qn('_order')) + ' ' + \
68
style.SQL_COLTYPE(models.IntegerField().db_type()) + ' ' + \
69
style.SQL_KEYWORD('NULL'))
70
for field_constraints in opts.unique_together:
71
table_output.append(style.SQL_KEYWORD('UNIQUE') + ' (%s)' % \
72
", ".join([style.SQL_FIELD(qn(opts.get_field(f).column)) for f in field_constraints]))
74
full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(qn(opts.db_table)) + ' (']
75
for i, line in enumerate(table_output): # Combine and add commas.
76
full_statement.append(' %s%s' % (line, i < len(table_output)-1 and ',' or ''))
77
full_statement.append(')')
78
if opts.db_tablespace:
79
full_statement.append(self.connection.ops.tablespace_sql(opts.db_tablespace))
80
full_statement.append(';')
81
final_output.append('\n'.join(full_statement))
83
if opts.has_auto_field:
84
# Add any extra SQL needed to support auto-incrementing primary keys.
85
auto_column = opts.auto_field.db_column or opts.auto_field.name
86
autoinc_sql = self.connection.ops.autoinc_sql(opts.db_table, auto_column)
88
for stmt in autoinc_sql:
89
final_output.append(stmt)
91
return final_output, pending_references
93
def sql_for_inline_foreign_key_references(self, field, known_models, style):
94
"Return the SQL snippet defining the foreign key reference for a field"
95
qn = self.connection.ops.quote_name
96
if field.rel.to in known_models:
97
output = [style.SQL_KEYWORD('REFERENCES') + ' ' + \
98
style.SQL_TABLE(qn(field.rel.to._meta.db_table)) + ' (' + \
99
style.SQL_FIELD(qn(field.rel.to._meta.get_field(field.rel.field_name).column)) + ')' +
100
self.connection.ops.deferrable_sql()
104
# We haven't yet created the table to which this field
105
# is related, so save it for later.
109
return output, pending
111
def sql_for_pending_references(self, model, style, pending_references):
112
"Returns any ALTER TABLE statements to add constraints after the fact."
113
from django.db.backends.util import truncate_name
115
qn = self.connection.ops.quote_name
118
if model in pending_references:
119
for rel_class, f in pending_references[model]:
120
rel_opts = rel_class._meta
121
r_table = rel_opts.db_table
123
table = opts.db_table
124
col = opts.get_field(f.rel.field_name).column
125
# For MySQL, r_name must be unique in the first 64 characters.
126
# So we are careful with character usage here.
127
r_name = '%s_refs_%s_%x' % (r_col, col, abs(hash((r_table, table))))
128
final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \
129
(qn(r_table), truncate_name(r_name, self.connection.ops.max_name_length()),
130
qn(r_col), qn(table), qn(col),
131
self.connection.ops.deferrable_sql()))
132
del pending_references[model]
135
def sql_for_many_to_many(self, model, style):
136
"Return the CREATE TABLE statments for all the many-to-many tables defined on a model"
138
for f in model._meta.local_many_to_many:
139
output.extend(self.sql_for_many_to_many_field(model, f, style))
142
def sql_for_many_to_many_field(self, model, f, style):
143
"Return the CREATE TABLE statements for a single m2m field"
144
from django.db import models
145
from django.db.backends.util import truncate_name
150
qn = self.connection.ops.quote_name
151
tablespace = f.db_tablespace or opts.db_tablespace
153
sql = self.connection.ops.tablespace_sql(tablespace, inline=True)
155
tablespace_sql = ' ' + sql
160
table_output = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + \
161
style.SQL_TABLE(qn(f.m2m_db_table())) + ' (']
162
table_output.append(' %s %s %s%s,' %
163
(style.SQL_FIELD(qn('id')),
164
style.SQL_COLTYPE(models.AutoField(primary_key=True).db_type()),
165
style.SQL_KEYWORD('NOT NULL PRIMARY KEY'),
169
inline_output, deferred = self.sql_for_inline_many_to_many_references(model, f, style)
170
table_output.extend(inline_output)
172
table_output.append(' %s (%s, %s)%s' %
173
(style.SQL_KEYWORD('UNIQUE'),
174
style.SQL_FIELD(qn(f.m2m_column_name())),
175
style.SQL_FIELD(qn(f.m2m_reverse_name())),
177
table_output.append(')')
178
if opts.db_tablespace:
179
# f.db_tablespace is only for indices, so ignore its value here.
180
table_output.append(self.connection.ops.tablespace_sql(opts.db_tablespace))
181
table_output.append(';')
182
output.append('\n'.join(table_output))
184
for r_table, r_col, table, col in deferred:
185
r_name = '%s_refs_%s_%x' % (r_col, col,
186
abs(hash((r_table, table))))
187
output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' %
189
truncate_name(r_name, self.connection.ops.max_name_length()),
190
qn(r_col), qn(table), qn(col),
191
self.connection.ops.deferrable_sql()))
193
# Add any extra SQL needed to support auto-incrementing PKs
194
autoinc_sql = self.connection.ops.autoinc_sql(f.m2m_db_table(), 'id')
196
for stmt in autoinc_sql:
200
def sql_for_inline_many_to_many_references(self, model, field, style):
201
"Create the references to other tables required by a many-to-many table"
202
from django.db import models
204
qn = self.connection.ops.quote_name
207
' %s %s %s %s (%s)%s,' %
208
(style.SQL_FIELD(qn(field.m2m_column_name())),
209
style.SQL_COLTYPE(models.ForeignKey(model).db_type()),
210
style.SQL_KEYWORD('NOT NULL REFERENCES'),
211
style.SQL_TABLE(qn(opts.db_table)),
212
style.SQL_FIELD(qn(opts.pk.column)),
213
self.connection.ops.deferrable_sql()),
214
' %s %s %s %s (%s)%s,' %
215
(style.SQL_FIELD(qn(field.m2m_reverse_name())),
216
style.SQL_COLTYPE(models.ForeignKey(field.rel.to).db_type()),
217
style.SQL_KEYWORD('NOT NULL REFERENCES'),
218
style.SQL_TABLE(qn(field.rel.to._meta.db_table)),
219
style.SQL_FIELD(qn(field.rel.to._meta.pk.column)),
220
self.connection.ops.deferrable_sql())
224
return table_output, deferred
226
def sql_indexes_for_model(self, model, style):
227
"Returns the CREATE INDEX SQL statements for a single model"
229
for f in model._meta.local_fields:
230
output.extend(self.sql_indexes_for_field(model, f, style))
233
def sql_indexes_for_field(self, model, f, style):
234
"Return the CREATE INDEX SQL statements for a single model field"
235
if f.db_index and not f.unique:
236
qn = self.connection.ops.quote_name
237
tablespace = f.db_tablespace or model._meta.db_tablespace
239
sql = self.connection.ops.tablespace_sql(tablespace)
241
tablespace_sql = ' ' + sql
246
output = [style.SQL_KEYWORD('CREATE INDEX') + ' ' +
247
style.SQL_TABLE(qn('%s_%s' % (model._meta.db_table, f.column))) + ' ' +
248
style.SQL_KEYWORD('ON') + ' ' +
249
style.SQL_TABLE(qn(model._meta.db_table)) + ' ' +
250
"(%s)" % style.SQL_FIELD(qn(f.column)) +
251
"%s;" % tablespace_sql]
256
def sql_destroy_model(self, model, references_to_delete, style):
257
"Return the DROP TABLE and restraint dropping statements for a single model"
259
qn = self.connection.ops.quote_name
260
output = ['%s %s;' % (style.SQL_KEYWORD('DROP TABLE'),
261
style.SQL_TABLE(qn(model._meta.db_table)))]
262
if model in references_to_delete:
263
output.extend(self.sql_remove_table_constraints(model, references_to_delete, style))
265
if model._meta.has_auto_field:
266
ds = self.connection.ops.drop_sequence_sql(model._meta.db_table)
271
def sql_remove_table_constraints(self, model, references_to_delete, style):
272
from django.db.backends.util import truncate_name
275
qn = self.connection.ops.quote_name
276
for rel_class, f in references_to_delete[model]:
277
table = rel_class._meta.db_table
279
r_table = model._meta.db_table
280
r_col = model._meta.get_field(f.rel.field_name).column
281
r_name = '%s_refs_%s_%x' % (col, r_col, abs(hash((table, r_table))))
282
output.append('%s %s %s %s;' % \
283
(style.SQL_KEYWORD('ALTER TABLE'),
284
style.SQL_TABLE(qn(table)),
285
style.SQL_KEYWORD(self.connection.ops.drop_foreignkey_sql()),
286
style.SQL_FIELD(truncate_name(r_name, self.connection.ops.max_name_length()))))
287
del references_to_delete[model]
290
def sql_destroy_many_to_many(self, model, f, style):
291
"Returns the DROP TABLE statements for a single m2m field"
292
qn = self.connection.ops.quote_name
295
output.append("%s %s;" % (style.SQL_KEYWORD('DROP TABLE'),
296
style.SQL_TABLE(qn(f.m2m_db_table()))))
297
ds = self.connection.ops.drop_sequence_sql("%s_%s" % (model._meta.db_table, f.column))
302
def create_test_db(self, verbosity=1, autoclobber=False):
304
Creates a test database, prompting the user for confirmation if the
305
database already exists. Returns the name of the test database created.
308
print "Creating test database..."
310
test_database_name = self._create_test_db(verbosity, autoclobber)
312
self.connection.close()
313
settings.DATABASE_NAME = test_database_name
315
call_command('syncdb', verbosity=verbosity, interactive=False)
317
if settings.CACHE_BACKEND.startswith('db://'):
318
cache_name = settings.CACHE_BACKEND[len('db://'):]
319
call_command('createcachetable', cache_name)
321
# Get a cursor (even though we don't need one yet). This has
322
# the side effect of initializing the test database.
323
cursor = self.connection.cursor()
325
return test_database_name
327
def _create_test_db(self, verbosity, autoclobber):
328
"Internal implementation - creates the test db tables."
329
suffix = self.sql_table_creation_suffix()
331
if settings.TEST_DATABASE_NAME:
332
test_database_name = settings.TEST_DATABASE_NAME
334
test_database_name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME
336
qn = self.connection.ops.quote_name
338
# Create the test database and connect to it. We need to autocommit
339
# if the database supports it because PostgreSQL doesn't allow
340
# CREATE/DROP DATABASE statements within transactions.
341
cursor = self.connection.cursor()
342
self.set_autocommit()
344
cursor.execute("CREATE DATABASE %s %s" % (qn(test_database_name), suffix))
346
sys.stderr.write("Got an error creating the test database: %s\n" % e)
348
confirm = raw_input("Type 'yes' if you would like to try deleting the test database '%s', or 'no' to cancel: " % test_database_name)
349
if autoclobber or confirm == 'yes':
352
print "Destroying old test database..."
353
cursor.execute("DROP DATABASE %s" % qn(test_database_name))
355
print "Creating test database..."
356
cursor.execute("CREATE DATABASE %s %s" % (qn(test_database_name), suffix))
358
sys.stderr.write("Got an error recreating the test database: %s\n" % e)
361
print "Tests cancelled."
364
return test_database_name
366
def destroy_test_db(self, old_database_name, verbosity=1):
368
Destroy a test database, prompting the user for confirmation if the
369
database already exists. Returns the name of the test database created.
372
print "Destroying test database..."
373
self.connection.close()
374
test_database_name = settings.DATABASE_NAME
375
settings.DATABASE_NAME = old_database_name
377
self._destroy_test_db(test_database_name, verbosity)
379
def _destroy_test_db(self, test_database_name, verbosity):
380
"Internal implementation - remove the test db tables."
381
# Remove the test database to clean up after
382
# ourselves. Connect to the previous database (not the test database)
383
# to do so, because it's not allowed to delete a database while being
385
cursor = self.connection.cursor()
386
self.set_autocommit()
387
time.sleep(1) # To avoid "database is being accessed by other users" errors.
388
cursor.execute("DROP DATABASE %s" % self.connection.ops.quote_name(test_database_name))
389
self.connection.close()
391
def set_autocommit(self):
392
"Make sure a connection is in autocommit mode."
393
if hasattr(self.connection.connection, "autocommit"):
394
if callable(self.connection.connection.autocommit):
395
self.connection.connection.autocommit(True)
397
self.connection.connection.autocommit = True
398
elif hasattr(self.connection.connection, "set_isolation_level"):
399
self.connection.connection.set_isolation_level(0)
401
def sql_table_creation_suffix(self):
402
"SQL to append to the end of the test table creation statements"