summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorLily Acorn <code@lilyf.org>2025-11-15 16:29:12 +0000
committerJacob Walls <jacobtylerwalls@gmail.com>2025-12-12 11:17:08 -0500
commitaccceec9493d08e19d59fa1a59f69c0fdf23bb13 (patch)
tree76fb22d06b9fa3074317a477b713c06fa68ddc12 /django
parent8932a677258fbda34f24a1be6f426265148b0003 (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.py3
-rw-r--r--django/db/backends/mysql/features.py12
-rw-r--r--django/db/backends/oracle/features.py4
-rw-r--r--django/db/backends/postgresql/features.py5
-rw-r--r--django/db/backends/sqlite3/_functions.py16
-rw-r--r--django/db/backends/sqlite3/features.py6
-rw-r--r--django/db/models/functions/__init__.py4
-rw-r--r--django/db/models/functions/uuid.py68
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.")