浏览代码

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

Mariusz Felisiak 5 年之前
父节点
当前提交
e2c6a0858d

+ 1 - 0
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
     )
 

+ 4 - 2
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):

+ 21 - 7
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)

+ 9 - 4
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()

+ 5 - 0
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