diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index b13fb1e9d53..3c998192180 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -39,6 +39,12 @@ class BaseDatabaseWrapper(object): """ Represents a database connection. """ + # Mapping of Field objects to their column types. + data_types = {} + # Mapping of Field objects to their SQL suffix such as AUTOINCREMENT. + data_types_suffix = {} + # Mapping of Field objects to their SQL for CHECK constraints. + data_type_check_constraints = {} ops = None vendor = 'unknown' SchemaEditorClass = None diff --git a/django/db/backends/creation.py b/django/db/backends/creation.py index 6ccc6a32367..5e0248d4df2 100644 --- a/django/db/backends/creation.py +++ b/django/db/backends/creation.py @@ -26,10 +26,6 @@ class BaseDatabaseCreation(object): Fields, the SQL used to create and destroy tables, and the creation and destruction of test databases. """ - data_types = {} - data_types_suffix = {} - data_type_check_constraints = {} - def __init__(self, connection): self.connection = connection diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index 6cfb1368791..3b258d35f3c 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -415,6 +415,45 @@ class DatabaseOperations(BaseDatabaseOperations): class DatabaseWrapper(BaseDatabaseWrapper): vendor = 'mysql' + # This dictionary maps Field objects to their associated MySQL column + # types, as strings. Column-type strings can contain format strings; they'll + # be interpolated against the values of Field.__dict__ before being output. + # If a column type is set to None, it won't be included in the output. + _data_types = { + 'AutoField': 'integer AUTO_INCREMENT', + 'BinaryField': 'longblob', + 'BooleanField': 'bool', + 'CharField': 'varchar(%(max_length)s)', + 'CommaSeparatedIntegerField': 'varchar(%(max_length)s)', + 'DateField': 'date', + 'DateTimeField': 'datetime', + 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)', + 'DurationField': 'bigint', + 'FileField': 'varchar(%(max_length)s)', + 'FilePathField': 'varchar(%(max_length)s)', + 'FloatField': 'double precision', + 'IntegerField': 'integer', + 'BigIntegerField': 'bigint', + 'IPAddressField': 'char(15)', + 'GenericIPAddressField': 'char(39)', + 'NullBooleanField': 'bool', + 'OneToOneField': 'integer', + 'PositiveIntegerField': 'integer UNSIGNED', + 'PositiveSmallIntegerField': 'smallint UNSIGNED', + 'SlugField': 'varchar(%(max_length)s)', + 'SmallIntegerField': 'smallint', + 'TextField': 'longtext', + 'TimeField': 'time', + 'UUIDField': 'char(32)', + } + + @cached_property + def data_types(self): + if self.features.supports_microsecond_precision: + return dict(self._data_types, DateTimeField='datetime(6)', TimeField='time(6)') + else: + return self._data_types + operators = { 'exact': '= %s', 'iexact': 'LIKE %s', diff --git a/django/db/backends/mysql/creation.py b/django/db/backends/mysql/creation.py index efa0ea06e75..8cce569058b 100644 --- a/django/db/backends/mysql/creation.py +++ b/django/db/backends/mysql/creation.py @@ -1,46 +1,7 @@ from django.db.backends.creation import BaseDatabaseCreation -from django.utils.functional import cached_property class DatabaseCreation(BaseDatabaseCreation): - # This dictionary maps Field objects to their associated MySQL column - # types, as strings. Column-type strings can contain format strings; they'll - # be interpolated against the values of Field.__dict__ before being output. - # If a column type is set to None, it won't be included in the output. - _data_types = { - 'AutoField': 'integer AUTO_INCREMENT', - 'BinaryField': 'longblob', - 'BooleanField': 'bool', - 'CharField': 'varchar(%(max_length)s)', - 'CommaSeparatedIntegerField': 'varchar(%(max_length)s)', - 'DateField': 'date', - 'DateTimeField': 'datetime', - 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)', - 'DurationField': 'bigint', - 'FileField': 'varchar(%(max_length)s)', - 'FilePathField': 'varchar(%(max_length)s)', - 'FloatField': 'double precision', - 'IntegerField': 'integer', - 'BigIntegerField': 'bigint', - 'IPAddressField': 'char(15)', - 'GenericIPAddressField': 'char(39)', - 'NullBooleanField': 'bool', - 'OneToOneField': 'integer', - 'PositiveIntegerField': 'integer UNSIGNED', - 'PositiveSmallIntegerField': 'smallint UNSIGNED', - 'SlugField': 'varchar(%(max_length)s)', - 'SmallIntegerField': 'smallint', - 'TextField': 'longtext', - 'TimeField': 'time', - 'UUIDField': 'char(32)', - } - - @cached_property - def data_types(self): - if self.connection.features.supports_microsecond_precision: - return dict(self._data_types, DateTimeField='datetime(6)', TimeField='time(6)') - else: - return self._data_types def sql_table_creation_suffix(self): suffix = [] diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index aadc7c96825..ea08ff466cd 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -575,6 +575,48 @@ class _UninitializedOperatorsDescriptor(object): class DatabaseWrapper(BaseDatabaseWrapper): vendor = 'oracle' + # This dictionary maps Field objects to their associated Oracle column + # types, as strings. Column-type strings can contain format strings; they'll + # be interpolated against the values of Field.__dict__ before being output. + # If a column type is set to None, it won't be included in the output. + # + # Any format strings starting with "qn_" are quoted before being used in the + # output (the "qn_" prefix is stripped before the lookup is performed. + data_types = { + 'AutoField': 'NUMBER(11)', + 'BinaryField': 'BLOB', + 'BooleanField': 'NUMBER(1)', + 'CharField': 'NVARCHAR2(%(max_length)s)', + 'CommaSeparatedIntegerField': 'VARCHAR2(%(max_length)s)', + 'DateField': 'DATE', + 'DateTimeField': 'TIMESTAMP', + 'DecimalField': 'NUMBER(%(max_digits)s, %(decimal_places)s)', + 'DurationField': 'INTERVAL DAY(9) TO SECOND(6)', + 'FileField': 'NVARCHAR2(%(max_length)s)', + 'FilePathField': 'NVARCHAR2(%(max_length)s)', + 'FloatField': 'DOUBLE PRECISION', + 'IntegerField': 'NUMBER(11)', + 'BigIntegerField': 'NUMBER(19)', + 'IPAddressField': 'VARCHAR2(15)', + 'GenericIPAddressField': 'VARCHAR2(39)', + 'NullBooleanField': 'NUMBER(1)', + 'OneToOneField': 'NUMBER(11)', + 'PositiveIntegerField': 'NUMBER(11)', + 'PositiveSmallIntegerField': 'NUMBER(11)', + 'SlugField': 'NVARCHAR2(%(max_length)s)', + 'SmallIntegerField': 'NUMBER(11)', + 'TextField': 'NCLOB', + 'TimeField': 'TIMESTAMP', + 'URLField': 'VARCHAR2(%(max_length)s)', + 'UUIDField': 'VARCHAR2(32)', + } + data_type_check_constraints = { + 'BooleanField': '%(qn_column)s IN (0,1)', + 'NullBooleanField': '(%(qn_column)s IN (0,1)) OR (%(qn_column)s IS NULL)', + 'PositiveIntegerField': '%(qn_column)s >= 0', + 'PositiveSmallIntegerField': '%(qn_column)s >= 0', + } + operators = _UninitializedOperatorsDescriptor() _standard_operators = { diff --git a/django/db/backends/oracle/creation.py b/django/db/backends/oracle/creation.py index e91cecf7d53..1c1c435fa1e 100644 --- a/django/db/backends/oracle/creation.py +++ b/django/db/backends/oracle/creation.py @@ -12,49 +12,6 @@ PASSWORD = 'Im_a_lumberjack' class DatabaseCreation(BaseDatabaseCreation): - # This dictionary maps Field objects to their associated Oracle column - # types, as strings. Column-type strings can contain format strings; they'll - # be interpolated against the values of Field.__dict__ before being output. - # If a column type is set to None, it won't be included in the output. - # - # Any format strings starting with "qn_" are quoted before being used in the - # output (the "qn_" prefix is stripped before the lookup is performed. - - data_types = { - 'AutoField': 'NUMBER(11)', - 'BinaryField': 'BLOB', - 'BooleanField': 'NUMBER(1)', - 'CharField': 'NVARCHAR2(%(max_length)s)', - 'CommaSeparatedIntegerField': 'VARCHAR2(%(max_length)s)', - 'DateField': 'DATE', - 'DateTimeField': 'TIMESTAMP', - 'DecimalField': 'NUMBER(%(max_digits)s, %(decimal_places)s)', - 'DurationField': 'INTERVAL DAY(9) TO SECOND(6)', - 'FileField': 'NVARCHAR2(%(max_length)s)', - 'FilePathField': 'NVARCHAR2(%(max_length)s)', - 'FloatField': 'DOUBLE PRECISION', - 'IntegerField': 'NUMBER(11)', - 'BigIntegerField': 'NUMBER(19)', - 'IPAddressField': 'VARCHAR2(15)', - 'GenericIPAddressField': 'VARCHAR2(39)', - 'NullBooleanField': 'NUMBER(1)', - 'OneToOneField': 'NUMBER(11)', - 'PositiveIntegerField': 'NUMBER(11)', - 'PositiveSmallIntegerField': 'NUMBER(11)', - 'SlugField': 'NVARCHAR2(%(max_length)s)', - 'SmallIntegerField': 'NUMBER(11)', - 'TextField': 'NCLOB', - 'TimeField': 'TIMESTAMP', - 'URLField': 'VARCHAR2(%(max_length)s)', - 'UUIDField': 'VARCHAR2(32)', - } - - data_type_check_constraints = { - 'BooleanField': '%(qn_column)s IN (0,1)', - 'NullBooleanField': '(%(qn_column)s IN (0,1)) OR (%(qn_column)s IS NULL)', - 'PositiveIntegerField': '%(qn_column)s >= 0', - 'PositiveSmallIntegerField': '%(qn_column)s >= 0', - } def _create_test_db(self, verbosity=1, autoclobber=False, keepdb=False): parameters = self._get_test_db_params() diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index c1dfa54c952..8b45b5d73ed 100644 --- a/django/db/backends/postgresql_psycopg2/base.py +++ b/django/db/backends/postgresql_psycopg2/base.py @@ -71,6 +71,41 @@ class DatabaseFeatures(BaseDatabaseFeatures): class DatabaseWrapper(BaseDatabaseWrapper): vendor = 'postgresql' + # This dictionary maps Field objects to their associated PostgreSQL column + # types, as strings. Column-type strings can contain format strings; they'll + # be interpolated against the values of Field.__dict__ before being output. + # If a column type is set to None, it won't be included in the output. + data_types = { + 'AutoField': 'serial', + 'BinaryField': 'bytea', + 'BooleanField': 'boolean', + 'CharField': 'varchar(%(max_length)s)', + 'CommaSeparatedIntegerField': 'varchar(%(max_length)s)', + 'DateField': 'date', + 'DateTimeField': 'timestamp with time zone', + 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)', + 'DurationField': 'interval', + 'FileField': 'varchar(%(max_length)s)', + 'FilePathField': 'varchar(%(max_length)s)', + 'FloatField': 'double precision', + 'IntegerField': 'integer', + 'BigIntegerField': 'bigint', + 'IPAddressField': 'inet', + 'GenericIPAddressField': 'inet', + 'NullBooleanField': 'boolean', + 'OneToOneField': 'integer', + 'PositiveIntegerField': 'integer', + 'PositiveSmallIntegerField': 'smallint', + 'SlugField': 'varchar(%(max_length)s)', + 'SmallIntegerField': 'smallint', + 'TextField': 'text', + 'TimeField': 'time', + 'UUIDField': 'uuid', + } + data_type_check_constraints = { + 'PositiveIntegerField': '"%(column)s" >= 0', + 'PositiveSmallIntegerField': '"%(column)s" >= 0', + } operators = { 'exact': '= %s', 'iexact': '= UPPER(%s)', diff --git a/django/db/backends/postgresql_psycopg2/creation.py b/django/db/backends/postgresql_psycopg2/creation.py index 45ce939165d..353d0b794f4 100644 --- a/django/db/backends/postgresql_psycopg2/creation.py +++ b/django/db/backends/postgresql_psycopg2/creation.py @@ -3,42 +3,6 @@ from django.db.backends.utils import truncate_name class DatabaseCreation(BaseDatabaseCreation): - # This dictionary maps Field objects to their associated PostgreSQL column - # types, as strings. Column-type strings can contain format strings; they'll - # be interpolated against the values of Field.__dict__ before being output. - # If a column type is set to None, it won't be included in the output. - data_types = { - 'AutoField': 'serial', - 'BinaryField': 'bytea', - 'BooleanField': 'boolean', - 'CharField': 'varchar(%(max_length)s)', - 'CommaSeparatedIntegerField': 'varchar(%(max_length)s)', - 'DateField': 'date', - 'DateTimeField': 'timestamp with time zone', - 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)', - 'DurationField': 'interval', - 'FileField': 'varchar(%(max_length)s)', - 'FilePathField': 'varchar(%(max_length)s)', - 'FloatField': 'double precision', - 'IntegerField': 'integer', - 'BigIntegerField': 'bigint', - 'IPAddressField': 'inet', - 'GenericIPAddressField': 'inet', - 'NullBooleanField': 'boolean', - 'OneToOneField': 'integer', - 'PositiveIntegerField': 'integer', - 'PositiveSmallIntegerField': 'smallint', - 'SlugField': 'varchar(%(max_length)s)', - 'SmallIntegerField': 'smallint', - 'TextField': 'text', - 'TimeField': 'time', - 'UUIDField': 'uuid', - } - - data_type_check_constraints = { - 'PositiveIntegerField': '"%(column)s" >= 0', - 'PositiveSmallIntegerField': '"%(column)s" >= 0', - } def sql_table_creation_suffix(self): test_settings = self.connection.settings_dict['TEST'] diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index 3560d4498ec..4e0cf0c9aa4 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -336,6 +336,39 @@ class DatabaseOperations(BaseDatabaseOperations): class DatabaseWrapper(BaseDatabaseWrapper): vendor = 'sqlite' + # SQLite doesn't actually support most of these types, but it "does the right + # thing" given more verbose field definitions, so leave them as is so that + # schema inspection is more useful. + data_types = { + 'AutoField': 'integer', + 'BinaryField': 'BLOB', + 'BooleanField': 'bool', + 'CharField': 'varchar(%(max_length)s)', + 'CommaSeparatedIntegerField': 'varchar(%(max_length)s)', + 'DateField': 'date', + 'DateTimeField': 'datetime', + 'DecimalField': 'decimal', + 'DurationField': 'bigint', + 'FileField': 'varchar(%(max_length)s)', + 'FilePathField': 'varchar(%(max_length)s)', + 'FloatField': 'real', + 'IntegerField': 'integer', + 'BigIntegerField': 'bigint', + 'IPAddressField': 'char(15)', + 'GenericIPAddressField': 'char(39)', + 'NullBooleanField': 'bool', + 'OneToOneField': 'integer', + 'PositiveIntegerField': 'integer unsigned', + 'PositiveSmallIntegerField': 'smallint unsigned', + 'SlugField': 'varchar(%(max_length)s)', + 'SmallIntegerField': 'smallint', + 'TextField': 'text', + 'TimeField': 'time', + 'UUIDField': 'char(32)', + } + data_types_suffix = { + 'AutoField': 'AUTOINCREMENT', + } # SQLite requires LIKE statements to include an ESCAPE clause if the value # being escaped has a percent or underscore in it. # See http://www.sqlite.org/lang_expr.html for an explanation. diff --git a/django/db/backends/sqlite3/creation.py b/django/db/backends/sqlite3/creation.py index ad7822c7184..3f1372630a1 100644 --- a/django/db/backends/sqlite3/creation.py +++ b/django/db/backends/sqlite3/creation.py @@ -7,39 +7,6 @@ from django.utils.six.moves import input class DatabaseCreation(BaseDatabaseCreation): - # SQLite doesn't actually support most of these types, but it "does the right - # thing" given more verbose field definitions, so leave them as is so that - # schema inspection is more useful. - data_types = { - 'AutoField': 'integer', - 'BinaryField': 'BLOB', - 'BooleanField': 'bool', - 'CharField': 'varchar(%(max_length)s)', - 'CommaSeparatedIntegerField': 'varchar(%(max_length)s)', - 'DateField': 'date', - 'DateTimeField': 'datetime', - 'DecimalField': 'decimal', - 'DurationField': 'bigint', - 'FileField': 'varchar(%(max_length)s)', - 'FilePathField': 'varchar(%(max_length)s)', - 'FloatField': 'real', - 'IntegerField': 'integer', - 'BigIntegerField': 'bigint', - 'IPAddressField': 'char(15)', - 'GenericIPAddressField': 'char(39)', - 'NullBooleanField': 'bool', - 'OneToOneField': 'integer', - 'PositiveIntegerField': 'integer unsigned', - 'PositiveSmallIntegerField': 'smallint unsigned', - 'SlugField': 'varchar(%(max_length)s)', - 'SmallIntegerField': 'smallint', - 'TextField': 'text', - 'TimeField': 'time', - 'UUIDField': 'char(32)', - } - data_types_suffix = { - 'AutoField': 'AUTOINCREMENT', - } def sql_for_pending_references(self, model, style, pending_references): "SQLite3 doesn't support constraints" diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 0a87f7812c5..bc7bbc1a30a 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -537,7 +537,7 @@ class Field(RegisterLookupMixin): # exactly which wacky database column type you want to use. data = DictWrapper(self.__dict__, connection.ops.quote_name, "qn_") try: - return connection.creation.data_types[self.get_internal_type()] % data + return connection.data_types[self.get_internal_type()] % data except KeyError: return None @@ -550,7 +550,7 @@ class Field(RegisterLookupMixin): data = DictWrapper(self.__dict__, connection.ops.quote_name, "qn_") type_string = self.db_type(connection) try: - check_string = connection.creation.data_type_check_constraints[self.get_internal_type()] % data + check_string = connection.data_type_check_constraints[self.get_internal_type()] % data except KeyError: check_string = None return { @@ -559,7 +559,7 @@ class Field(RegisterLookupMixin): } def db_type_suffix(self, connection): - return connection.creation.data_types_suffix.get(self.get_internal_type()) + return connection.data_types_suffix.get(self.get_internal_type()) def get_db_converters(self, connection): if hasattr(self, 'from_db_value'): diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index a34f3bf5b83..f1e8eceee61 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -849,6 +849,16 @@ Also private APIs ``django.template.base.compile_string()``, ``django.template.loader.find_template()``, and ``django.template.loader.get_template_from_string()`` were removed. +Database backend API +~~~~~~~~~~~~~~~~~~~~ + +The following changes to the database backend API are documented to assist +those writing third-party backends in updating their code: + +* The ``data_types``, ``data_types_suffix``, and + ``data_type_check_constraints`` attributes have moved from the + ``DatabaseCreation`` class to ``DatabaseWrapper``. + Miscellaneous ~~~~~~~~~~~~~ diff --git a/tests/commands_sql/tests.py b/tests/commands_sql/tests.py index 83c47a888fa..5eb9f0191a3 100644 --- a/tests/commands_sql/tests.py +++ b/tests/commands_sql/tests.py @@ -45,7 +45,7 @@ class SQLCommandsTestCase(TestCase): 'commands_sql_comment', 'commands_sql_book', 'commands_sql_book_comments' }) - @unittest.skipUnless('PositiveIntegerField' in connections[DEFAULT_DB_ALIAS].creation.data_type_check_constraints, 'Backend does not have checks.') + @unittest.skipUnless('PositiveIntegerField' in connections[DEFAULT_DB_ALIAS].data_type_check_constraints, 'Backend does not have checks.') def test_sql_create_check(self): """Regression test for #23416 -- Check that db_params['check'] is respected.""" app_config = apps.get_app_config('commands_sql')