PageRenderTime 48ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/django/db/backends/creation.py

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