From: Federico Caselli Date: Fri, 16 Apr 2021 20:07:34 +0000 (+0200) Subject: Fit literal compile of empty in on a tuple X-Git-Tag: rel_1_4_9~4 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=91562f56185e1e69676712ccb109865a6622a538;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Fit literal compile of empty in on a tuple Fixed regression where an empty in statement on a tuple would result in an error when compiled with the option ``literal_binds=True``. Fixes: #6290 Change-Id: Ic0dff8f4a874cccdb201b6d9dcd3c2e7b7884cbb --- diff --git a/doc/build/changelog/unreleased_14/6290.rst b/doc/build/changelog/unreleased_14/6290.rst new file mode 100644 index 0000000000..64e2317e64 --- /dev/null +++ b/doc/build/changelog/unreleased_14/6290.rst @@ -0,0 +1,6 @@ +.. change:: + :tags: bug, compiler, regression + :tickets: 6290 + + Fixed regression where an empty in statement on a tuple would result + in an error when compiled with the option ``literal_binds=True``. diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index 0c701cb523..a466216e3c 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -1914,10 +1914,14 @@ class SQLCompiler(Compiled): ): if not values: - assert not parameter.type._is_tuple_type - replacement_expression = self.visit_empty_set_expr( - [parameter.type] - ) + if parameter.type._is_tuple_type: + replacement_expression = ( + "VALUES " if self.dialect.tuple_in_values else "" + ) + self.visit_empty_set_expr(parameter.type.types) + else: + replacement_expression = self.visit_empty_set_expr( + [parameter.type] + ) elif isinstance(values[0], (tuple, list)): assert parameter.type._is_tuple_type diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index e97ed252eb..699a874fb8 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -1407,8 +1407,8 @@ class BindParameter(roles.InElementRole, ColumnElement): self.type = type_api._resolve_value_to_type(check_value) elif isinstance(type_, type): self.type = type_() - elif type_._is_tuple_type: - if expanding and value: + elif type_._is_tuple_type and value: + if expanding: check_value = value[0] else: check_value = value diff --git a/test/sql/test_operators.py b/test/sql/test_operators.py index 878360b9d8..8fe802bf3d 100644 --- a/test/sql/test_operators.py +++ b/test/sql/test_operators.py @@ -1933,18 +1933,51 @@ class InTest(fixtures.TestBase, testing.AssertsCompiledSQL): self.table1.c.myid.in_([None]), "mytable.myid IN (NULL)" ) - def test_in_29(self): + @testing.combinations(True, False) + def test_in_29(self, is_in): a, b, c = ( column("a", Integer), column("b", String), column("c", LargeBinary), ) t1 = tuple_(a, b, c) - expr = t1.in_([(3, "hi", "there"), (4, "Q", "P")]) + expr = t1.in_([(3, "hi", b"there"), (4, "Q", b"P")]) + if not is_in: + expr = ~expr + self.assert_compile( + expr, + "(a, b, c) %s ([POSTCOMPILE_param_1])" + % ("IN" if is_in else "NOT IN"), + checkparams={"param_1": [(3, "hi", b"there"), (4, "Q", b"P")]}, + ) self.assert_compile( expr, - "(a, b, c) IN ([POSTCOMPILE_param_1])", - checkparams={"param_1": [(3, "hi", "there"), (4, "Q", "P")]}, + "(a, b, c) %s ((3, 'hi', 'there'), (4, 'Q', 'P'))" + % ("IN" if is_in else "NOT IN"), + literal_binds=True, + ) + + @testing.combinations(True, False) + def test_in_empty_tuple(self, is_in): + a, b, c = ( + column("a", Integer), + column("b", String), + column("c", LargeBinary), + ) + t1 = tuple_(a, b, c) + expr = t1.in_([]) if is_in else t1.not_in([]) + self.assert_compile( + expr, + "(a, b, c) %s ([POSTCOMPILE_param_1])" + % ("IN" if is_in else "NOT IN"), + checkparams={"param_1": []}, + ) + self.assert_compile( + expr, + "(a, b, c) %s (SELECT 1 WHERE 1!=1)" + % ("IN" if is_in else "NOT IN"), + literal_binds=True, + dialect="default_enhanced", ) def test_in_set(self):