From e2c6a0858d7d9ad85eda353076a5b46608b704a9 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 4 Sep 2019 16:54:27 +0200 Subject: [PATCH] Fixed #30750 -- Added support for check constraints on MySQL 8.0.16+. --- django/db/backends/mysql/base.py | 1 + django/db/backends/mysql/features.py | 6 +++-- django/db/backends/mysql/introspection.py | 28 +++++++++++++++++------ django/db/backends/mysql/schema.py | 13 +++++++---- docs/releases/3.0.txt | 5 ++++ 5 files changed, 40 insertions(+), 13 deletions(-) diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index febc246e09..d60ff7937d 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -61,6 +61,7 @@ class CursorWrapper: codes_for_integrityerror = ( 1048, # Column cannot be null 1690, # BIGINT UNSIGNED value is out of range + 3819, # CHECK constraint is violated 4025, # CHECK constraint failed ) diff --git a/django/db/backends/mysql/features.py b/django/db/backends/mysql/features.py index 229e26a750..5800ad6c95 100644 --- a/django/db/backends/mysql/features.py +++ b/django/db/backends/mysql/features.py @@ -89,7 +89,9 @@ class DatabaseFeatures(BaseDatabaseFeatures): @cached_property 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')) @@ -99,7 +101,7 @@ class DatabaseFeatures(BaseDatabaseFeatures): version = self.connection.mysql_version if (version >= (10, 2, 22) and version < (10, 3)) or version >= (10, 3, 10): return True - return False + return self.connection.mysql_version >= (8, 0, 16) @cached_property def has_select_for_update_skip_locked(self): diff --git a/django/db/backends/mysql/introspection.py b/django/db/backends/mysql/introspection.py index 82637d15fb..a014b87916 100644 --- a/django/db/backends/mysql/introspection.py +++ b/django/db/backends/mysql/introspection.py @@ -209,13 +209,27 @@ class DatabaseIntrospection(BaseDatabaseIntrospection): if self.connection.features.can_introspect_check_constraints: unnamed_constraints_index = 0 columns = {info.name for info in self.get_table_description(cursor, table_name)} - type_query = """ - SELECT c.constraint_name, c.check_clause - FROM information_schema.check_constraints AS c - WHERE - c.constraint_schema = DATABASE() AND - c.table_name = %s - """ + if self.connection.mysql_is_mariadb: + type_query = """ + SELECT c.constraint_name, c.check_clause + FROM information_schema.check_constraints AS c + WHERE + 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]) for constraint, check_clause in cursor.fetchall(): constraint_columns = self._parse_constraint_columns(check_clause, columns) diff --git a/django/db/backends/mysql/schema.py b/django/db/backends/mysql/schema.py index d138606791..d379965c27 100644 --- a/django/db/backends/mysql/schema.py +++ b/django/db/backends/mysql/schema.py @@ -28,10 +28,15 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): sql_delete_pk = "ALTER TABLE %(table)s DROP PRIMARY KEY" 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 - # removed during a "MODIFY" column statement. - sql_delete_check = 'ALTER TABLE %(table)s DROP CONSTRAINT IF EXISTS %(name)s' + + @property + def sql_delete_check(self): + 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): self.connection.ensure_connection() diff --git a/docs/releases/3.0.txt b/docs/releases/3.0.txt index 2868e37e16..570effe6b3 100644 --- a/docs/releases/3.0.txt +++ b/docs/releases/3.0.txt @@ -353,6 +353,8 @@ Models * :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 ~~~~~~~~~~~~~~~~~~~~~~ @@ -579,6 +581,9 @@ Miscellaneous :ref:`x-content-type-options` header on all responses that do not already 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: Features deprecated in 3.0