postgres.git / summary / log / commit / refs

commit    9d0adee8768852fc0e5713ec27cf52d46d97279d
Author:   Richard Guo <rguo@postgresql.org>
Date:     Mon Sep 14 08:04:13 2026 +0000

    Don't let JSON constructor coercions block SQL function inlining
    
    contain_context_dependent_node_walker() reports a CaseTestExpr as
    context-dependent unless it sits under a simple CaseExpr or the
    elemexpr of an ArrayCoerceExpr.  A JsonConstructorExpr whose RETURNING
    type requires a coercion also carries a CaseTestExpr placeholder in
    that coercion, so any SQL function called with such a constructor as
    an argument was refused inlining.
    
    Teach the walker that a CaseTestExpr is expected within the coercion
    of a JsonConstructorExpr, the same way it already handles the elemexpr
    of an ArrayCoerceExpr.  This is safe now that eval_const_expressions
    no longer lets an enclosing simple CASE clobber that placeholder.
    
    Author: Richard Guo <guofenglinux@gmail.com>
    Discussion: https://postgr.es/m/CAMbWs48A=VCFbteTkuCoknO1_0-Cu0aMBT0M07dm7vj1QyixDg@mail.gmail.com
    Backpatch-through: 19


src/backend/optimizer/util/clauses.c | 23 ++++++++++++++++++++++- src/test/regress/expected/sqljson.out | 18 ++++++++++++++++++ src/test/regress/sql/sqljson.sql | 8 ++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 3ba0ef384f2..3e1f210652d 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -1255,7 +1255,8 @@ contain_exec_param_walker(Node *node, List *param_ids) * not nested within another one, or they'll see the wrong test value. If one * appears "bare" in the arguments of a SQL function, then we can't inline the * SQL function for fear of creating such a situation. The same applies for - * CaseTestExpr used within the elemexpr of an ArrayCoerceExpr. + * CaseTestExpr used within the elemexpr of an ArrayCoerceExpr or the coercion + * of a JsonConstructorExpr. * * CoerceToDomainValue would have the same issue if domain CHECK expressions * could get inlined into larger expressions, but presently that's impossible. @@ -1328,6 +1329,26 @@ contain_context_dependent_node_walker(Node *node, int *flags) *flags = save_flags; return res; } + else if (IsA(node, JsonConstructorExpr)) + { + JsonConstructorExpr *jce = (JsonConstructorExpr *) node; + int save_flags; + bool res; + + /* Check the args and func expressions */ + if (contain_context_dependent_node_walker((Node *) jce->args, flags)) + return true; + if (contain_context_dependent_node_walker((Node *) jce->func, flags)) + return true; + + /* Check the coercion, which is allowed to contain CaseTestExpr */ + save_flags = *flags; + *flags |= CCDN_CASETESTEXPR_OK; + res = contain_context_dependent_node_walker((Node *) jce->coercion, + flags); + *flags = save_flags; + return res; + } return expression_tree_walker(node, contain_context_dependent_node_walker, flags); } diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out index d3cab7ae16c..30f9e10ef0a 100644 --- a/src/test/regress/expected/sqljson.out +++ b/src/test/regress/expected/sqljson.out @@ -580,6 +580,24 @@ SELECT CASE 'x' WHEN JSON_OBJECT('a': 'b' RETURNING text) THEN 1 ELSE 0 END; 0 (1 row) +-- the RETURNING coercion must not prevent inlining of a SQL function +CREATE FUNCTION json_object_inline_test(text) RETURNS text +LANGUAGE sql IMMUTABLE AS $$ SELECT $1 || '!' $$; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT json_object_inline_test(JSON_OBJECT('a': 'b' RETURNING text)); + QUERY PLAN +---------------------------------------------------------------------- + Result + Output: (JSON_OBJECT('a' : 'b'::text RETURNING text) || '!'::text) +(2 rows) + +SELECT json_object_inline_test(JSON_OBJECT('a': 'b' RETURNING text)); + json_object_inline_test +------------------------- + {"a" : "b"}! +(1 row) + +DROP FUNCTION json_object_inline_test(text); -- BUG: https://postgr.es/m/CADXhmgTJtJZK9A3Na_ry%2BXrq-ghjcejBRhcRMzWZvbd__QdgJA%40mail.gmail.com -- datum_to_jsonb_internal() didn't catch keys that are casts instead of a simple scalar CREATE TYPE mood AS ENUM ('happy', 'sad', 'neutral'); diff --git a/src/test/regress/sql/sqljson.sql b/src/test/regress/sql/sqljson.sql index 7b50cbf9d42..9fa203ccb53 100644 --- a/src/test/regress/sql/sqljson.sql +++ b/src/test/regress/sql/sqljson.sql @@ -155,6 +155,14 @@ SELECT JSON_OBJECT(1: 1, '2': NULL, '3': 1, 4: NULL, '5': 'a' ABSENT ON NULL WIT -- the RETURNING coercion must not pick up the test value of an enclosing CASE SELECT CASE 'x' WHEN JSON_OBJECT('a': 'b' RETURNING text) THEN 1 ELSE 0 END; +-- the RETURNING coercion must not prevent inlining of a SQL function +CREATE FUNCTION json_object_inline_test(text) RETURNS text +LANGUAGE sql IMMUTABLE AS $$ SELECT $1 || '!' $$; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT json_object_inline_test(JSON_OBJECT('a': 'b' RETURNING text)); +SELECT json_object_inline_test(JSON_OBJECT('a': 'b' RETURNING text)); +DROP FUNCTION json_object_inline_test(text); + -- BUG: https://postgr.es/m/CADXhmgTJtJZK9A3Na_ry%2BXrq-ghjcejBRhcRMzWZvbd__QdgJA%40mail.gmail.com -- datum_to_jsonb_internal() didn't catch keys that are casts instead of a simple scalar CREATE TYPE mood AS ENUM ('happy', 'sad', 'neutral'); [parent: e17df18a811e]