1
0
mirror of https://github.com/postgres/postgres.git synced 2026-10-23 13:29:09 +03:00

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.

Back-patch to v16, where the SQL/JSON constructor functions were
introduced.

Author: Richard Guo <guofenglinux@gmail.com>
Discussion: https://postgr.es/m/CAMbWs48A=VCFbteTkuCoknO1_0-Cu0aMBT0M07dm7vj1QyixDg@mail.gmail.com
Backpatch-through: 16
This commit is contained in:
Richard Guo
2026-09-14 17:09:12 +09:00
parent 54ac06f31b
commit 27ef9097b5
4 changed files with 48 additions and 6 deletions

View File

@@ -2904,7 +2904,6 @@ eval_const_expressions_mutator(Node *node,
}
break;
}
case T_JsonValueExpr:
{
JsonValueExpr *jve = (JsonValueExpr *) node;
@@ -2928,7 +2927,41 @@ eval_const_expressions_mutator(Node *node,
(Expr *) formatted_expr,
copyObject(jve->format));
}
case T_JsonConstructorExpr:
{
JsonConstructorExpr *jce = (JsonConstructorExpr *) node;
JsonConstructorExpr *newjce;
Node *save_case_val;
/*
* 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;
}
case T_SubPlan:
case T_AlternativeSubPlan:

View File

@@ -1271,11 +1271,11 @@ typedef struct CaseWhen
* * Placeholder for intermediate results in some SQL/JSON expression nodes,
* such as JsonConstructorExpr.
*
* The uses in CaseExpr and ArrayCoerceExpr are safe only to the extent that
* there is not any other CaseExpr or ArrayCoerceExpr between the value source
* node and its child CaseTestExpr(s). This is true in the parse analysis
* output, but the planner's function-inlining logic has to be careful not to
* break it.
* The uses in CaseExpr, ArrayCoerceExpr, and JsonConstructorExpr are safe
* only to the extent that there is not any other such node between the
* value source node and its child CaseTestExpr(s). This is true in the
* parse analysis output, but the planner's constant-folding of simple CASE
* and its function-inlining logic have to be careful not to break it.
*
* The nested-assignment-expression case is safe because the only node types
* that can be above such CaseTestExprs are FieldStore and SubscriptingRef.

View File

@@ -281,6 +281,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)
-- JSON_ARRAY()
SELECT JSON_ARRAY();
json_array

View File

@@ -88,6 +88,8 @@ 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;
-- JSON_ARRAY()
SELECT JSON_ARRAY();