mirror of
https://github.com/django/django.git
synced 2026-02-09 02:49:25 +08: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)"
|
||||
conflict_suffix_sql = f"AS new {conflict_suffix_sql}"
|
||||
field_sql = "%(field)s = new.%(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