]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Fix PostgreSQL JSONB subscripting regression with functions
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 1 Aug 2025 16:48:15 +0000 (12:48 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 4 Aug 2025 19:06:47 +0000 (15:06 -0400)
Fixed regression in PostgreSQL dialect where JSONB subscription syntax
would generate incorrect SQL for JSONB-returning functions, causing syntax
errors. The dialect now properly wraps function calls and expressions in
parentheses when using the ``[]`` subscription syntax, generating
``(function_call)[index]`` instead of ``function_call[index]`` to comply
with PostgreSQL syntax requirements.

Fixes: #12778
Change-Id: If1238457e6bba6a933023b26519a41aa5de4dbcd

doc/build/changelog/unreleased_20/12778.rst [new file with mode: 0644]
doc/build/tutorial/data_select.rst
lib/sqlalchemy/sql/functions.py
test/dialect/postgresql/test_compiler.py

diff --git a/doc/build/changelog/unreleased_20/12778.rst b/doc/build/changelog/unreleased_20/12778.rst
new file mode 100644 (file)
index 0000000..fc22fc6
--- /dev/null
@@ -0,0 +1,10 @@
+.. change::
+    :tags: bug, postgresql
+    :tickets: 12778
+
+    Fixed regression in PostgreSQL dialect where JSONB subscription syntax
+    would generate incorrect SQL for JSONB-returning functions, causing syntax
+    errors. The dialect now properly wraps function calls and expressions in
+    parentheses when using the ``[]`` subscription syntax, generating
+    ``(function_call)[index]`` instead of ``function_call[index]`` to comply
+    with PostgreSQL syntax requirements.
index 38faddb61a1c96aaf87ad80ee0f17ec91f8674f3..d880b4a4ae7615f107a0f7a4f8003ab5fe030719 100644 (file)
@@ -1476,7 +1476,7 @@ elements::
 
     >>> stmt = select(function_expr["def"])
     >>> print(stmt)
-    {printsql}SELECT json_object(:json_object_1)[:json_object_2] AS anon_1
+    {printsql}SELECT (json_object(:json_object_1))[:json_object_2] AS anon_1
 
 Built-in Functions Have Pre-Configured Return Types
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
index 375cb26f13f6d05a7aef1948c90be3625c6a7d1c..9a28dcfb4f27c1193c139ed0c0b4eeb4063444db 100644 (file)
@@ -743,9 +743,7 @@ class FunctionElement(Executable, ColumnElement[_T], FromClause, Generative):
         # expressions against getitem.  This may need to be made
         # more portable if in the future we support other DBs
         # besides postgresql.
-        if against is operators.getitem and isinstance(
-            self.type, sqltypes.ARRAY
-        ):
+        if against in (operators.getitem, operators.json_getitem_op):
             return Grouping(self)
         else:
             return super().self_group(against=against)
index 6c8289867e2cf171fddcce3516acb7d0f43906c2..5be149cf6a250c82d410f5067a1e2dc8b1aeac52 100644 (file)
@@ -2756,6 +2756,32 @@ class CompileTest(fixtures.TestBase, AssertsCompiledSQL):
             "UPDATE data SET x -> %(x_1)s=(data.x -> %(x_2)s)",
         )
 
+    def test_jsonb_functions_use_parentheses_with_subscripting(self):
+        """test #12778 - JSONB functions are parenthesized with [] syntax"""
+        data = table("data", column("id", Integer), column("x", JSONB))
+
+        # Test that JSONB functions are properly parenthesized with [] syntax
+        # This ensures correct PostgreSQL syntax: (function_call)[index]
+        # instead of the invalid: function_call[index]
+
+        stmt = select(func.jsonb_array_elements(data.c.x, type_=JSONB)["key"])
+        self.assert_compile(
+            stmt,
+            "SELECT "
+            "(jsonb_array_elements(data.x))[%(jsonb_array_elements_1)s] "
+            "AS anon_1 FROM data",
+        )
+
+        # Test with nested function calls
+        stmt = select(
+            func.jsonb_array_elements(data.c.x["items"], type_=JSONB)["key"]
+        )
+        self.assert_compile(
+            stmt,
+            "SELECT (jsonb_array_elements(data.x[%(x_1)s]))"
+            "[%(jsonb_array_elements_1)s] AS anon_1 FROM data",
+        )
+
     def test_range_custom_object_hook(self):
         # See issue #8884
         from datetime import date