2014-05-17 00:11:29 +08:00
|
|
|
import codecs
|
2015-02-12 00:15:41 +08:00
|
|
|
import contextlib
|
2014-05-20 23:25:59 +08:00
|
|
|
import copy
|
2014-05-08 04:46:23 +08:00
|
|
|
from decimal import Decimal
|
2015-01-13 04:20:40 +08:00
|
|
|
|
2013-12-24 19:25:17 +08:00
|
|
|
from django.apps.registry import Apps
|
2015-01-13 04:20:40 +08:00
|
|
|
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
|
|
|
from django.utils import six
|
|
|
|
|
2012-08-18 21:16:52 +08:00
|
|
|
|
|
|
|
class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
|
|
|
|
|
|
|
sql_delete_table = "DROP TABLE %(table)s"
|
2013-12-29 05:00:16 +08:00
|
|
|
sql_create_inline_fk = "REFERENCES %(to_table)s (%(to_column)s)"
|
2015-03-22 00:38:23 +08:00
|
|
|
sql_create_unique = "CREATE UNIQUE INDEX %(name)s ON %(table)s (%(columns)s)"
|
|
|
|
sql_delete_unique = "DROP INDEX %(name)s"
|
2012-09-08 00:51:11 +08:00
|
|
|
|
2015-02-18 05:59:25 +08:00
|
|
|
def __enter__(self):
|
|
|
|
with self.connection.cursor() as c:
|
|
|
|
# Some SQLite schema alterations need foreign key constraints to be
|
|
|
|
# disabled. This is the default in SQLite but can be changed with a
|
|
|
|
# build flag and might change in future, so can't be relied upon.
|
|
|
|
# We enforce it here for the duration of the transaction.
|
|
|
|
c.execute('PRAGMA foreign_keys')
|
|
|
|
self._initial_pragma_fk = c.fetchone()[0]
|
|
|
|
c.execute('PRAGMA foreign_keys = 0')
|
|
|
|
return super(DatabaseSchemaEditor, self).__enter__()
|
|
|
|
|
|
|
|
def __exit__(self, exc_type, exc_value, traceback):
|
|
|
|
super(DatabaseSchemaEditor, self).__exit__(exc_type, exc_value, traceback)
|
|
|
|
with self.connection.cursor() as c:
|
|
|
|
# Restore initial FK setting - PRAGMA values can't be parametrized
|
|
|
|
c.execute('PRAGMA foreign_keys = %s' % int(self._initial_pragma_fk))
|
|
|
|
|
2014-02-09 20:41:55 +08:00
|
|
|
def quote_value(self, value):
|
2015-05-04 01:41:34 +08:00
|
|
|
# The backend "mostly works" without this function and there are use
|
|
|
|
# cases for compiling Python without the sqlite3 libraries (e.g.
|
|
|
|
# security hardening).
|
2015-06-06 03:47:17 +08:00
|
|
|
import sqlite3
|
2014-02-09 20:41:55 +08:00
|
|
|
try:
|
2015-06-06 03:47:17 +08:00
|
|
|
value = sqlite3.adapt(value)
|
|
|
|
except sqlite3.ProgrammingError:
|
2014-02-09 20:41:55 +08:00
|
|
|
pass
|
|
|
|
# Manual emulation of SQLite parameter quoting
|
|
|
|
if isinstance(value, type(True)):
|
|
|
|
return str(int(value))
|
2014-05-08 04:46:23 +08:00
|
|
|
elif isinstance(value, (Decimal, float)):
|
|
|
|
return str(value)
|
2014-02-09 20:41:55 +08:00
|
|
|
elif isinstance(value, six.integer_types):
|
|
|
|
return str(value)
|
|
|
|
elif isinstance(value, six.string_types):
|
2014-05-20 22:41:01 +08:00
|
|
|
return "'%s'" % six.text_type(value).replace("\'", "\'\'")
|
2014-02-09 20:41:55 +08:00
|
|
|
elif value is None:
|
|
|
|
return "NULL"
|
2014-05-17 00:11:29 +08:00
|
|
|
elif isinstance(value, (bytes, bytearray, six.memoryview)):
|
|
|
|
# Bytes are only allowed for BLOB fields, encoded as string
|
|
|
|
# literals containing hexadecimal data and preceded by a single "X"
|
|
|
|
# character:
|
|
|
|
# value = b'\x01\x02' => value_hex = b'0102' => return X'0102'
|
|
|
|
value = bytes(value)
|
|
|
|
hex_encoder = codecs.getencoder('hex_codec')
|
|
|
|
value_hex, _length = hex_encoder(value)
|
|
|
|
# Use 'ascii' encoding for b'01' => '01', no need to use force_text here.
|
|
|
|
return "X'%s'" % value_hex.decode('ascii')
|
2014-02-09 20:41:55 +08:00
|
|
|
else:
|
2014-05-08 04:46:23 +08:00
|
|
|
raise ValueError("Cannot quote parameter value %r of type %s" % (value, type(value)))
|
2014-02-09 20:41:55 +08:00
|
|
|
|
2014-12-01 01:11:52 +08:00
|
|
|
def _remake_table(self, model, create_fields=[], delete_fields=[], alter_fields=[], override_uniques=None,
|
|
|
|
override_indexes=None):
|
2012-09-18 17:37:30 +08:00
|
|
|
"""
|
|
|
|
Shortcut to transform a model from old_model into new_model
|
2015-02-12 00:15:41 +08:00
|
|
|
|
|
|
|
The essential steps are:
|
|
|
|
1. rename the model's existing table, e.g. "app_model" to "app_model__old"
|
|
|
|
2. create a table with the updated definition called "app_model"
|
|
|
|
3. copy the data from the old renamed table to the new table
|
|
|
|
4. delete the "app_model__old" table
|
2012-09-18 17:37:30 +08:00
|
|
|
"""
|
2012-09-08 00:51:11 +08:00
|
|
|
# Work out the new fields dict / mapping
|
2014-12-07 05:00:09 +08:00
|
|
|
body = {f.name: f for f in model._meta.local_fields}
|
2014-05-17 00:11:29 +08:00
|
|
|
# Since mapping might mix column names and default values,
|
|
|
|
# its values must be already quoted.
|
2014-12-07 05:00:09 +08:00
|
|
|
mapping = {f.column: self.quote_name(f.column) for f in model._meta.local_fields}
|
2014-07-21 18:36:34 +08:00
|
|
|
# This maps field names (not columns) for things like unique_together
|
|
|
|
rename_mapping = {}
|
2012-09-08 00:51:11 +08:00
|
|
|
# If any of the new or altered fields is introducing a new PK,
|
|
|
|
# remove the old one
|
|
|
|
restore_pk_field = None
|
|
|
|
if any(f.primary_key for f in create_fields) or any(n.primary_key for o, n in alter_fields):
|
|
|
|
for name, field in list(body.items()):
|
|
|
|
if field.primary_key:
|
|
|
|
field.primary_key = False
|
|
|
|
restore_pk_field = field
|
|
|
|
if field.auto_created:
|
|
|
|
del body[name]
|
|
|
|
del mapping[field.column]
|
|
|
|
# Add in any created fields
|
|
|
|
for field in create_fields:
|
|
|
|
body[field.name] = field
|
2014-12-15 22:14:39 +08:00
|
|
|
# Choose a default and insert it into the copy map
|
2015-01-09 06:51:00 +08:00
|
|
|
if not field.many_to_many:
|
2014-02-09 20:41:55 +08:00
|
|
|
mapping[field.column] = self.quote_value(
|
2014-05-08 04:46:23 +08:00
|
|
|
self.effective_default(field)
|
2014-01-20 01:10:24 +08:00
|
|
|
)
|
2012-09-08 00:51:11 +08:00
|
|
|
# Add in any altered fields
|
|
|
|
for (old_field, new_field) in alter_fields:
|
2014-10-24 00:24:34 +08:00
|
|
|
body.pop(old_field.name, None)
|
|
|
|
mapping.pop(old_field.column, None)
|
2012-09-08 00:51:11 +08:00
|
|
|
body[new_field.name] = new_field
|
2014-10-07 07:53:21 +08:00
|
|
|
if old_field.null and not new_field.null:
|
|
|
|
case_sql = "coalesce(%(col)s, %(default)s)" % {
|
|
|
|
'col': self.quote_name(old_field.column),
|
|
|
|
'default': self.quote_value(self.effective_default(new_field))
|
|
|
|
}
|
|
|
|
mapping[new_field.column] = case_sql
|
|
|
|
else:
|
|
|
|
mapping[new_field.column] = self.quote_name(old_field.column)
|
2014-07-21 18:36:34 +08:00
|
|
|
rename_mapping[old_field.name] = new_field.name
|
2012-09-08 00:51:11 +08:00
|
|
|
# Remove any deleted fields
|
|
|
|
for field in delete_fields:
|
|
|
|
del body[field.name]
|
|
|
|
del mapping[field.column]
|
2014-03-18 11:54:35 +08:00
|
|
|
# Remove any implicit M2M tables
|
2015-02-26 22:19:17 +08:00
|
|
|
if field.many_to_many and field.remote_field.through._meta.auto_created:
|
|
|
|
return self.delete_model(field.remote_field.through)
|
2013-12-24 19:25:17 +08:00
|
|
|
# Work inside a new app registry
|
|
|
|
apps = Apps()
|
2014-05-20 23:25:59 +08:00
|
|
|
|
2015-02-12 00:15:41 +08:00
|
|
|
# Provide isolated instances of the fields to the new model body so
|
|
|
|
# that the existing model's internals aren't interfered with when
|
|
|
|
# the dummy model is constructed.
|
2014-05-20 23:25:59 +08:00
|
|
|
body = copy.deepcopy(body)
|
|
|
|
|
2014-07-21 18:36:34 +08:00
|
|
|
# Work out the new value of unique_together, taking renames into
|
|
|
|
# account
|
|
|
|
if override_uniques is None:
|
|
|
|
override_uniques = [
|
|
|
|
[rename_mapping.get(n, n) for n in unique]
|
|
|
|
for unique in model._meta.unique_together
|
|
|
|
]
|
|
|
|
|
2014-12-01 01:11:52 +08:00
|
|
|
# Work out the new value for index_together, taking renames into
|
|
|
|
# account
|
|
|
|
if override_indexes is None:
|
|
|
|
override_indexes = [
|
|
|
|
[rename_mapping.get(n, n) for n in index]
|
|
|
|
for index in model._meta.index_together
|
|
|
|
]
|
|
|
|
|
2012-09-08 00:51:11 +08:00
|
|
|
# Construct a new model for the new state
|
|
|
|
meta_contents = {
|
|
|
|
'app_label': model._meta.app_label,
|
2015-02-12 00:15:41 +08:00
|
|
|
'db_table': model._meta.db_table,
|
2014-07-21 18:36:34 +08:00
|
|
|
'unique_together': override_uniques,
|
2014-12-01 01:11:52 +08:00
|
|
|
'index_together': override_indexes,
|
2013-12-24 19:25:17 +08:00
|
|
|
'apps': apps,
|
2012-09-08 00:51:11 +08:00
|
|
|
}
|
|
|
|
meta = type("Meta", tuple(), meta_contents)
|
|
|
|
body['Meta'] = meta
|
2012-09-22 07:47:04 +08:00
|
|
|
body['__module__'] = model.__module__
|
2014-05-20 23:25:59 +08:00
|
|
|
|
2012-09-22 07:47:04 +08:00
|
|
|
temp_model = type(model._meta.object_name, model.__bases__, body)
|
2015-02-12 00:15:41 +08:00
|
|
|
|
|
|
|
# We need to modify model._meta.db_table, but everything explodes
|
|
|
|
# if the change isn't reversed before the end of this method. This
|
|
|
|
# context manager helps us avoid that situation.
|
|
|
|
@contextlib.contextmanager
|
|
|
|
def altered_table_name(model, temporary_table_name):
|
|
|
|
original_table_name = model._meta.db_table
|
|
|
|
model._meta.db_table = temporary_table_name
|
|
|
|
yield
|
|
|
|
model._meta.db_table = original_table_name
|
|
|
|
|
2015-04-02 00:57:53 +08:00
|
|
|
with altered_table_name(model, model._meta.db_table + "__old"):
|
|
|
|
# Rename the old table to make way for the new
|
2015-02-12 00:15:41 +08:00
|
|
|
self.alter_db_table(model, temp_model._meta.db_table, model._meta.db_table)
|
|
|
|
|
2015-04-02 00:57:53 +08:00
|
|
|
# Create a new table with the updated schema. We remove things
|
|
|
|
# from the deferred SQL that match our table name, too
|
|
|
|
self.deferred_sql = [x for x in self.deferred_sql if temp_model._meta.db_table not in x]
|
|
|
|
self.create_model(temp_model)
|
|
|
|
|
|
|
|
# Copy data from the old table into the new table
|
|
|
|
field_maps = list(mapping.items())
|
|
|
|
self.execute("INSERT INTO %s (%s) SELECT %s FROM %s" % (
|
|
|
|
self.quote_name(temp_model._meta.db_table),
|
|
|
|
', '.join(self.quote_name(x) for x, y in field_maps),
|
|
|
|
', '.join(y for x, y in field_maps),
|
|
|
|
self.quote_name(model._meta.db_table),
|
|
|
|
))
|
|
|
|
|
|
|
|
# Delete the old table
|
2015-02-12 00:15:41 +08:00
|
|
|
self.delete_model(model, handle_autom2m=False)
|
|
|
|
|
2012-09-08 00:51:11 +08:00
|
|
|
# Run deferred SQL on correct table
|
|
|
|
for sql in self.deferred_sql:
|
2015-02-12 00:15:41 +08:00
|
|
|
self.execute(sql)
|
2012-09-08 00:51:11 +08:00
|
|
|
self.deferred_sql = []
|
|
|
|
# Fix any PK-removed field
|
|
|
|
if restore_pk_field:
|
|
|
|
restore_pk_field.primary_key = True
|
|
|
|
|
2014-07-16 17:06:33 +08:00
|
|
|
def delete_model(self, model, handle_autom2m=True):
|
|
|
|
if handle_autom2m:
|
|
|
|
super(DatabaseSchemaEditor, self).delete_model(model)
|
|
|
|
else:
|
|
|
|
# Delete the table (and only that)
|
|
|
|
self.execute(self.sql_delete_table % {
|
|
|
|
"table": self.quote_name(model._meta.db_table),
|
|
|
|
})
|
|
|
|
|
2013-05-30 00:47:10 +08:00
|
|
|
def add_field(self, model, field):
|
2012-09-08 00:51:11 +08:00
|
|
|
"""
|
|
|
|
Creates a field on a model.
|
|
|
|
Usually involves adding a column, but may involve adding a
|
|
|
|
table instead (for M2M fields)
|
|
|
|
"""
|
|
|
|
# Special-case implicit M2M tables
|
2015-02-26 22:19:17 +08:00
|
|
|
if field.many_to_many and field.remote_field.through._meta.auto_created:
|
|
|
|
return self.create_model(field.remote_field.through)
|
2012-09-08 00:51:11 +08:00
|
|
|
self._remake_table(model, create_fields=[field])
|
|
|
|
|
2013-05-30 00:47:10 +08:00
|
|
|
def remove_field(self, model, field):
|
2012-09-08 00:51:11 +08:00
|
|
|
"""
|
|
|
|
Removes a field from a model. Usually involves deleting a column,
|
|
|
|
but for M2Ms may involve deleting a table.
|
|
|
|
"""
|
2014-04-08 13:30:25 +08:00
|
|
|
# M2M fields are a special case
|
2015-01-09 06:51:00 +08:00
|
|
|
if field.many_to_many:
|
2014-04-08 13:30:25 +08:00
|
|
|
# For implicit M2M tables, delete the auto-created table
|
2015-02-26 22:19:17 +08:00
|
|
|
if field.remote_field.through._meta.auto_created:
|
|
|
|
self.delete_model(field.remote_field.through)
|
2014-04-08 13:30:25 +08:00
|
|
|
# For explicit "through" M2M fields, do nothing
|
2012-09-08 00:51:11 +08:00
|
|
|
# For everything else, remake.
|
2014-04-08 13:30:25 +08:00
|
|
|
else:
|
2014-08-10 19:31:06 +08:00
|
|
|
# It might not actually have a column behind it
|
|
|
|
if field.db_parameters(connection=self.connection)['type'] is None:
|
|
|
|
return
|
2014-04-08 13:30:25 +08:00
|
|
|
self._remake_table(model, delete_fields=[field])
|
2012-09-08 00:51:11 +08:00
|
|
|
|
2014-09-04 20:15:09 +08:00
|
|
|
def _alter_field(self, model, old_field, new_field, old_type, new_type,
|
|
|
|
old_db_params, new_db_params, strict=False):
|
2014-05-20 23:25:59 +08:00
|
|
|
"""Actually perform a "physical" (non-ManyToMany) field update."""
|
2012-09-08 00:51:11 +08:00
|
|
|
# Alter by remaking table
|
|
|
|
self._remake_table(model, alter_fields=[(old_field, new_field)])
|
|
|
|
|
2014-12-01 01:11:52 +08:00
|
|
|
def alter_index_together(self, model, old_index_together, new_index_together):
|
|
|
|
"""
|
|
|
|
Deals with a model changing its index_together.
|
|
|
|
Note: The input index_togethers must be doubly-nested, not the single-
|
|
|
|
nested ["foo", "bar"] format.
|
|
|
|
"""
|
|
|
|
self._remake_table(model, override_indexes=new_index_together)
|
|
|
|
|
2012-09-08 00:51:11 +08:00
|
|
|
def alter_unique_together(self, model, old_unique_together, new_unique_together):
|
2012-09-18 17:37:30 +08:00
|
|
|
"""
|
|
|
|
Deals with a model changing its unique_together.
|
|
|
|
Note: The input unique_togethers must be doubly-nested, not the single-
|
|
|
|
nested ["foo", "bar"] format.
|
|
|
|
"""
|
2012-09-08 00:51:11 +08:00
|
|
|
self._remake_table(model, override_uniques=new_unique_together)
|
2012-09-08 02:39:22 +08:00
|
|
|
|
|
|
|
def _alter_many_to_many(self, model, old_field, new_field, strict):
|
2012-09-18 17:37:30 +08:00
|
|
|
"""
|
|
|
|
Alters M2Ms to repoint their to= endpoints.
|
|
|
|
"""
|
2015-02-26 22:19:17 +08:00
|
|
|
if old_field.remote_field.through._meta.db_table == new_field.remote_field.through._meta.db_table:
|
2014-05-20 23:25:59 +08:00
|
|
|
# The field name didn't change, but some options did; we have to propagate this altering.
|
|
|
|
self._remake_table(
|
2015-02-26 22:19:17 +08:00
|
|
|
old_field.remote_field.through,
|
2014-05-20 23:25:59 +08:00
|
|
|
alter_fields=[(
|
|
|
|
# We need the field that points to the target model, so we can tell alter_field to change it -
|
|
|
|
# this is m2m_reverse_field_name() (as opposed to m2m_field_name, which points to our model)
|
2015-02-26 22:19:17 +08:00
|
|
|
old_field.remote_field.through._meta.get_field(old_field.m2m_reverse_field_name()),
|
|
|
|
new_field.remote_field.through._meta.get_field(new_field.m2m_reverse_field_name()),
|
2014-05-20 23:25:59 +08:00
|
|
|
)],
|
|
|
|
override_uniques=(new_field.m2m_field_name(), new_field.m2m_reverse_field_name()),
|
|
|
|
)
|
2014-03-20 12:08:28 +08:00
|
|
|
return
|
|
|
|
|
2012-09-08 02:39:22 +08:00
|
|
|
# Make a new through table
|
2015-02-26 22:19:17 +08:00
|
|
|
self.create_model(new_field.remote_field.through)
|
2012-09-08 02:39:22 +08:00
|
|
|
# Copy the data across
|
2013-09-07 04:27:51 +08:00
|
|
|
self.execute("INSERT INTO %s (%s) SELECT %s FROM %s" % (
|
2015-02-26 22:19:17 +08:00
|
|
|
self.quote_name(new_field.remote_field.through._meta.db_table),
|
2012-09-08 02:39:22 +08:00
|
|
|
', '.join([
|
|
|
|
"id",
|
|
|
|
new_field.m2m_column_name(),
|
|
|
|
new_field.m2m_reverse_name(),
|
|
|
|
]),
|
|
|
|
', '.join([
|
|
|
|
"id",
|
|
|
|
old_field.m2m_column_name(),
|
|
|
|
old_field.m2m_reverse_name(),
|
|
|
|
]),
|
2015-02-26 22:19:17 +08:00
|
|
|
self.quote_name(old_field.remote_field.through._meta.db_table),
|
2012-09-08 02:39:22 +08:00
|
|
|
))
|
|
|
|
# Delete the old through table
|
2015-02-26 22:19:17 +08:00
|
|
|
self.delete_model(old_field.remote_field.through)
|