From c1017bc800509870f8594de6bf384a86ed2a3f9a Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Mon, 16 Oct 2023 13:01:42 -0400 Subject: [PATCH] implement eager_grouping for expression clauselists the expression clauselist feature added in #7744 failed to accommodate this parameter that is used only by the PostgreSQL JSON operators. Fixed 2.0 regression caused by :ticket:`7744` where chains of expressions involving PostgreSQL JSON operators combined with other operators such as string concatenation would lose correct parenthesization, due to an implementation detail specific to the PostgreSQL dialect. Fixes: #10479 Change-Id: Ic168bf6afd8bf1cfa648f2bad22fdd7254feaa34 --- doc/build/changelog/unreleased_20/10479.rst | 8 ++++++++ lib/sqlalchemy/sql/compiler.py | 7 ++++--- test/dialect/postgresql/test_compiler.py | 18 ++++++++++++++++++ test/sql/test_operators.py | 15 +++++++++++++++ 4 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 doc/build/changelog/unreleased_20/10479.rst diff --git a/doc/build/changelog/unreleased_20/10479.rst b/doc/build/changelog/unreleased_20/10479.rst new file mode 100644 index 0000000000..2c9adfeeae --- /dev/null +++ b/doc/build/changelog/unreleased_20/10479.rst @@ -0,0 +1,8 @@ +.. change:: + :tags: bug, postgresql + :tickets: 10479 + + Fixed 2.0 regression caused by :ticket:`7744` where chains of expressions + involving PostgreSQL JSON operators combined with other operators such as + string concatenation would lose correct parenthesization, due to an + implementation detail specific to the PostgreSQL dialect. diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index 171dd1f1bd..58d249e8dd 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -2742,6 +2742,7 @@ class SQLCompiler(Compiled): except KeyError as err: raise exc.UnsupportedCompilationError(self, operator_) from err else: + kw["_in_operator_expression"] = True return self._generate_delimited_list( clauselist.clauses, opstring, **kw ) @@ -3370,9 +3371,9 @@ class SQLCompiler(Compiled): def _generate_generic_binary( self, binary, opstring, eager_grouping=False, **kw ): - _in_binary = kw.get("_in_binary", False) + _in_operator_expression = kw.get("_in_operator_expression", False) - kw["_in_binary"] = True + kw["_in_operator_expression"] = True kw["_binary_op"] = binary.operator text = ( binary.left._compiler_dispatch( @@ -3384,7 +3385,7 @@ class SQLCompiler(Compiled): ) ) - if _in_binary and eager_grouping: + if _in_operator_expression and eager_grouping: text = "(%s)" % text return text diff --git a/test/dialect/postgresql/test_compiler.py b/test/dialect/postgresql/test_compiler.py index 6e2907039c..5851a86e6d 100644 --- a/test/dialect/postgresql/test_compiler.py +++ b/test/dialect/postgresql/test_compiler.py @@ -43,6 +43,7 @@ from sqlalchemy.dialects.postgresql import array_agg as pg_array_agg from sqlalchemy.dialects.postgresql import DOMAIN from sqlalchemy.dialects.postgresql import ExcludeConstraint from sqlalchemy.dialects.postgresql import insert +from sqlalchemy.dialects.postgresql import JSON from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.dialects.postgresql import JSONPATH from sqlalchemy.dialects.postgresql import Range @@ -2556,6 +2557,23 @@ class CompileTest(fixtures.TestBase, AssertsCompiledSQL): "AS jsonb_path_exists_1 FROM data", ) + @testing.combinations( + (lambda col: col["foo"] + " ", "(x -> %(x_1)s) || %(param_1)s"), + ( + lambda col: col["foo"] + " " + col["bar"], + "(x -> %(x_1)s) || %(param_1)s || (x -> %(x_2)s)", + ), + argnames="expr, expected", + ) + @testing.combinations((JSON(),), (JSONB(),), argnames="type_") + def test_eager_grouping_flag(self, expr, expected, type_): + """test #10479""" + col = Column("x", type_) + + expr = testing.resolve_lambda(expr, col=col) + + self.assert_compile(expr, expected) + def test_custom_object_hook(self): # See issue #8884 from datetime import date diff --git a/test/sql/test_operators.py b/test/sql/test_operators.py index eca45c5d5d..af51010c76 100644 --- a/test/sql/test_operators.py +++ b/test/sql/test_operators.py @@ -1010,6 +1010,21 @@ class JSONIndexOpTest(fixtures.TestBase, testing.AssertsCompiledSQL): is_(col[5]["foo"].type._type_affinity, JSON) is_(col[("a", "b", "c")].type._type_affinity, JSON) + @testing.combinations( + (lambda col: col["foo"] + " ", "(x -> :x_1) || :param_1"), + ( + lambda col: col["foo"] + " " + col["bar"], + "(x -> :x_1) || :param_1 || (x -> :x_2)", + ), + ) + def test_eager_grouping_flag(self, expr, expected): + """test #10479""" + col = Column("x", self.MyType()) + + expr = testing.resolve_lambda(expr, col=col) + + self.assert_compile(expr, expected) + def test_getindex_literal_integer(self): col = Column("x", self.MyType()) -- 2.47.3