--- /dev/null
+.. 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.
@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
@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
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
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:
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
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