From 03cf128c45f01da92fd862325607612f0507d1e3 Mon Sep 17 00:00:00 2001 From: Richard Guo Date: Fri, 28 Aug 2026 21:56:05 +0900 Subject: [PATCH v1] Fix const-folding of JSON constructors inside a simple CASE A JSON constructor with a RETURNING clause uses a CaseTestExpr as the placeholder for its result in the coercion expression. When such a constructor appears in a WHEN clause of a simple CASE whose test expression is a constant, eval_const_expressions substituted that constant for the placeholder, so the coercion produced the CASE's test value instead of the constructor's result. For instance, CASE 'x' WHEN JSON_OBJECT('a': 'b' RETURNING text) THEN 1 ELSE 0 END evaluated to 1. To fix, keep case_val out of scope while simplifying the coercion, as is already done for the elemexpr of an ArrayCoerceExpr. --- src/backend/optimizer/util/clauses.c | 32 ++++++++++++++++++++++++++- src/test/regress/expected/sqljson.out | 7 ++++++ src/test/regress/sql/sqljson.sql | 3 +++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 8da4ed617b5..d7a20974d4d 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -3339,6 +3339,8 @@ eval_const_expressions_mutator(Node *node, case T_JsonConstructorExpr: { JsonConstructorExpr *jce = (JsonConstructorExpr *) node; + JsonConstructorExpr *newjce; + Node *save_case_val; /* * JSCTOR_JSON_ARRAY_QUERY carries a pre-built executable form @@ -3349,8 +3351,36 @@ eval_const_expressions_mutator(Node *node, if (jce->type == JSCTOR_JSON_ARRAY_QUERY) return eval_const_expressions_mutator((Node *) jce->func, context); + + /* + * Copy the node and const-simplify its arguments. We can't + * use ece_generic_processing() here because we need to mess + * with case_val only while processing the coercion. + */ + newjce = makeNode(JsonConstructorExpr); + memcpy(newjce, jce, sizeof(JsonConstructorExpr)); + newjce->args = (List *) + eval_const_expressions_mutator((Node *) jce->args, + context); + newjce->func = (Expr *) + eval_const_expressions_mutator((Node *) jce->func, + context); + + /* + * Set up for the CaseTestExpr node contained in the coercion. + * We must prevent it from absorbing any outer CASE value. + */ + save_case_val = context->case_val; + context->case_val = NULL; + + newjce->coercion = (Expr *) + eval_const_expressions_mutator((Node *) jce->coercion, + context); + + context->case_val = save_case_val; + + return (Node *) newjce; } - break; case T_SubPlan: case T_AlternativeSubPlan: diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out index d72278d67ca..d3cab7ae16c 100644 --- a/src/test/regress/expected/sqljson.out +++ b/src/test/regress/expected/sqljson.out @@ -573,6 +573,13 @@ SELECT JSON_OBJECT(1: 1, '2': NULL, '3': 1, 4: NULL, '5': 'a' ABSENT ON NULL WIT {"1": 1, "3": 1, "5": "a"} (1 row) +-- 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; + case +------ + 0 +(1 row) + -- 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 96217a55935..7b50cbf9d42 100644 --- a/src/test/regress/sql/sqljson.sql +++ b/src/test/regress/sql/sqljson.sql @@ -152,6 +152,9 @@ SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITH UNIQUE RETURNING SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITHOUT UNIQUE RETURNING jsonb); SELECT JSON_OBJECT(1: 1, '2': NULL, '3': 1, 4: NULL, '5': 'a' ABSENT ON NULL WITH UNIQUE RETURNING jsonb); +-- 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; + -- 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'); -- 2.37.1 (Apple Git-137.1)