mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #36624 -- Dropped support for MySQL < 8.4.
This commit is contained in:
		| @@ -76,8 +76,6 @@ class MySQLOperations(BaseSpatialOperations, DatabaseOperations): | ||||
|         if is_mariadb: | ||||
|             if self.connection.mysql_version < (12, 0, 1): | ||||
|                 disallowed_aggregates.insert(0, models.Collect) | ||||
|         elif self.connection.mysql_version < (8, 0, 24): | ||||
|             disallowed_aggregates.insert(0, models.Collect) | ||||
|         return tuple(disallowed_aggregates) | ||||
|  | ||||
|     function_names = { | ||||
|   | ||||
| @@ -10,16 +10,6 @@ logger = logging.getLogger("django.contrib.gis") | ||||
| class MySQLGISSchemaEditor(DatabaseSchemaEditor): | ||||
|     sql_add_spatial_index = "CREATE SPATIAL INDEX %(index)s ON %(table)s(%(column)s)" | ||||
|  | ||||
|     def skip_default(self, field): | ||||
|         # Geometry fields are stored as BLOB/TEXT, for which MySQL < 8.0.13 | ||||
|         # doesn't support defaults. | ||||
|         if ( | ||||
|             isinstance(field, GeometryField) | ||||
|             and not self._supports_limited_data_type_defaults | ||||
|         ): | ||||
|             return True | ||||
|         return super().skip_default(field) | ||||
|  | ||||
|     def quote_value(self, value): | ||||
|         if isinstance(value, self.connection.ops.Adapter): | ||||
|             return super().quote_value(str(value)) | ||||
|   | ||||
| @@ -144,11 +144,8 @@ class DatabaseWrapper(BaseDatabaseWrapper): | ||||
|             _data_types["UUIDField"] = "uuid" | ||||
|         return _data_types | ||||
|  | ||||
|     # For these data types: | ||||
|     # - MySQL < 8.0.13 doesn't accept default values and implicitly treats them | ||||
|     #   as nullable | ||||
|     # - all versions of MySQL and MariaDB don't support full width database | ||||
|     #   indexes | ||||
|     # For these data types MySQL and MariaDB don't support full width database | ||||
|     # indexes. | ||||
|     _limited_data_types = ( | ||||
|         "tinyblob", | ||||
|         "blob", | ||||
|   | ||||
| @@ -66,7 +66,7 @@ class DatabaseFeatures(BaseDatabaseFeatures): | ||||
|         if self.connection.mysql_is_mariadb: | ||||
|             return (10, 6) | ||||
|         else: | ||||
|             return (8, 0, 11) | ||||
|             return (8, 4) | ||||
|  | ||||
|     @cached_property | ||||
|     def test_collations(self): | ||||
| @@ -104,24 +104,6 @@ class DatabaseFeatures(BaseDatabaseFeatures): | ||||
|                 "update.tests.AdvancedTests.test_update_ordered_by_m2m_annotation_desc", | ||||
|             }, | ||||
|         } | ||||
|         if not self.supports_explain_analyze: | ||||
|             skips.update( | ||||
|                 { | ||||
|                     "MariaDB and MySQL >= 8.0.18 specific.": { | ||||
|                         "queries.test_explain.ExplainTests.test_mysql_analyze", | ||||
|                     }, | ||||
|                 } | ||||
|             ) | ||||
|         if self.connection.mysql_version < (8, 0, 31): | ||||
|             skips.update( | ||||
|                 { | ||||
|                     "Nesting of UNIONs at the right-hand side is not supported on " | ||||
|                     "MySQL < 8.0.31": { | ||||
|                         "queries.test_qs_combinators.QuerySetSetOperationTests." | ||||
|                         "test_union_nested" | ||||
|                     }, | ||||
|                 } | ||||
|             ) | ||||
|         if not self.connection.mysql_is_mariadb: | ||||
|             skips.update( | ||||
|                 { | ||||
| @@ -186,44 +168,16 @@ class DatabaseFeatures(BaseDatabaseFeatures): | ||||
|     def is_sql_auto_is_null_enabled(self): | ||||
|         return self.connection.mysql_server_data["sql_auto_is_null"] | ||||
|  | ||||
|     @cached_property | ||||
|     def supports_column_check_constraints(self): | ||||
|         if self.connection.mysql_is_mariadb: | ||||
|             return True | ||||
|         return self.connection.mysql_version >= (8, 0, 16) | ||||
|  | ||||
|     supports_table_check_constraints = property( | ||||
|         operator.attrgetter("supports_column_check_constraints") | ||||
|     ) | ||||
|  | ||||
|     @cached_property | ||||
|     def can_introspect_check_constraints(self): | ||||
|         if self.connection.mysql_is_mariadb: | ||||
|             return True | ||||
|         return self.connection.mysql_version >= (8, 0, 16) | ||||
|  | ||||
|     @cached_property | ||||
|     def has_select_for_update_of(self): | ||||
|         return not self.connection.mysql_is_mariadb | ||||
|  | ||||
|     @cached_property | ||||
|     def supports_explain_analyze(self): | ||||
|         return self.connection.mysql_is_mariadb or self.connection.mysql_version >= ( | ||||
|             8, | ||||
|             0, | ||||
|             18, | ||||
|         ) | ||||
|  | ||||
|     @cached_property | ||||
|     def supported_explain_formats(self): | ||||
|         # Alias MySQL's TRADITIONAL to TEXT for consistency with other | ||||
|         # backends. | ||||
|         formats = {"JSON", "TEXT", "TRADITIONAL"} | ||||
|         if not self.connection.mysql_is_mariadb and self.connection.mysql_version >= ( | ||||
|             8, | ||||
|             0, | ||||
|             16, | ||||
|         ): | ||||
|         if not self.connection.mysql_is_mariadb: | ||||
|             formats.add("TREE") | ||||
|         return formats | ||||
|  | ||||
| @@ -262,24 +216,8 @@ class DatabaseFeatures(BaseDatabaseFeatures): | ||||
|         return ( | ||||
|             not self.connection.mysql_is_mariadb | ||||
|             and self._mysql_storage_engine != "MyISAM" | ||||
|             and self.connection.mysql_version >= (8, 0, 13) | ||||
|         ) | ||||
|  | ||||
|     @cached_property | ||||
|     def supports_select_intersection(self): | ||||
|         is_mariadb = self.connection.mysql_is_mariadb | ||||
|         return is_mariadb or self.connection.mysql_version >= (8, 0, 31) | ||||
|  | ||||
|     supports_select_difference = property( | ||||
|         operator.attrgetter("supports_select_intersection") | ||||
|     ) | ||||
|  | ||||
|     @cached_property | ||||
|     def supports_expression_defaults(self): | ||||
|         if self.connection.mysql_is_mariadb: | ||||
|             return True | ||||
|         return self.connection.mysql_version >= (8, 0, 13) | ||||
|  | ||||
|     @cached_property | ||||
|     def has_native_uuid_field(self): | ||||
|         is_mariadb = self.connection.mysql_is_mariadb | ||||
|   | ||||
| @@ -349,7 +349,7 @@ class DatabaseOperations(BaseDatabaseOperations): | ||||
|             format = "TREE" | ||||
|         analyze = options.pop("analyze", False) | ||||
|         prefix = super().explain_query_prefix(format, **options) | ||||
|         if analyze and self.connection.features.supports_explain_analyze: | ||||
|         if analyze: | ||||
|             # MariaDB uses ANALYZE instead of EXPLAIN ANALYZE. | ||||
|             prefix = ( | ||||
|                 "ANALYZE" if self.connection.mysql_is_mariadb else prefix + " ANALYZE" | ||||
| @@ -407,15 +407,11 @@ class DatabaseOperations(BaseDatabaseOperations): | ||||
|     def on_conflict_suffix_sql(self, fields, on_conflict, update_fields, unique_fields): | ||||
|         if on_conflict == OnConflict.UPDATE: | ||||
|             conflict_suffix_sql = "ON DUPLICATE KEY UPDATE %(fields)s" | ||||
|             # The use of VALUES() is deprecated in MySQL 8.0.20+. Instead, use | ||||
|             # aliases for the new row and its columns available in MySQL | ||||
|             # 8.0.19+. | ||||
|             # The use of VALUES() is not supported in MySQL. Instead, use | ||||
|             # aliases for the new row and its columns. | ||||
|             if not self.connection.mysql_is_mariadb: | ||||
|                 if self.connection.mysql_version >= (8, 0, 19): | ||||
|                 conflict_suffix_sql = f"AS new {conflict_suffix_sql}" | ||||
|                 field_sql = "%(field)s = new.%(field)s" | ||||
|                 else: | ||||
|                     field_sql = "%(field)s = VALUES(%(field)s)" | ||||
|             # Use VALUE() on MariaDB. | ||||
|             else: | ||||
|                 field_sql = "%(field)s = VALUE(%(field)s)" | ||||
|   | ||||
| @@ -65,13 +65,10 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): | ||||
|         default_is_empty = self.effective_default(field) in ("", b"") | ||||
|         if default_is_empty and self._is_text_or_blob(field): | ||||
|             return True | ||||
|         if not self._supports_limited_data_type_defaults: | ||||
|             return self._is_limited_data_type(field) | ||||
|         return False | ||||
|  | ||||
|     def skip_default_on_alter(self, field): | ||||
|         default_is_empty = self.effective_default(field) in ("", b"") | ||||
|         if default_is_empty and self._is_text_or_blob(field): | ||||
|         if self.skip_default(field): | ||||
|             return True | ||||
|         if self._is_limited_data_type(field) and not self.connection.mysql_is_mariadb: | ||||
|             # MySQL doesn't support defaults for BLOB and TEXT in the | ||||
| @@ -79,19 +76,8 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): | ||||
|             return True | ||||
|         return False | ||||
|  | ||||
|     @property | ||||
|     def _supports_limited_data_type_defaults(self): | ||||
|         # MariaDB and MySQL >= 8.0.13 support defaults for BLOB and TEXT. | ||||
|         if self.connection.mysql_is_mariadb: | ||||
|             return True | ||||
|         return self.connection.mysql_version >= (8, 0, 13) | ||||
|  | ||||
|     def _column_default_sql(self, field): | ||||
|         if ( | ||||
|             not self.connection.mysql_is_mariadb | ||||
|             and self._supports_limited_data_type_defaults | ||||
|             and self._is_limited_data_type(field) | ||||
|         ): | ||||
|         if not self.connection.mysql_is_mariadb and self._is_limited_data_type(field): | ||||
|             # MySQL supports defaults for BLOB and TEXT columns only if the | ||||
|             # default value is written as an expression i.e. in parentheses. | ||||
|             return "(%s)" | ||||
|   | ||||
| @@ -58,7 +58,7 @@ supported versions, and any notes for each of the supported database backends: | ||||
| Database            Library Requirements            Supported Versions  Notes | ||||
| ==================  ==============================  ==================  ========================================= | ||||
| PostgreSQL          GEOS, GDAL, PROJ, PostGIS       15+                 Requires PostGIS. | ||||
| MySQL               GEOS, GDAL                      8.0.11+             :ref:`Limited functionality <mysql-spatial-limitations>`. | ||||
| MySQL               GEOS, GDAL                      8.4+                :ref:`Limited functionality <mysql-spatial-limitations>`. | ||||
| Oracle              GEOS, GDAL                      19+                 XE not supported. | ||||
| SQLite              GEOS, GDAL, PROJ, SpatiaLite    3.37.0+             Requires SpatiaLite 4.3+ | ||||
| ==================  ==============================  ==================  ========================================= | ||||
|   | ||||
| @@ -434,7 +434,7 @@ MySQL notes | ||||
| Version support | ||||
| --------------- | ||||
|  | ||||
| Django supports MySQL 8.0.11 and higher. | ||||
| Django supports MySQL 8.4 and higher. | ||||
|  | ||||
| Django's ``inspectdb`` feature uses the ``information_schema`` database, which | ||||
| contains detailed data on all database schemas. | ||||
|   | ||||
| @@ -63,10 +63,9 @@ and the ``weight`` rounded to the nearest integer. | ||||
|     error. This means that functions such as | ||||
|     :class:`Concat() <django.db.models.functions.Concat>` aren't accepted. | ||||
|  | ||||
| .. admonition:: MySQL and MariaDB | ||||
| .. admonition:: MariaDB | ||||
|  | ||||
|     Functional indexes are ignored with MySQL < 8.0.13 and MariaDB as neither | ||||
|     supports them. | ||||
|     Functional indexes are unsupported and ignored with MariaDB. | ||||
|  | ||||
| ``fields`` | ||||
| ---------- | ||||
|   | ||||
| @@ -3161,8 +3161,8 @@ Pass these flags as keyword arguments. For example, when using PostgreSQL: | ||||
|  | ||||
| On some databases, flags may cause the query to be executed which could have | ||||
| adverse effects on your database. For example, the ``ANALYZE`` flag supported | ||||
| by MariaDB, MySQL 8.0.18+, and PostgreSQL could result in changes to data if | ||||
| there are triggers or if a function is called, even for a ``SELECT`` query. | ||||
| by MariaDB, MySQL, and PostgreSQL could result in changes to data if there are | ||||
| triggers or if a function is called, even for a ``SELECT`` query. | ||||
|  | ||||
| .. _field-lookups: | ||||
|  | ||||
|   | ||||
| @@ -319,6 +319,12 @@ Dropped support for PostgreSQL 14 | ||||
| Upstream support for PostgreSQL 14 ends in November 2026. Django 6.1 supports | ||||
| PostgreSQL 15 and higher. | ||||
|  | ||||
| Dropped support for MySQL < 8.4 | ||||
| ------------------------------- | ||||
|  | ||||
| Upstream support for MySQL 8.0 ends in April 2026, and MySQL 8.1-8.3 are | ||||
| short-term innovation releases. Django 6.1 supports MySQL 8.4 and higher. | ||||
|  | ||||
| Miscellaneous | ||||
| ------------- | ||||
|  | ||||
|   | ||||
| @@ -109,8 +109,8 @@ class Tests(TestCase): | ||||
|             mocked_get_database_version.return_value = (10, 5) | ||||
|             msg = "MariaDB 10.6 or later is required (found 10.5)." | ||||
|         else: | ||||
|             mocked_get_database_version.return_value = (8, 0, 4) | ||||
|             msg = "MySQL 8.0.11 or later is required (found 8.0.4)." | ||||
|             mocked_get_database_version.return_value = (8, 0, 31) | ||||
|             msg = "MySQL 8.4 or later is required (found 8.0.31)." | ||||
|  | ||||
|         with self.assertRaisesMessage(NotSupportedError, msg): | ||||
|             connection.check_database_version_supported() | ||||
|   | ||||
| @@ -159,9 +159,7 @@ class ExplainTests(TestCase): | ||||
|         self.assertEqual(len(captured_queries), 1) | ||||
|         self.assertIn("FORMAT=TRADITIONAL", captured_queries[0]["sql"]) | ||||
|  | ||||
|     @unittest.skipUnless( | ||||
|         connection.vendor == "mysql", "MariaDB and MySQL >= 8.0.18 specific." | ||||
|     ) | ||||
|     @unittest.skipUnless(connection.vendor == "mysql", "MySQL specific") | ||||
|     def test_mysql_analyze(self): | ||||
|         qs = Tag.objects.filter(name="test") | ||||
|         with CaptureQueriesContext(connection) as captured_queries: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user