diff options
| author | Lily Acorn <code@lilyf.org> | 2025-11-15 16:29:12 +0000 |
|---|---|---|
| committer | Jacob Walls <jacobtylerwalls@gmail.com> | 2025-12-12 11:17:08 -0500 |
| commit | accceec9493d08e19d59fa1a59f69c0fdf23bb13 (patch) | |
| tree | 76fb22d06b9fa3074317a477b713c06fa68ddc12 /django | |
| parent | 8932a677258fbda34f24a1be6f426265148b0003 (diff) | |
Fixed #36735 -- Added UUID4 and UUID7 database functions.
Co-authored-by: Mariusz Felisiak <felisiak.mariusz@gmail.com>
Diffstat (limited to 'django')
| -rw-r--r-- | django/db/backends/base/features.py | 3 | ||||
| -rw-r--r-- | django/db/backends/mysql/features.py | 12 | ||||
| -rw-r--r-- | django/db/backends/oracle/features.py | 4 | ||||
| -rw-r--r-- | django/db/backends/postgresql/features.py | 5 | ||||
| -rw-r--r-- | django/db/backends/sqlite3/_functions.py | 16 | ||||
| -rw-r--r-- | django/db/backends/sqlite3/features.py | 6 | ||||
| -rw-r--r-- | django/db/models/functions/__init__.py | 4 | ||||
| -rw-r--r-- | django/db/models/functions/uuid.py | 68 |
8 files changed, 118 insertions, 0 deletions
diff --git a/django/db/backends/base/features.py b/django/db/backends/base/features.py index 38af239cbe..15ac4d7ac0 100644 --- a/django/db/backends/base/features.py +++ b/django/db/backends/base/features.py @@ -426,6 +426,9 @@ class BaseDatabaseFeatures: # that should be skipped for this database. django_test_skips = {} + supports_uuid4_function = False + supports_uuid7_function = False + def __init__(self, connection): self.connection = connection diff --git a/django/db/backends/mysql/features.py b/django/db/backends/mysql/features.py index 3315db6ae9..4f61e2bdf9 100644 --- a/django/db/backends/mysql/features.py +++ b/django/db/backends/mysql/features.py @@ -232,3 +232,15 @@ class DatabaseFeatures(BaseDatabaseFeatures): @cached_property def supports_any_value(self): return not self.connection.mysql_is_mariadb + + @cached_property + def supports_uuid4_function(self): + if self.connection.mysql_is_mariadb: + return self.connection.mysql_version >= (11, 7) + return False + + @cached_property + def supports_uuid7_function(self): + if self.connection.mysql_is_mariadb: + return self.connection.mysql_version >= (11, 7) + return False diff --git a/django/db/backends/oracle/features.py b/django/db/backends/oracle/features.py index 50fa2ce7e9..78dc97a2ec 100644 --- a/django/db/backends/oracle/features.py +++ b/django/db/backends/oracle/features.py @@ -224,3 +224,7 @@ class DatabaseFeatures(BaseDatabaseFeatures): def supports_tuple_lookups(self): # Support is known to be missing on 23.2 but available on 23.4. return self.connection.oracle_version >= (23, 4) + + @cached_property + def supports_uuid4_function(self): + return self.connection.oracle_version >= (23, 9) diff --git a/django/db/backends/postgresql/features.py b/django/db/backends/postgresql/features.py index 30d06c95c5..edca03b191 100644 --- a/django/db/backends/postgresql/features.py +++ b/django/db/backends/postgresql/features.py @@ -79,6 +79,11 @@ class DatabaseFeatures(BaseDatabaseFeatures): } test_now_utc_template = "STATEMENT_TIMESTAMP() AT TIME ZONE 'UTC'" insert_test_table_with_defaults = "INSERT INTO {} DEFAULT VALUES" + supports_uuid4_function = True + + @cached_property + def supports_uuid7_function(self): + return self.is_postgresql_18 @cached_property def django_test_skips(self): diff --git a/django/db/backends/sqlite3/_functions.py b/django/db/backends/sqlite3/_functions.py index b38e06eec2..aa4100e83f 100644 --- a/django/db/backends/sqlite3/_functions.py +++ b/django/db/backends/sqlite3/_functions.py @@ -27,6 +27,7 @@ from math import ( tan, ) from re import search as re_search +from uuid import uuid4 from django.db.backends.utils import ( split_tzname_delta, @@ -35,6 +36,10 @@ from django.db.backends.utils import ( ) from django.utils import timezone from django.utils.duration import duration_microseconds +from django.utils.version import PY314 + +if PY314: + from uuid import uuid7 def register(connection): @@ -81,6 +86,9 @@ def register(connection): connection.create_aggregate("VAR_POP", 1, VarPop) connection.create_aggregate("VAR_SAMP", 1, VarSamp) connection.create_aggregate("ANY_VALUE", 1, AnyValue) + connection.create_function("UUIDV4", 0, _sqlite_uuid4) + if PY314: + connection.create_function("UUIDV7", 0, _sqlite_uuid7) # Some math functions are enabled by default in SQLite 3.35+. sql = "select sqlite_compileoption_used('ENABLE_MATH_FUNCTIONS')" if not connection.execute(sql).fetchone()[0]: @@ -496,6 +504,14 @@ def _sqlite_tan(x): return tan(x) +def _sqlite_uuid4(): + return uuid4().hex + + +def _sqlite_uuid7(): + return uuid7().hex + + class ListAggregate(list): step = list.append diff --git a/django/db/backends/sqlite3/features.py b/django/db/backends/sqlite3/features.py index 09a0b7fc22..668a53928d 100644 --- a/django/db/backends/sqlite3/features.py +++ b/django/db/backends/sqlite3/features.py @@ -5,6 +5,7 @@ from django.db import transaction from django.db.backends.base.features import BaseDatabaseFeatures from django.db.utils import OperationalError from django.utils.functional import cached_property +from django.utils.version import PY314 from .base import Database @@ -59,6 +60,11 @@ class DatabaseFeatures(BaseDatabaseFeatures): can_return_columns_from_insert = True can_return_rows_from_bulk_insert = True can_return_rows_from_update = True + supports_uuid4_function = True + + @cached_property + def supports_uuid7_function(self): + return PY314 @cached_property def django_test_skips(self): diff --git a/django/db/models/functions/__init__.py b/django/db/models/functions/__init__.py index 2d36c6945a..5464126f5b 100644 --- a/django/db/models/functions/__init__.py +++ b/django/db/models/functions/__init__.py @@ -78,6 +78,7 @@ from .text import ( Trim, Upper, ) +from .uuid import UUID4, UUID7 from .window import ( CumeDist, DenseRank, @@ -178,6 +179,9 @@ __all__ = [ "Substr", "Trim", "Upper", + # uuid + "UUID4", + "UUID7", # window "CumeDist", "DenseRank", diff --git a/django/db/models/functions/uuid.py b/django/db/models/functions/uuid.py new file mode 100644 index 0000000000..d4b8d7110a --- /dev/null +++ b/django/db/models/functions/uuid.py @@ -0,0 +1,68 @@ +from django.db import NotSupportedError +from django.db.models.expressions import Func +from django.db.models.fields import UUIDField + + +class UUID4(Func): + function = "UUIDV4" + arity = 0 + output_field = UUIDField() + + def as_sql(self, compiler, connection, **extra_context): + if connection.features.supports_uuid4_function: + return super().as_sql(compiler, connection, **extra_context) + raise NotSupportedError("UUID4 is not supported on this database backend.") + + def as_postgresql(self, compiler, connection, **extra_context): + if connection.features.is_postgresql_18: + return self.as_sql(compiler, connection, **extra_context) + return self.as_sql( + compiler, connection, function="GEN_RANDOM_UUID", **extra_context + ) + + def as_mysql(self, compiler, connection, **extra_context): + if not connection.features.supports_uuid4_function: + if connection.mysql_is_mariadb: + raise NotSupportedError("UUID4 requires MariaDB version 11.7 or later.") + raise NotSupportedError("UUID4 is not supported on MySQL.") + return self.as_sql(compiler, connection, function="UUID_V4", **extra_context) + + def as_oracle(self, compiler, connection, **extra_context): + if not connection.features.supports_uuid4_function: + raise NotSupportedError( + "UUID4 requires Oracle version 23ai/26ai (23.9) or later." + ) + return self.as_sql(compiler, connection, function="UUID", **extra_context) + + +class UUID7(Func): + function = "UUIDV7" + arity = 0 + output_field = UUIDField() + + def as_sql(self, compiler, connection, **extra_context): + if connection.features.supports_uuid7_function: + return super().as_sql(compiler, connection, **extra_context) + raise NotSupportedError("UUID7 is not supported on this database backend.") + + def as_postgresql(self, compiler, connection, **extra_context): + if connection.features.supports_uuid7_function: + return self.as_sql(compiler, connection, **extra_context) + raise NotSupportedError("UUID7 requires PostgreSQL version 18 or later.") + + # PY314: When dropping support for 3.14, remove the entire method. + def as_sqlite(self, compiler, connection, **extra_context): + if connection.features.supports_uuid7_function: + return self.as_sql(compiler, connection, **extra_context) + raise NotSupportedError( + "UUID7 on SQLite requires Python version 3.14 or later." + ) + + def as_mysql(self, compiler, connection, **extra_context): + if connection.features.supports_uuid7_function: + return self.as_sql( + compiler, connection, function="UUID_V7", **extra_context + ) + if connection.mysql_is_mariadb: + raise NotSupportedError("UUID7 requires MariaDB version 11.7 or later.") + raise NotSupportedError("UUID7 is not supported on MySQL.") |
