From: Mike Bayer Date: Wed, 5 Feb 2025 13:37:04 +0000 (-0500) Subject: remove None exception in IN X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=79505b03b61f622615be2d2bc1434671c29b0cc5;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git remove None exception in IN Fixed SQL composition bug which impacted caching where using a ``None`` value inside of an ``in_()`` expression would bypass the usual "expanded bind parameter" logic used by the IN construct, which allows proper caching to take place. Fixes: #12314 References: #12312 Change-Id: I0d2fc4e15c73407379ba368dd4ee32660fc66259 --- diff --git a/doc/build/changelog/unreleased_20/12314.rst b/doc/build/changelog/unreleased_20/12314.rst new file mode 100644 index 0000000000..6d5e83adeb --- /dev/null +++ b/doc/build/changelog/unreleased_20/12314.rst @@ -0,0 +1,9 @@ +.. change:: + :tags: bug, sql + :tickets: 12314 + + Fixed SQL rendering bug which impacted caching where using a ``None`` value + inside of an ``in_()`` expression would bypass the usual "expanded bind + parameter" logic used by the IN construct, which allows proper caching to + take place. + diff --git a/lib/sqlalchemy/sql/coercions.py b/lib/sqlalchemy/sql/coercions.py index 7119ae1c1f..39655e56d9 100644 --- a/lib/sqlalchemy/sql/coercions.py +++ b/lib/sqlalchemy/sql/coercions.py @@ -859,8 +859,6 @@ class InElementImpl(RoleImpl): else: non_literal_expressions[o] = o - elif o is None: - non_literal_expressions[o] = elements.Null() if non_literal_expressions: return elements.ClauseList( diff --git a/test/dialect/mssql/test_compiler.py b/test/dialect/mssql/test_compiler.py index 59b13b91e0..eb4dba0a07 100644 --- a/test/dialect/mssql/test_compiler.py +++ b/test/dialect/mssql/test_compiler.py @@ -393,7 +393,11 @@ class CompileTest(fixtures.TestBase, AssertsCompiledSQL): "check_post_param": {}, }, ), - (lambda t: t.c.foo.in_([None]), "sometable.foo IN (NULL)", {}), + ( + lambda t: t.c.foo.in_([None]), + "sometable.foo IN (__[POSTCOMPILE_foo_1])", + {}, + ), ) def test_strict_binds(self, expr, compiled, kw): """test the 'strict' compiler binds.""" diff --git a/test/sql/test_compare.py b/test/sql/test_compare.py index f9c435f839..5c7c5053e9 100644 --- a/test/sql/test_compare.py +++ b/test/sql/test_compare.py @@ -1274,6 +1274,23 @@ class CacheKeyTest(fixtures.CacheKeyFixture, CoreFixtures, fixtures.TestBase): is_true(c1._generate_cache_key() != c3._generate_cache_key()) is_false(c1._generate_cache_key() == c3._generate_cache_key()) + def test_in_with_none(self): + """test #12314""" + + def fixture(): + elements = list( + random_choices([1, 2, None, 3, 4], k=random.randint(1, 7)) + ) + + # slight issue. if the first element is None and not an int, + # the type of the BindParameter goes from Integer to Nulltype. + # but if we set the left side to be Integer then it comes from + # that side, and the vast majority of in_() use cases come from + # a typed column expression, so this is fine + return (column("x", Integer).in_(elements),) + + self._run_cache_key_fixture(fixture, False) + def test_cache_key(self): for fixtures_, compare_values in [ (self.fixtures, True), diff --git a/test/sql/test_operators.py b/test/sql/test_operators.py index fbe9ba3900..6ed2c76d75 100644 --- a/test/sql/test_operators.py +++ b/test/sql/test_operators.py @@ -2321,8 +2321,23 @@ class InTest(fixtures.TestBase, testing.AssertsCompiledSQL): ) def test_in_28(self): + """revised to test #12314""" self.assert_compile( - self.table1.c.myid.in_([None]), "mytable.myid IN (NULL)" + self.table1.c.myid.in_([None]), + "mytable.myid IN (__[POSTCOMPILE_myid_1])", + ) + + @testing.combinations( + [1, 2, None, 3], + [None, None, None], + [None, 2, 3, 3], + ) + def test_in_null_combinations(self, expr): + """test #12314""" + + self.assert_compile( + self.table1.c.myid.in_(expr), + "mytable.myid IN (__[POSTCOMPILE_myid_1])", ) @testing.combinations(True, False)