]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
use os.urandom() for CTE, aliased anon id
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 9 Dec 2025 14:07:20 +0000 (09:07 -0500)
committerMichael Bayer <mike_mp@zzzcomputing.com>
Tue, 9 Dec 2025 15:23:29 +0000 (15:23 +0000)
Fixed issue where anonymous label generation for :class:`.CTE` constructs
could produce name collisions when Python's garbage collector reused memory
addresses during complex query compilation. The anonymous name generation
for :class:`.CTE` and other aliased constructs like :class:`.Alias`,
:class:`.Subquery` and others now use :func:`os.urandom` to generate unique
identifiers instead of relying on object ``id()``, ensuring uniqueness even
in cases of aggressive garbage collection and memory reuse.

Fixes: #12990
Change-Id: If56a53840684bc7d2b7637f1e154dfed1cac5f32

doc/build/changelog/unreleased_21/12990.rst [new file with mode: 0644]
lib/sqlalchemy/sql/elements.py
lib/sqlalchemy/sql/selectable.py
test/orm/test_query.py

diff --git a/doc/build/changelog/unreleased_21/12990.rst b/doc/build/changelog/unreleased_21/12990.rst
new file mode 100644 (file)
index 0000000..92b441c
--- /dev/null
@@ -0,0 +1,11 @@
+.. change::
+    :tags: bug, sql
+    :tickets: 12990
+
+    Fixed issue where anonymous label generation for :class:`.CTE` constructs
+    could produce name collisions when Python's garbage collector reused memory
+    addresses during complex query compilation. The anonymous name generation
+    for :class:`.CTE` and other aliased constructs like :class:`.Alias`,
+    :class:`.Subquery` and others now use :func:`os.urandom` to generate unique
+    identifiers instead of relying on object ``id()``, ensuring uniqueness even
+    in cases of aggressive garbage collection and memory reuse.
index 674560d7e190ff63f1c095781add7b6ce17e7f83..0b97df18c7221b3652dec58cb4f19c36580d7b86 100644 (file)
@@ -5953,7 +5953,7 @@ class _anonymous_label(_truncated_label):
 
     @classmethod
     def safe_construct_with_key(
-        cls, seed: int, body: str, sanitize_key: bool = False
+        cls, seed: int | str, body: str, sanitize_key: bool = False
     ) -> typing_Tuple[_anonymous_label, str]:
         # need to escape chars that interfere with format
         # strings in any case, issue #8724
@@ -5969,7 +5969,7 @@ class _anonymous_label(_truncated_label):
 
     @classmethod
     def safe_construct(
-        cls, seed: int, body: str, sanitize_key: bool = False
+        cls, seed: int | str, body: str, sanitize_key: bool = False
     ) -> _anonymous_label:
         # need to escape chars that interfere with format
         # strings in any case, issue #8724
index 0668bc3672962555c789d2a250765d0760636df9..e1bf22856ca61918ab4177c0663c9969d6bebde2 100644 (file)
@@ -16,6 +16,7 @@ from __future__ import annotations
 import collections
 from enum import Enum
 import itertools
+import os
 from typing import AbstractSet
 from typing import Any as TODO_Any
 from typing import Any
@@ -1744,7 +1745,9 @@ class AliasedReturnsRows(NoInit, NamedFromClause):
                 name = getattr(selectable, "name", None)
                 if isinstance(name, _anonymous_label):
                     name = None
-            name = _anonymous_label.safe_construct(id(self), name or "anon")
+            name = _anonymous_label.safe_construct(
+                os.urandom(10).hex(), name or "anon"
+            )
         self.name = name
 
     def _refresh_for_new_column(self, column: ColumnElement[Any]) -> None:
index 5863b553fc175caeb2137ccd34db2645c1fb39f2..e39a70c47c4c2932e15bb203bdec43706df9cd92 100644 (file)
@@ -73,6 +73,7 @@ from sqlalchemy.testing.assertions import assert_raises
 from sqlalchemy.testing.assertions import assert_raises_message
 from sqlalchemy.testing.assertions import assert_warns_message
 from sqlalchemy.testing.assertions import eq_
+from sqlalchemy.testing.assertions import eq_regex
 from sqlalchemy.testing.assertions import expect_deprecated
 from sqlalchemy.testing.assertions import expect_raises
 from sqlalchemy.testing.assertions import expect_warnings
@@ -2175,7 +2176,7 @@ class ExpressionTest(QueryTest, AssertsCompiledSQL):
 
         eq_(a1.name, "foo1")
         eq_(a2.name, "foo2")
-        eq_(a3.name, "%%(%d anon)s" % id(a3))
+        eq_regex(a3.name, r"%\([0-9a-z]+ anon\)s")
 
     def test_labeled_subquery(self):
         User = self.classes.User