which must be a SELECT query returning a single column. If
<literal>ABSENT ON NULL</literal> is specified, NULL values are ignored.
This is always the case if a
- <replaceable>query_expression</replaceable> is used.
+ <replaceable>query_expression</replaceable> is used. If the query returns
+ no rows, an empty JSON array is returned.
</para>
<para>
<literal>json_array(1,true,json '{"a":null}')</literal>
{
const JsonConstructorExpr *ctor = (const JsonConstructorExpr *) expr;
+ /*
+ * Collation comes from coercion if present, otherwise from
+ * func. The func fallback is needed in cases where func
+ * already produces the final output type and no coercion is
+ * needed (cf. the JSCTOR_JSON_ARRAY_QUERY case).
+ */
if (ctor->coercion)
coll = exprCollation((Node *) ctor->coercion);
+ else if (ctor->func)
+ coll = exprCollation((Node *) ctor->func);
else
coll = InvalidOid;
}
{
JsonConstructorExpr *ctor = (JsonConstructorExpr *) expr;
+ /* See comment in exprCollation() */
if (ctor->coercion)
exprSetCollation((Node *) ctor->coercion, collation);
+ else if (ctor->func)
+ exprSetCollation((Node *) ctor->func, collation);
else
Assert(!OidIsValid(collation)); /* result is always a
* json[b] type */
}
break;
}
-
case T_JsonValueExpr:
{
JsonValueExpr *jve = (JsonValueExpr *) node;
(Expr *) formatted_expr,
copyObject(jve->format));
}
+ case T_JsonConstructorExpr:
+ {
+ JsonConstructorExpr *jce = (JsonConstructorExpr *) node;
+ /*
+ * JSCTOR_JSON_ARRAY_QUERY carries a pre-built executable form
+ * in its func field (a COALESCE-wrapped JSON_ARRAYAGG
+ * subquery, constructed during parse analysis). Replace the
+ * node with that expression and continue simplifying.
+ */
+ if (jce->type == JSCTOR_JSON_ARRAY_QUERY)
+ return eval_const_expressions_mutator((Node *) jce->func,
+ context);
+ }
+ break;
case T_SubPlan:
case T_AlternativeSubPlan:
}
/*
- * Transform JSON_ARRAY(query [FORMAT] [RETURNING] [ON NULL]) into
- * (SELECT JSON_ARRAYAGG(a [FORMAT] [RETURNING] [ON NULL]) FROM (query) q(a))
+ * Transform JSON_ARRAY(subquery) constructor.
+ *
+ * JSON_ARRAY(subquery) is transformed into a JsonConstructorExpr node of type
+ * JSCTOR_JSON_ARRAY_QUERY. The node carries:
+ *
+ * - func: the executable form, which is a COALESCE expression wrapping a
+ * JSON_ARRAYAGG subquery:
+ *
+ * COALESCE((SELECT JSON_ARRAYAGG(a) FROM (subquery) q(a)), '[]')
+ *
+ * The COALESCE ensures that an empty result set produces '[]' rather than
+ * NULL, per the SQL/JSON standard.
+ *
+ * - orig_query: the transformed Query of the user's original subquery, so
+ * that ruleutils.c can deparse the original JSON_ARRAY(SELECT ...) syntax
+ * for view definitions.
*/
static Node *
transformJsonArrayQueryConstructor(ParseState *pstate,
JsonArrayQueryConstructor *ctor)
{
- SubLink *sublink = makeNode(SubLink);
- SelectStmt *select = makeNode(SelectStmt);
- RangeSubselect *range = makeNode(RangeSubselect);
- Alias *alias = makeNode(Alias);
- ResTarget *target = makeNode(ResTarget);
- JsonArrayAgg *agg = makeNode(JsonArrayAgg);
- ColumnRef *colref = makeNode(ColumnRef);
Query *query;
ParseState *qpstate;
+ SubLink *sublink;
+ SelectStmt *select;
+ RangeSubselect *range;
+ Alias *alias;
+ ResTarget *target;
+ JsonArrayAgg *agg;
+ ColumnRef *colref;
+ Node *exec_expr;
+ CoalesceExpr *coalesce;
+ Const *empty_const;
+ Oid result_type;
+ Oid typinput;
+ Oid typioparam;
+ int16 typlen;
+ bool typbyval;
+ JsonReturning *returning;
+ List *args;
+ Node *result;
- /* Transform query only for counting target list entries. */
+ /*
+ * Transform a copy of the subquery to validate the single-column
+ * constraint and to obtain the transformed Query for deparsing. This
+ * uses a private ParseState so it doesn't affect the main parse context.
+ */
qpstate = make_parsestate(pstate);
query = transformStmt(qpstate, copyObject(ctor->query));
free_parsestate(qpstate);
+ /*
+ * Build the executable form by constructing query:
+ *
+ * (SELECT JSON_ARRAYAGG(a [FORMAT] [RETURNING]) FROM (subquery) q(a))
+ *
+ * ... using raw parse tree nodes, then transforming via
+ * transformExprRecurse.
+ */
+ colref = makeNode(ColumnRef);
colref->fields = list_make2(makeString(pstrdup("q")),
makeString(pstrdup("a")));
colref->location = ctor->location;
- /*
- * No formatting necessary, so set formatted_expr to be the same as
- * raw_expr.
- */
+ agg = makeNode(JsonArrayAgg);
agg->arg = makeJsonValueExpr((Expr *) colref, (Expr *) colref,
ctor->format);
agg->absent_on_null = ctor->absent_on_null;
agg->constructor->output = ctor->output;
agg->constructor->location = ctor->location;
+ target = makeNode(ResTarget);
target->name = NULL;
target->indirection = NIL;
target->val = (Node *) agg;
target->location = ctor->location;
+ alias = makeNode(Alias);
alias->aliasname = pstrdup("q");
alias->colnames = list_make1(makeString(pstrdup("a")));
+ range = makeNode(RangeSubselect);
range->lateral = false;
range->subquery = ctor->query;
range->alias = alias;
+ select = makeNode(SelectStmt);
select->targetList = list_make1(target);
select->fromClause = list_make1(range);
+ sublink = makeNode(SubLink);
sublink->subLinkType = EXPR_SUBLINK;
sublink->subLinkId = 0;
sublink->testexpr = NULL;
sublink->subselect = (Node *) select;
sublink->location = ctor->location;
- return transformExprRecurse(pstate, (Node *) sublink);
+ exec_expr = transformExprRecurse(pstate, (Node *) sublink);
+
+ /*
+ * Wrap in COALESCE so that an empty result set produces '[]' rather than
+ * NULL. The empty-array constant is created in the output type so that
+ * the COALESCE arguments have consistent types.
+ */
+ result_type = exprType(exec_expr);
+ getTypeInputInfo(result_type, &typinput, &typioparam);
+ get_typlenbyval(result_type, &typlen, &typbyval);
+
+ empty_const = makeConst(result_type,
+ -1,
+ exprCollation(exec_expr),
+ (int) typlen,
+ OidInputFunctionCall(typinput, "[]",
+ typioparam, -1),
+ false,
+ typbyval);
+
+ coalesce = makeNode(CoalesceExpr);
+ coalesce->coalescetype = result_type;
+ coalesce->coalescecollid = exprCollation(exec_expr);
+ coalesce->args = list_make2(exec_expr, empty_const);
+ coalesce->location = ctor->location;
+
+ /*
+ * Build the JSCTOR_JSON_ARRAY_QUERY node. The COALESCE goes in func as
+ * the executable form; during planning, eval_const_expressions replaces
+ * the entire node with func. The transformed Query is stored in
+ * orig_query so that ruleutils.c can deparse the original syntax.
+ */
+ args = list_make1(linitial_node(TargetEntry, query->targetList)->expr);
+ returning = transformJsonConstructorOutput(pstate, ctor->output, args);
+
+ result = makeJsonConstructorExpr(pstate, JSCTOR_JSON_ARRAY_QUERY,
+ NIL, (Expr *) coalesce, returning,
+ false, ctor->absent_on_null,
+ ctor->location);
+ ((JsonConstructorExpr *) result)->orig_query = (Node *) query;
+
+ return result;
}
/*
get_json_agg_constructor(ctor, context, "JSON_ARRAYAGG", false);
return;
}
+ else if (ctor->type == JSCTOR_JSON_ARRAY_QUERY)
+ {
+ Query *query = castNode(Query, ctor->orig_query);
+
+ appendStringInfo(buf, "JSON_ARRAY(");
+
+ get_query_def(query, buf, context->namespaces, NULL, false,
+ context->prettyFlags, context->wrapColumn,
+ context->indentLevel);
+
+ get_json_constructor_options(ctor, buf);
+ appendStringInfoChar(buf, ')');
+
+ return;
+ }
switch (ctor->type)
{
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 202604061
+#define CATALOG_VERSION_NO 202605011
#endif
typedef enum JsonConstructorType
{
JSCTOR_JSON_OBJECT = 1,
- JSCTOR_JSON_ARRAY = 2,
- JSCTOR_JSON_OBJECTAGG = 3,
- JSCTOR_JSON_ARRAYAGG = 4,
- JSCTOR_JSON_PARSE = 5,
- JSCTOR_JSON_SCALAR = 6,
- JSCTOR_JSON_SERIALIZE = 7,
+ JSCTOR_JSON_ARRAY,
+ JSCTOR_JSON_ARRAY_QUERY,
+ JSCTOR_JSON_OBJECTAGG,
+ JSCTOR_JSON_ARRAYAGG,
+ JSCTOR_JSON_PARSE,
+ JSCTOR_JSON_SCALAR,
+ JSCTOR_JSON_SERIALIZE,
} JsonConstructorType;
/*
* JsonConstructorExpr -
* wrapper over FuncExpr/Aggref/WindowFunc for SQL/JSON constructors
+ *
+ * func is the executable expression:
+ * - Aggref/WindowFunc for JSON_OBJECTAGG/JSON_ARRAYAGG,
+ * - CoalesceExpr for JSON_ARRAY_QUERY,
+ * - NULL for other types (the executor calls the underlying json[b]_xxx()
+ * functions directly).
+ *
+ * orig_query holds the user's original subquery for JSON_ARRAY(query), used
+ * only by ruleutils.c for deparsing; it is not walked because func is
+ * authoritative for all other purposes.
*/
typedef struct JsonConstructorExpr
{
Expr xpr;
JsonConstructorType type; /* constructor type */
List *args;
- Expr *func; /* underlying json[b]_xxx() function call */
+ Expr *func; /* executable expression or NULL */
Expr *coercion; /* coercion to RETURNING type */
JsonReturning *returning; /* RETURNING clause */
+ Node *orig_query; /* original subquery for deparsing */
bool absent_on_null; /* ABSENT ON NULL? */
bool unique; /* WITH UNIQUE KEYS? (JSON_OBJECT[AGG] only) */
ParseLoc location;
[[{ "a" : 123 }]]
(1 row)
+-- JSON_ARRAY(subquery)
SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i));
json_array
------------
[true]
(1 row)
+-- JSON_ARRAY(subquery) with empty result set
+SELECT JSON_ARRAY(SELECT 1 WHERE FALSE);
+ json_array
+------------
+ []
+(1 row)
+
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) WHERE i > 4);
+ json_array
+------------
+ []
+(1 row)
+
+-- JSON_ARRAY(subquery) with a correlated subquery in the WHERE clause
+SELECT * FROM (VALUES (1), (2), (NULL), (4)) t1(a)
+WHERE JSON_ARRAY(
+ SELECT b FROM (VALUES (1), (2), (3)) t2(b) WHERE b = t1.a
+ RETURNING jsonb
+) = '[]'::jsonb;
+ a
+---
+
+ 4
+(2 rows)
+
-- Should fail
SELECT JSON_ARRAY(SELECT FROM (VALUES (1)) foo(i));
ERROR: subquery must return only one column
QUERY PLAN
---------------------------------------------------------------------
Result
- Output: (InitPlan expr_1).col1
+ Output: COALESCE((InitPlan expr_1).col1, '[]'::jsonb)
InitPlan expr_1
-> Aggregate
Output: JSON_ARRAYAGG("*VALUES*".column1 RETURNING jsonb)
SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING jsonb);
\sv json_array_subquery_view
CREATE OR REPLACE VIEW public.json_array_subquery_view AS
- SELECT ( SELECT JSON_ARRAYAGG(q.a RETURNING jsonb) AS "json_arrayagg"
- FROM ( SELECT foo.i
- FROM ( VALUES (1), (2), (NULL::integer), (4)) foo(i)) q(a)) AS "json_array"
+ SELECT JSON_ARRAY( SELECT foo.i
+ FROM ( VALUES (1), (2), (NULL::integer), (4)) foo(i) RETURNING jsonb) AS "json_array"
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) ORDER BY i LIMIT 3 RETURNING jsonb);
+ QUERY PLAN
+---------------------------------------------------------------------
+ Result
+ Output: COALESCE((InitPlan expr_1).col1, '[]'::jsonb)
+ InitPlan expr_1
+ -> Aggregate
+ Output: JSON_ARRAYAGG("*VALUES*".column1 RETURNING jsonb)
+ -> Limit
+ Output: "*VALUES*".column1
+ -> Sort
+ Output: "*VALUES*".column1
+ Sort Key: "*VALUES*".column1
+ -> Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1
+(12 rows)
+
+CREATE OR REPLACE VIEW json_array_subquery_view AS
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) ORDER BY i LIMIT 3 RETURNING jsonb);
+\sv json_array_subquery_view
+CREATE OR REPLACE VIEW public.json_array_subquery_view AS
+ SELECT JSON_ARRAY( SELECT foo.i
+ FROM ( VALUES (1), (2), (NULL::integer), (4)) foo(i)
+ ORDER BY foo.i
+ LIMIT 3 RETURNING jsonb) AS "json_array"
+DROP VIEW json_array_subquery_view;
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT * FROM (VALUES (1), (2), (NULL), (4)) t1(a)
+WHERE JSON_ARRAY(
+ SELECT b FROM (VALUES (1), (2), (3)) t2(b) WHERE b = t1.a
+ RETURNING jsonb
+) = '[]'::jsonb;
+ QUERY PLAN
+-----------------------------------------------------------------------
+ Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1
+ Filter: (COALESCE((SubPlan expr_1), '[]'::jsonb) = '[]'::jsonb)
+ SubPlan expr_1
+ -> Aggregate
+ Output: JSON_ARRAYAGG("*VALUES*_1".column1 RETURNING jsonb)
+ -> Values Scan on "*VALUES*_1"
+ Output: "*VALUES*_1".column1
+ Filter: ("*VALUES*_1".column1 = "*VALUES*".column1)
+(9 rows)
+
+CREATE VIEW json_array_subquery_view AS
+SELECT * FROM (VALUES (1), (2), (NULL), (4)) t1(a)
+WHERE JSON_ARRAY(
+ SELECT b FROM (VALUES (1), (2), (3)) t2(b) WHERE b = t1.a
+ RETURNING jsonb
+) = '[]'::jsonb;
+\sv json_array_subquery_view
+CREATE OR REPLACE VIEW public.json_array_subquery_view AS
+ SELECT a
+ FROM ( VALUES (1), (2), (NULL::integer), (4)) t1(a)
+ WHERE JSON_ARRAY( SELECT t2.b
+ FROM ( VALUES (1), (2), (3)) t2(b)
+ WHERE t2.b = t1.a RETURNING jsonb) = '[]'::jsonb
+DROP VIEW json_array_subquery_view;
+-- JSON_ARRAY(subquery) with RETURNING text
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING text);
+ QUERY PLAN
+--------------------------------------------------------------------
+ Result
+ Output: COALESCE((InitPlan expr_1).col1, '[]'::text)
+ InitPlan expr_1
+ -> Aggregate
+ Output: JSON_ARRAYAGG("*VALUES*".column1 RETURNING text)
+ -> Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1
+(7 rows)
+
+CREATE VIEW json_array_subquery_view AS
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING text);
+\sv json_array_subquery_view
+CREATE OR REPLACE VIEW public.json_array_subquery_view AS
+ SELECT JSON_ARRAY( SELECT foo.i
+ FROM ( VALUES (1), (2), (NULL::integer), (4)) foo(i) RETURNING text) AS "json_array"
DROP VIEW json_array_subquery_view;
-- Test mutability of JSON_OBJECTAGG, JSON_ARRAYAGG, JSON_ARRAY, JSON_OBJECT
create type comp1 as (a int, b date);
SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING text));
SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING text) FORMAT JSON);
+-- JSON_ARRAY(subquery)
SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i));
SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i));
SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i) RETURNING jsonb);
SELECT JSON_ARRAY(SELECT i FROM (VALUES (3), (1), (NULL), (2)) foo(i) ORDER BY i);
SELECT JSON_ARRAY(WITH x AS (SELECT 1) VALUES (TRUE));
+-- JSON_ARRAY(subquery) with empty result set
+SELECT JSON_ARRAY(SELECT 1 WHERE FALSE);
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) WHERE i > 4);
+
+-- JSON_ARRAY(subquery) with a correlated subquery in the WHERE clause
+SELECT * FROM (VALUES (1), (2), (NULL), (4)) t1(a)
+WHERE JSON_ARRAY(
+ SELECT b FROM (VALUES (1), (2), (3)) t2(b) WHERE b = t1.a
+ RETURNING jsonb
+) = '[]'::jsonb;
+
-- Should fail
SELECT JSON_ARRAY(SELECT FROM (VALUES (1)) foo(i));
SELECT JSON_ARRAY(SELECT i, i FROM (VALUES (1)) foo(i));
\sv json_array_subquery_view
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) ORDER BY i LIMIT 3 RETURNING jsonb);
+
+CREATE OR REPLACE VIEW json_array_subquery_view AS
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) ORDER BY i LIMIT 3 RETURNING jsonb);
+
+\sv json_array_subquery_view
+
+DROP VIEW json_array_subquery_view;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT * FROM (VALUES (1), (2), (NULL), (4)) t1(a)
+WHERE JSON_ARRAY(
+ SELECT b FROM (VALUES (1), (2), (3)) t2(b) WHERE b = t1.a
+ RETURNING jsonb
+) = '[]'::jsonb;
+
+CREATE VIEW json_array_subquery_view AS
+SELECT * FROM (VALUES (1), (2), (NULL), (4)) t1(a)
+WHERE JSON_ARRAY(
+ SELECT b FROM (VALUES (1), (2), (3)) t2(b) WHERE b = t1.a
+ RETURNING jsonb
+) = '[]'::jsonb;
+
+\sv json_array_subquery_view
+
+DROP VIEW json_array_subquery_view;
+
+-- JSON_ARRAY(subquery) with RETURNING text
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING text);
+
+CREATE VIEW json_array_subquery_view AS
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING text);
+
+\sv json_array_subquery_view
+
DROP VIEW json_array_subquery_view;
-- Test mutability of JSON_OBJECTAGG, JSON_ARRAYAGG, JSON_ARRAY, JSON_OBJECT