mirror of
https://github.com/postgres/postgres.git
synced 2026-07-08 01:01:01 -04:00
Fix EXPLAIN failure when deparsing SQL/JSON aggregates
If an expression containing an aggregate is evaluated above the plan node that computes the aggregate, as happens with window functions or with expressions postponed to above the final sort, setrefs.c replaces the Aggref or WindowFunc with a Var referencing the lower node's output. For SQL/JSON aggregates such as JSON_ARRAYAGG and JSON_OBJECTAGG, deparsing the containing JsonConstructorExpr then failed with "invalid JsonConstructorExpr underlying node type", since get_json_agg_constructor() did not expect a Var there. Fix by resolving the Var back to the underlying Aggref or WindowFunc and deparsing the constructor as if the aggregate were computed at the current node. The JsonConstructorExpr retains the RETURNING clause and the ABSENT/NULL ON NULL and WITH UNIQUE options, and the arguments come from the resolved aggregate, so the original JSON aggregate syntax is reproduced in full. This mirrors how get_agg_expr() already looks through such a Var when deparsing a combining aggregate. Reported-by: Thom Brown <thom@linux.com> Author: Richard Guo <guofenglinux@gmail.com> Discussion: https://postgr.es/m/CAA-aLv5QYTaMOk=Qhv6cgwceeHETZV8YJvWZ_rH+yVZCuchATA@mail.gmail.com Backpatch-through: 16
This commit is contained in:
parent
5e78ebca57
commit
dcda1f07da
3 changed files with 171 additions and 0 deletions
|
|
@ -481,6 +481,8 @@ static void get_json_agg_constructor(JsonConstructorExpr *ctor,
|
|||
deparse_context *context,
|
||||
const char *funcname,
|
||||
bool is_json_objectagg);
|
||||
static void get_json_agg_constructor_expr(Node *node, deparse_context *context,
|
||||
void *callback_arg);
|
||||
static void simple_quote_literal(StringInfo buf, const char *val);
|
||||
static void get_sublink_expr(SubLink *sublink, deparse_context *context);
|
||||
static void get_tablefunc(TableFunc *tf, deparse_context *context,
|
||||
|
|
@ -11451,11 +11453,41 @@ get_json_agg_constructor(JsonConstructorExpr *ctor, deparse_context *context,
|
|||
get_windowfunc_expr_helper((WindowFunc *) ctor->func, context,
|
||||
funcname, options.data,
|
||||
is_json_objectagg);
|
||||
else if (IsA(ctor->func, Var))
|
||||
{
|
||||
/*
|
||||
* If the aggregate is computed by a lower plan node, setrefs.c will
|
||||
* have replaced the Aggref or WindowFunc with a Var referencing that
|
||||
* node's output. Chase the Var back to it so we can still print the
|
||||
* original JSON aggregate syntax. This only happens in EXPLAIN.
|
||||
*/
|
||||
resolve_special_varno((Node *) ctor->func, context,
|
||||
get_json_agg_constructor_expr, ctor);
|
||||
}
|
||||
else
|
||||
elog(ERROR, "invalid JsonConstructorExpr underlying node type: %d",
|
||||
nodeTag(ctor->func));
|
||||
}
|
||||
|
||||
/*
|
||||
* Deparse a JsonConstructorExpr whose aggregate is computed by a lower plan
|
||||
* node; resolve_special_varno has located the underlying Aggref/WindowFunc.
|
||||
*/
|
||||
static void
|
||||
get_json_agg_constructor_expr(Node *node, deparse_context *context,
|
||||
void *callback_arg)
|
||||
{
|
||||
JsonConstructorExpr ctor;
|
||||
|
||||
if (!IsA(node, Aggref) && !IsA(node, WindowFunc))
|
||||
elog(ERROR, "JSON aggregate constructor does not point to an Aggref or WindowFunc");
|
||||
|
||||
/* Flat copy suffices; we only replace func. */
|
||||
ctor = *(JsonConstructorExpr *) callback_arg;
|
||||
ctor.func = (Expr *) node;
|
||||
get_json_constructor(&ctor, context, false);
|
||||
}
|
||||
|
||||
/*
|
||||
* simple_quote_literal - Format a string as a SQL literal, append to buf
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1403,3 +1403,101 @@ SELECT JSON_OBJECT('a': JSON_OBJECTAGG('b': 1 RETURNING text) FORMAT JSON);
|
|||
(1 row)
|
||||
|
||||
DROP FUNCTION volatile_one, stable_one;
|
||||
-- Test deparsing of JSON aggregates that are computed below a WindowAgg
|
||||
-- node.
|
||||
EXPLAIN (VERBOSE, COSTS OFF)
|
||||
SELECT i % 2 AS g,
|
||||
JSON_ARRAYAGG(i ORDER BY i RETURNING jsonb) AS ja,
|
||||
JSON_ARRAYAGG(i ORDER BY i RETURNING text) AS ja_text,
|
||||
JSON_ARRAYAGG(i ORDER BY i NULL ON NULL RETURNING jsonb) AS ja_null,
|
||||
JSON_OBJECTAGG(i: i ABSENT ON NULL RETURNING jsonb) AS jo_absent,
|
||||
JSON_OBJECTAGG(i: i WITH UNIQUE RETURNING jsonb) AS jo_unique,
|
||||
row_number() OVER (ORDER BY i % 2) AS rn
|
||||
FROM generate_series(1, 3) i
|
||||
GROUP BY i % 2;
|
||||
QUERY PLAN
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
WindowAgg
|
||||
Output: ((i % 2)), JSON_ARRAYAGG(i ORDER BY i RETURNING jsonb), JSON_ARRAYAGG(i ORDER BY i RETURNING text), JSON_ARRAYAGG(i ORDER BY i NULL ON NULL RETURNING jsonb), JSON_OBJECTAGG(i : i ABSENT ON NULL RETURNING jsonb), JSON_OBJECTAGG(i : i WITH UNIQUE KEYS RETURNING jsonb), row_number() OVER (?)
|
||||
-> GroupAggregate
|
||||
Output: ((i % 2)), jsonb_agg_strict(i ORDER BY i), json_agg_strict(i ORDER BY i), jsonb_agg(i ORDER BY i), jsonb_object_agg_strict(i, i), jsonb_object_agg_unique(i, i)
|
||||
Group Key: ((i.i % 2))
|
||||
-> Sort
|
||||
Output: ((i % 2)), i
|
||||
Sort Key: ((i.i % 2)), i.i
|
||||
-> Function Scan on pg_catalog.generate_series i
|
||||
Output: (i % 2), i
|
||||
Function Call: generate_series(1, 3)
|
||||
(11 rows)
|
||||
|
||||
SELECT i % 2 AS g,
|
||||
JSON_ARRAYAGG(i ORDER BY i RETURNING jsonb) AS ja,
|
||||
JSON_ARRAYAGG(i ORDER BY i RETURNING text) AS ja_text,
|
||||
JSON_ARRAYAGG(i ORDER BY i NULL ON NULL RETURNING jsonb) AS ja_null,
|
||||
JSON_OBJECTAGG(i: i ABSENT ON NULL RETURNING jsonb) AS jo_absent,
|
||||
JSON_OBJECTAGG(i: i WITH UNIQUE RETURNING jsonb) AS jo_unique,
|
||||
row_number() OVER (ORDER BY i % 2) AS rn
|
||||
FROM generate_series(1, 3) i
|
||||
GROUP BY i % 2;
|
||||
g | ja | ja_text | ja_null | jo_absent | jo_unique | rn
|
||||
---+--------+---------+---------+------------------+------------------+----
|
||||
0 | [2] | [2] | [2] | {"2": 2} | {"2": 2} | 1
|
||||
1 | [1, 3] | [1, 3] | [1, 3] | {"1": 1, "3": 3} | {"1": 1, "3": 3} | 2
|
||||
(2 rows)
|
||||
|
||||
-- The same, but with the JSON aggregate used as a window function that is
|
||||
-- computed below another WindowAgg node.
|
||||
EXPLAIN (VERBOSE, COSTS OFF)
|
||||
SELECT JSON_ARRAYAGG(i NULL ON NULL RETURNING jsonb) OVER (ORDER BY i DESC) AS ja,
|
||||
row_number() OVER (ORDER BY i) AS rn
|
||||
FROM generate_series(1, 3) i;
|
||||
QUERY PLAN
|
||||
--------------------------------------------------------------------------------------------
|
||||
WindowAgg
|
||||
Output: JSON_ARRAYAGG(i NULL ON NULL RETURNING jsonb) OVER (?), row_number() OVER (?), i
|
||||
-> Sort
|
||||
Output: i, (jsonb_agg(i) OVER (?))
|
||||
Sort Key: i.i
|
||||
-> WindowAgg
|
||||
Output: i, jsonb_agg(i) OVER (?)
|
||||
-> Sort
|
||||
Output: i
|
||||
Sort Key: i.i DESC
|
||||
-> Function Scan on pg_catalog.generate_series i
|
||||
Output: i
|
||||
Function Call: generate_series(1, 3)
|
||||
(13 rows)
|
||||
|
||||
SELECT JSON_ARRAYAGG(i NULL ON NULL RETURNING jsonb) OVER (ORDER BY i DESC) AS ja,
|
||||
row_number() OVER (ORDER BY i) AS rn
|
||||
FROM generate_series(1, 3) i;
|
||||
ja | rn
|
||||
-----------+----
|
||||
[3, 2, 1] | 1
|
||||
[3, 2] | 2
|
||||
[3] | 3
|
||||
(3 rows)
|
||||
|
||||
-- The same, but with the expression containing the JSON aggregate postponed
|
||||
-- to above the final sort due to being volatile.
|
||||
EXPLAIN (VERBOSE, COSTS OFF)
|
||||
SELECT i % 2 AS g,
|
||||
JSON_ARRAYAGG(i RETURNING text) || random()::text AS ja
|
||||
FROM generate_series(1, 3) i
|
||||
GROUP BY i % 2
|
||||
ORDER BY count(*);
|
||||
QUERY PLAN
|
||||
----------------------------------------------------------------------------------------
|
||||
Result
|
||||
Output: ((i % 2)), (JSON_ARRAYAGG(i RETURNING text) || (random())::text), (count(*))
|
||||
-> Sort
|
||||
Output: ((i % 2)), (count(*)), (json_agg_strict(i))
|
||||
Sort Key: (count(*))
|
||||
-> HashAggregate
|
||||
Output: ((i % 2)), count(*), json_agg_strict(i)
|
||||
Group Key: (i.i % 2)
|
||||
-> Function Scan on pg_catalog.generate_series i
|
||||
Output: (i % 2), i
|
||||
Function Call: generate_series(1, 3)
|
||||
(11 rows)
|
||||
|
||||
|
|
|
|||
|
|
@ -529,3 +529,44 @@ SELECT JSON_OBJECT('a': JSON_OBJECTAGG('b': stable_one() RETURNING text) FORMAT
|
|||
EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_OBJECT('a': JSON_OBJECTAGG('b': 1 RETURNING text) FORMAT JSON);
|
||||
SELECT JSON_OBJECT('a': JSON_OBJECTAGG('b': 1 RETURNING text) FORMAT JSON);
|
||||
DROP FUNCTION volatile_one, stable_one;
|
||||
|
||||
-- Test deparsing of JSON aggregates that are computed below a WindowAgg
|
||||
-- node.
|
||||
EXPLAIN (VERBOSE, COSTS OFF)
|
||||
SELECT i % 2 AS g,
|
||||
JSON_ARRAYAGG(i ORDER BY i RETURNING jsonb) AS ja,
|
||||
JSON_ARRAYAGG(i ORDER BY i RETURNING text) AS ja_text,
|
||||
JSON_ARRAYAGG(i ORDER BY i NULL ON NULL RETURNING jsonb) AS ja_null,
|
||||
JSON_OBJECTAGG(i: i ABSENT ON NULL RETURNING jsonb) AS jo_absent,
|
||||
JSON_OBJECTAGG(i: i WITH UNIQUE RETURNING jsonb) AS jo_unique,
|
||||
row_number() OVER (ORDER BY i % 2) AS rn
|
||||
FROM generate_series(1, 3) i
|
||||
GROUP BY i % 2;
|
||||
SELECT i % 2 AS g,
|
||||
JSON_ARRAYAGG(i ORDER BY i RETURNING jsonb) AS ja,
|
||||
JSON_ARRAYAGG(i ORDER BY i RETURNING text) AS ja_text,
|
||||
JSON_ARRAYAGG(i ORDER BY i NULL ON NULL RETURNING jsonb) AS ja_null,
|
||||
JSON_OBJECTAGG(i: i ABSENT ON NULL RETURNING jsonb) AS jo_absent,
|
||||
JSON_OBJECTAGG(i: i WITH UNIQUE RETURNING jsonb) AS jo_unique,
|
||||
row_number() OVER (ORDER BY i % 2) AS rn
|
||||
FROM generate_series(1, 3) i
|
||||
GROUP BY i % 2;
|
||||
|
||||
-- The same, but with the JSON aggregate used as a window function that is
|
||||
-- computed below another WindowAgg node.
|
||||
EXPLAIN (VERBOSE, COSTS OFF)
|
||||
SELECT JSON_ARRAYAGG(i NULL ON NULL RETURNING jsonb) OVER (ORDER BY i DESC) AS ja,
|
||||
row_number() OVER (ORDER BY i) AS rn
|
||||
FROM generate_series(1, 3) i;
|
||||
SELECT JSON_ARRAYAGG(i NULL ON NULL RETURNING jsonb) OVER (ORDER BY i DESC) AS ja,
|
||||
row_number() OVER (ORDER BY i) AS rn
|
||||
FROM generate_series(1, 3) i;
|
||||
|
||||
-- The same, but with the expression containing the JSON aggregate postponed
|
||||
-- to above the final sort due to being volatile.
|
||||
EXPLAIN (VERBOSE, COSTS OFF)
|
||||
SELECT i % 2 AS g,
|
||||
JSON_ARRAYAGG(i RETURNING text) || random()::text AS ja
|
||||
FROM generate_series(1, 3) i
|
||||
GROUP BY i % 2
|
||||
ORDER BY count(*);
|
||||
|
|
|
|||
Loading…
Reference in a new issue