--- /dev/null
+.. change::
+ :tags: bug, postgresql
+ :tickets: 13067
+
+ Fixed regression in PostgreSQL dialect where JSONB subscription syntax
+ would generate incorrect SQL for :func:`.cast` expressions returning JSONB,
+ causing syntax errors. The dialect now properly wraps cast expressions in
+ parentheses when using the ``[]`` subscription syntax, generating
+ ``(CAST(...))[index]`` instead of ``CAST(...)[index]`` to comply with
+ PostgreSQL syntax requirements. This extends the fix from :ticket:`12778`
+ which addressed the same issue for function calls.
>>> 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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from ...sql import compiler
from ...sql import elements
from ...sql import expression
+from ...sql import functions
from ...sql import roles
from ...sql import sqltypes
from ...sql import util as sql_util
and isinstance(binary.left.type, _json.JSONB)
and self.dialect._supports_jsonb_subscripting
):
+ left = binary.left
+ if isinstance(left, (functions.FunctionElement, elements.Cast)):
+ left = elements.Grouping(left)
+
# for pg14+JSONB use subscript notation: col['key'] instead
# of col -> 'key'
return "%s[%s]" % (
- self.process(binary.left, **kw),
+ self.process(left, **kw),
self.process(binary.right, **kw),
)
else:
# expressions against getitem. This may need to be made
# more portable if in the future we support other DBs
# besides postgresql.
- if against in (operators.getitem, operators.json_getitem_op):
+ if against is operators.getitem and isinstance(
+ self.type, sqltypes.ARRAY
+ ):
return Grouping(self)
else:
return super().self_group(against=against)
" AS anon_1 FROM data",
)
+ def test_jsonb_cast_use_parentheses_with_subscripting(self):
+ """test #13067 - JSONB cast expressions parenthesized with [] syntax"""
+
+ # Test that JSONB cast expressions are properly parenthesized with []
+ # syntax. This ensures correct PostgreSQL syntax: (CAST(...))[index]
+ # instead of the invalid: CAST(...)[index]
+
+ stmt = select(cast({"foo": "bar"}, JSONB)["foo"])
+ self.assert_compile(
+ stmt,
+ "SELECT (CAST(%(param_1)s::JSONB AS JSONB))[%(param_2)s::TEXT] "
+ "AS anon_1",
+ )
+
+ # Test with nested cast within subscripts
+ data = table("data", column("id", Integer), column("x", JSONB))
+ stmt = select(data.c.x[cast("key", String)])
+ self.assert_compile(
+ stmt,
+ "SELECT data.x[CAST(%(param_1)s::VARCHAR AS VARCHAR)] AS anon_1 "
+ "FROM data",
+ )
+
def test_range_custom_object_hook(self):
# See issue #8884
from datetime import date
eq_(connection.execute(stmt).all(), [(1, "foo"), (2, "bar")])
-class JSONUpdateTest(fixtures.TablesTest):
+class JSONQueryTest(fixtures.TablesTest):
"""round trip tests related to using JSON and JSONB in UPDATE statements
with PG-specific features
{"tags": ["python", "postgresql", "postgres"], "priority": "high"},
)
+ @testing.combinations(
+ (lambda: cast({"foo": "bar"}, JSONB)["foo"], "bar"),
+ (
+ cast({"user": {"name": "Alice", "age": 30}}, JSONB)["user"][
+ "name"
+ ],
+ "Alice",
+ ),
+ (cast({"x": 1, "y": 2}, JSONB)["x"], 1),
+ (
+ func.jsonb_build_object("key", "value", type_=JSONB)["key"],
+ "value",
+ ),
+ (
+ func.jsonb_array_elements(
+ cast([{"name": "Bob"}, {"name": "Carol"}], JSONB), type_=JSONB
+ )["name"],
+ "Bob",
+ ),
+ (
+ cast(func.jsonb_build_object("key1", "val1", type_=JSONB), JSONB)[
+ "key1"
+ ],
+ "val1",
+ ),
+ (
+ func.jsonb_build_array(
+ cast({"item": "first"}, JSONB),
+ cast({"item": "second"}, JSONB),
+ type_=JSONB,
+ )[0]["item"],
+ "first",
+ ),
+ argnames="expr, expected",
+ )
+ def test_jsonb_cast_and_function_with_subscript(
+ self, connection, expr, expected
+ ):
+ """Test JSONB cast/function expressions with newer subscript [] syntax
+ that occurs on pg14+
+
+ these tests cover round trips for #12778 and #13067 (so far)
+
+ """
+ stmt = select(expr)
+ result = connection.scalar(stmt)
+ eq_(result, expected)
+
class HstoreUpdateTest(fixtures.TablesTest):
"""round trip tests related to using HSTORE in UPDATE statements