summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
Diffstat (limited to 'django')
-rw-r--r--django/db/backends/base/base.py34
-rw-r--r--django/db/backends/base/features.py2
-rw-r--r--django/db/backends/mysql/base.py4
-rw-r--r--django/db/backends/mysql/features.py7
-rw-r--r--django/db/backends/oracle/base.py4
-rw-r--r--django/db/backends/oracle/features.py1
-rw-r--r--django/db/backends/postgresql/base.py8
-rw-r--r--django/db/backends/postgresql/features.py1
-rw-r--r--django/db/backends/sqlite3/base.py15
-rw-r--r--django/db/backends/sqlite3/features.py1
10 files changed, 60 insertions, 17 deletions
diff --git a/django/db/backends/base/base.py b/django/db/backends/base/base.py
index d2e79c1dd4..f093f2bd8b 100644
--- a/django/db/backends/base/base.py
+++ b/django/db/backends/base/base.py
@@ -13,7 +13,7 @@ except ImportError:
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
-from django.db import DEFAULT_DB_ALIAS, DatabaseError
+from django.db import DEFAULT_DB_ALIAS, DatabaseError, NotSupportedError
from django.db.backends import utils
from django.db.backends.base.validation import BaseDatabaseValidation
from django.db.backends.signals import connection_created
@@ -24,6 +24,7 @@ from django.utils.asyncio import async_unsafe
from django.utils.functional import cached_property
NO_DB_ALIAS = "__no_db__"
+RAN_DB_VERSION_CHECK = set()
# RemovedInDjango50Warning
@@ -185,6 +186,29 @@ class BaseDatabaseWrapper:
)
return list(self.queries_log)
+ def get_database_version(self):
+ """Return a tuple of the database's version."""
+ raise NotImplementedError(
+ "subclasses of BaseDatabaseWrapper may require a get_database_version() "
+ "method."
+ )
+
+ def check_database_version_supported(self):
+ """
+ Raise an error if the database version isn't supported by this
+ version of Django.
+ """
+ if (
+ self.features.minimum_database_version is not None
+ and self.get_database_version() < self.features.minimum_database_version
+ ):
+ db_version = ".".join(map(str, self.get_database_version()))
+ min_db_version = ".".join(map(str, self.features.minimum_database_version))
+ raise NotSupportedError(
+ f"{self.display_name} {min_db_version} or later is required "
+ f"(found {db_version})."
+ )
+
# ##### Backend-specific methods for creating connections and cursors #####
def get_connection_params(self):
@@ -203,10 +227,10 @@ class BaseDatabaseWrapper:
def init_connection_state(self):
"""Initialize the database connection settings."""
- raise NotImplementedError(
- "subclasses of BaseDatabaseWrapper may require an init_connection_state() "
- "method"
- )
+ global RAN_DB_VERSION_CHECK
+ if self.alias not in RAN_DB_VERSION_CHECK:
+ self.check_database_version_supported()
+ RAN_DB_VERSION_CHECK.add(self.alias)
def create_cursor(self, name=None):
"""Create a cursor. Assume that a connection is established."""
diff --git a/django/db/backends/base/features.py b/django/db/backends/base/features.py
index 42399b769a..ccf9104c21 100644
--- a/django/db/backends/base/features.py
+++ b/django/db/backends/base/features.py
@@ -3,6 +3,8 @@ from django.utils.functional import cached_property
class BaseDatabaseFeatures:
+ # An optional tuple indicating the minimum supported database version.
+ minimum_database_version = None
gis_enabled = False
# Oracle can't group by LOB (large object) data types.
allows_group_by_lob = True
diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py
index 1c20554b4d..ca12917322 100644
--- a/django/db/backends/mysql/base.py
+++ b/django/db/backends/mysql/base.py
@@ -200,6 +200,9 @@ class DatabaseWrapper(BaseDatabaseWrapper):
ops_class = DatabaseOperations
validation_class = DatabaseValidation
+ def get_database_version(self):
+ return self.mysql_version
+
def get_connection_params(self):
kwargs = {
"conv": django_conversions,
@@ -251,6 +254,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
return connection
def init_connection_state(self):
+ super().init_connection_state()
assignments = []
if self.features.is_sql_auto_is_null_enabled:
# SQL_AUTO_IS_NULL controls whether an AUTO_INCREMENT column on
diff --git a/django/db/backends/mysql/features.py b/django/db/backends/mysql/features.py
index e27e766e48..f5b9ef9b55 100644
--- a/django/db/backends/mysql/features.py
+++ b/django/db/backends/mysql/features.py
@@ -49,6 +49,13 @@ class DatabaseFeatures(BaseDatabaseFeatures):
order_by_nulls_first = True
@cached_property
+ def minimum_database_version(self):
+ if self.connection.mysql_is_mariadb:
+ return (10, 2)
+ else:
+ return (5, 7)
+
+ @cached_property
def test_collations(self):
charset = "utf8"
if self.connection.mysql_is_mariadb and self.connection.mysql_version >= (
diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py
index 7cbee768ea..2ccd3bc028 100644
--- a/django/db/backends/oracle/base.py
+++ b/django/db/backends/oracle/base.py
@@ -239,6 +239,9 @@ class DatabaseWrapper(BaseDatabaseWrapper):
)
self.features.can_return_columns_from_insert = use_returning_into
+ def get_database_version(self):
+ return self.oracle_version
+
def get_connection_params(self):
conn_params = self.settings_dict["OPTIONS"].copy()
if "use_returning_into" in conn_params:
@@ -255,6 +258,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
)
def init_connection_state(self):
+ super().init_connection_state()
cursor = self.create_cursor()
# Set the territory first. The territory overrides NLS_DATE_FORMAT
# and NLS_TIMESTAMP_FORMAT to the territory default. When all of
diff --git a/django/db/backends/oracle/features.py b/django/db/backends/oracle/features.py
index 257c3e1b87..2580e5c6c1 100644
--- a/django/db/backends/oracle/features.py
+++ b/django/db/backends/oracle/features.py
@@ -4,6 +4,7 @@ from django.utils.functional import cached_property
class DatabaseFeatures(BaseDatabaseFeatures):
+ minimum_database_version = (19,)
# Oracle crashes with "ORA-00932: inconsistent datatypes: expected - got
# BLOB" when grouping by LOBs (#24096).
allows_group_by_lob = False
diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py
index 92f393227e..630da22964 100644
--- a/django/db/backends/postgresql/base.py
+++ b/django/db/backends/postgresql/base.py
@@ -153,6 +153,13 @@ class DatabaseWrapper(BaseDatabaseWrapper):
# PostgreSQL backend-specific attributes.
_named_cursor_idx = 0
+ def get_database_version(self):
+ """
+ Return a tuple of the database's version.
+ E.g. for pg_version 120004, return (12, 4).
+ """
+ return divmod(self.pg_version, 10000)
+
def get_connection_params(self):
settings_dict = self.settings_dict
# None may be used to connect to the default 'postgres' db
@@ -236,6 +243,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
return False
def init_connection_state(self):
+ super().init_connection_state()
self.connection.set_client_encoding("UTF8")
timezone_changed = self.ensure_timezone()
diff --git a/django/db/backends/postgresql/features.py b/django/db/backends/postgresql/features.py
index 7c5d09d193..5e6752b97a 100644
--- a/django/db/backends/postgresql/features.py
+++ b/django/db/backends/postgresql/features.py
@@ -6,6 +6,7 @@ from django.utils.functional import cached_property
class DatabaseFeatures(BaseDatabaseFeatures):
+ minimum_database_version = (10,)
allows_group_by_selected_pks = True
can_return_columns_from_insert = True
can_return_rows_from_bulk_insert = True
diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
index 5bcd61eb96..8ca076a1d9 100644
--- a/django/db/backends/sqlite3/base.py
+++ b/django/db/backends/sqlite3/base.py
@@ -29,15 +29,6 @@ def decoder(conv_func):
return lambda s: conv_func(s.decode())
-def check_sqlite_version():
- if Database.sqlite_version_info < (3, 9, 0):
- raise ImproperlyConfigured(
- "SQLite 3.9.0 or later is required (found %s)." % Database.sqlite_version
- )
-
-
-check_sqlite_version()
-
Database.register_converter("bool", b"1".__eq__)
Database.register_converter("time", decoder(parse_time))
Database.register_converter("datetime", decoder(parse_datetime))
@@ -168,6 +159,9 @@ class DatabaseWrapper(BaseDatabaseWrapper):
kwargs.update({"check_same_thread": False, "uri": True})
return kwargs
+ def get_database_version(self):
+ return self.Database.sqlite_version_info
+
@async_unsafe
def get_new_connection(self, conn_params):
conn = Database.connect(**conn_params)
@@ -179,9 +173,6 @@ class DatabaseWrapper(BaseDatabaseWrapper):
conn.execute("PRAGMA legacy_alter_table = OFF")
return conn
- def init_connection_state(self):
- pass
-
def create_cursor(self, name=None):
return self.connection.cursor(factory=SQLiteCursorWrapper)
diff --git a/django/db/backends/sqlite3/features.py b/django/db/backends/sqlite3/features.py
index 9161ae3133..2886ecc3be 100644
--- a/django/db/backends/sqlite3/features.py
+++ b/django/db/backends/sqlite3/features.py
@@ -9,6 +9,7 @@ from .base import Database
class DatabaseFeatures(BaseDatabaseFeatures):
+ minimum_database_version = (3, 9)
test_db_allows_multiple_connections = False
supports_unspecified_pk = True
supports_timezones = False