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,
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
*/
(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)
+
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(*);