Fixed #30750 -- Added support for check constraints on MySQL 8.0.16+.

This commit is contained in:
Mariusz Felisiak 2019-09-04 16:54:27 +02:00
parent b93d786251
commit e2c6a0858d
5 changed files with 40 additions and 13 deletions

View File

@ -61,6 +61,7 @@ class CursorWrapper:
codes_for_integrityerror = ( codes_for_integrityerror = (
1048, # Column cannot be null 1048, # Column cannot be null
1690, # BIGINT UNSIGNED value is out of range 1690, # BIGINT UNSIGNED value is out of range
3819, # CHECK constraint is violated
4025, # CHECK constraint failed 4025, # CHECK constraint failed
) )

View File

@ -89,7 +89,9 @@ class DatabaseFeatures(BaseDatabaseFeatures):
@cached_property @cached_property
def supports_column_check_constraints(self): def supports_column_check_constraints(self):
return self.connection.mysql_is_mariadb and self.connection.mysql_version >= (10, 2, 1) if self.connection.mysql_is_mariadb:
return self.connection.mysql_version >= (10, 2, 1)
return self.connection.mysql_version >= (8, 0, 16)
supports_table_check_constraints = property(operator.attrgetter('supports_column_check_constraints')) supports_table_check_constraints = property(operator.attrgetter('supports_column_check_constraints'))
@ -99,7 +101,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
version = self.connection.mysql_version version = self.connection.mysql_version
if (version >= (10, 2, 22) and version < (10, 3)) or version >= (10, 3, 10): if (version >= (10, 2, 22) and version < (10, 3)) or version >= (10, 3, 10):
return True return True
return False return self.connection.mysql_version >= (8, 0, 16)
@cached_property @cached_property
def has_select_for_update_skip_locked(self): def has_select_for_update_skip_locked(self):

View File

@ -209,13 +209,27 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
if self.connection.features.can_introspect_check_constraints: if self.connection.features.can_introspect_check_constraints:
unnamed_constraints_index = 0 unnamed_constraints_index = 0
columns = {info.name for info in self.get_table_description(cursor, table_name)} columns = {info.name for info in self.get_table_description(cursor, table_name)}
type_query = """ if self.connection.mysql_is_mariadb:
SELECT c.constraint_name, c.check_clause type_query = """
FROM information_schema.check_constraints AS c SELECT c.constraint_name, c.check_clause
WHERE FROM information_schema.check_constraints AS c
c.constraint_schema = DATABASE() AND WHERE
c.table_name = %s c.constraint_schema = DATABASE() AND
""" c.table_name = %s
"""
else:
type_query = """
SELECT cc.constraint_name, cc.check_clause
FROM
information_schema.check_constraints AS cc,
information_schema.table_constraints AS tc
WHERE
cc.constraint_schema = DATABASE() AND
tc.table_schema = cc.constraint_schema AND
cc.constraint_name = tc.constraint_name AND
tc.constraint_type = 'CHECK' AND
tc.table_name = %s
"""
cursor.execute(type_query, [table_name]) cursor.execute(type_query, [table_name])
for constraint, check_clause in cursor.fetchall(): for constraint, check_clause in cursor.fetchall():
constraint_columns = self._parse_constraint_columns(check_clause, columns) constraint_columns = self._parse_constraint_columns(check_clause, columns)

View File

@ -28,10 +28,15 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
sql_delete_pk = "ALTER TABLE %(table)s DROP PRIMARY KEY" sql_delete_pk = "ALTER TABLE %(table)s DROP PRIMARY KEY"
sql_create_index = 'CREATE INDEX %(name)s ON %(table)s (%(columns)s)%(extra)s' sql_create_index = 'CREATE INDEX %(name)s ON %(table)s (%(columns)s)%(extra)s'
# The name of the column check constraint is the same as the field name on
# MariaDB. Adding IF EXISTS clause prevents migrations crash. Constraint is @property
# removed during a "MODIFY" column statement. def sql_delete_check(self):
sql_delete_check = 'ALTER TABLE %(table)s DROP CONSTRAINT IF EXISTS %(name)s' if self.connection.mysql_is_mariadb:
# The name of the column check constraint is the same as the field
# name on MariaDB. Adding IF EXISTS clause prevents migrations
# crash. Constraint is removed during a "MODIFY" column statement.
return 'ALTER TABLE %(table)s DROP CONSTRAINT IF EXISTS %(name)s'
return 'ALTER TABLE %(table)s DROP CHECK %(name)s'
def quote_value(self, value): def quote_value(self, value):
self.connection.ensure_connection() self.connection.ensure_connection()

View File

@ -353,6 +353,8 @@ Models
* :attr:`.FileField.upload_to` now supports :class:`pathlib.Path`. * :attr:`.FileField.upload_to` now supports :class:`pathlib.Path`.
* :class:`~django.db.models.CheckConstraint` is now supported on MySQL 8.0.16+.
Requests and Responses Requests and Responses
~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~
@ -579,6 +581,9 @@ Miscellaneous
:ref:`x-content-type-options` header on all responses that do not already :ref:`x-content-type-options` header on all responses that do not already
have it. have it.
* On MySQL 8.0.16+, ``PositiveIntegerField`` and ``PositiveSmallIntegerField``
now include a check constraint to prevent negative values in the database.
.. _deprecated-features-3.0: .. _deprecated-features-3.0:
Features deprecated in 3.0 Features deprecated in 3.0