From 60b0a693c97e7ab504a0d36497b71ccba24ac8e8 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 14 Apr 2021 18:53:25 -0400 Subject: Fix with_expression() cache leak; don't adapt singletons Fixed a cache leak involving the :func:`_orm.with_expression` loader option, where the given SQL expression would not be correctly considered as part of the cache key. Additionally, fixed regression involving the corresponding :func:`_orm.query_expression` feature. While the bug technically exists in 1.3 as well, it was not exposed until 1.4. The "default expr" value of ``null()`` would be rendered when not needed, and additionally was also not adapted correctly when the ORM rewrites statements such as when using joined eager loading. The fix ensures "singleton" expressions like ``NULL`` and ``true`` aren't "adapted" to refer to columns in ORM statements, and additionally ensures that a :func:`_orm.query_expression` with no default expression doesn't render in the statement if a :func:`_orm.with_expression` isn't used. Fixes: #6259 Change-Id: I5a70bc12dadad125bbc4324b64048c8d4a18916c --- test/sql/test_external_traversal.py | 71 +++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) (limited to 'test/sql/test_external_traversal.py') diff --git a/test/sql/test_external_traversal.py b/test/sql/test_external_traversal.py index e7c6cccca..e1490adfd 100644 --- a/test/sql/test_external_traversal.py +++ b/test/sql/test_external_traversal.py @@ -12,11 +12,13 @@ from sqlalchemy import Integer from sqlalchemy import literal from sqlalchemy import literal_column from sqlalchemy import MetaData +from sqlalchemy import null from sqlalchemy import select from sqlalchemy import String from sqlalchemy import Table from sqlalchemy import testing from sqlalchemy import text +from sqlalchemy import true from sqlalchemy import tuple_ from sqlalchemy import union from sqlalchemy.sql import ClauseElement @@ -402,6 +404,75 @@ class ClauseTest(fixtures.TestBase, AssertsCompiledSQL): select(f), "SELECT t1_1.col1 * :col1_1 AS anon_1 FROM t1 AS t1_1" ) + @testing.combinations((null(),), (true(),)) + def test_dont_adapt_singleton_elements(self, elem): + """test :ticket:`6259`""" + t1 = table("t1", column("c1")) + + stmt = select(t1.c.c1, elem) + + wherecond = t1.c.c1.is_(elem) + + subq = stmt.subquery() + + adapted_wherecond = sql_util.ClauseAdapter(subq).traverse(wherecond) + stmt = select(subq).where(adapted_wherecond) + + self.assert_compile( + stmt, + "SELECT anon_1.c1, anon_1.anon_2 FROM (SELECT t1.c1 AS c1, " + "%s AS anon_2 FROM t1) AS anon_1 WHERE anon_1.c1 IS %s" + % (str(elem), str(elem)), + dialect="default_enhanced", + ) + + def test_adapt_funcs_etc_on_identity_one(self): + """Adapting to a function etc. will adapt if its on identity""" + t1 = table("t1", column("c1")) + + elem = func.foobar() + + stmt = select(t1.c.c1, elem) + + wherecond = t1.c.c1 == elem + + subq = stmt.subquery() + + adapted_wherecond = sql_util.ClauseAdapter(subq).traverse(wherecond) + stmt = select(subq).where(adapted_wherecond) + + self.assert_compile( + stmt, + "SELECT anon_1.c1, anon_1.foobar_1 FROM (SELECT t1.c1 AS c1, " + "foobar() AS foobar_1 FROM t1) AS anon_1 " + "WHERE anon_1.c1 = anon_1.foobar_1", + dialect="default_enhanced", + ) + + def test_adapt_funcs_etc_on_identity_two(self): + """Adapting to a function etc. will not adapt if they are different""" + t1 = table("t1", column("c1")) + + elem = func.foobar() + elem2 = func.foobar() + + stmt = select(t1.c.c1, elem) + + wherecond = t1.c.c1 == elem2 + + subq = stmt.subquery() + + adapted_wherecond = sql_util.ClauseAdapter(subq).traverse(wherecond) + stmt = select(subq).where(adapted_wherecond) + + self.assert_compile( + stmt, + "SELECT anon_1.c1, anon_1.foobar_1 FROM (SELECT t1.c1 AS c1, " + "foobar() AS foobar_1 FROM t1) AS anon_1 " + "WHERE anon_1.c1 = foobar()", + dialect="default_enhanced", + ) + def test_join(self): clause = t1.join(t2, t1.c.col2 == t2.c.col2) c1 = str(clause) -- cgit v1.2.1