mirror of
https://github.com/postgres/postgres.git
synced 2026-05-16 11:29:49 -04:00
Fix JSON_ARRAY(query) empty set handling and view deparsing
According to the SQL/JSON standard, JSON_ARRAY(query) must return an
empty JSON array ('[]') when the subquery returns zero rows.
Previously, the parser rewrote JSON_ARRAY(query) into a JSON_ARRAYAGG
aggregate function. Because this aggregate evaluates to NULL over an
empty set without a GROUP BY clause, the constructor erroneously
returned NULL. Additionally, this premature rewrite baked physical
implementation details into the catalog, preventing ruleutils.c from
deparsing the original syntax for views.
This patch resolves both issues by introducing a new
JSCTOR_JSON_ARRAY_QUERY constructor type. The parser builds the
executable form --- a COALESCE-wrapped JSON_ARRAYAGG subquery --- from
raw parse nodes via transformExprRecurse, and stores it in the func
field. The original transformed Query is kept in a new orig_query
field so that ruleutils.c can deparse the original syntax for views.
During planning, eval_const_expressions replaces the node with the
pre-built func expression.
The deparsing issue was reported by Tom Lane.
Bump catalog version.
Bug: #19418
Reported-by: Lukas Eder <lukas.eder@gmail.com>
Author: Richard Guo <guofenglinux@gmail.com>
Reviewed-by: Amit Langote <amitlangote09@gmail.com>
Discussion: https://postgr.es/m/19418-591ba1f29862ef5b@postgresql.org
This commit is contained in:
parent
6ca631b990
commit
8d829f5a02
9 changed files with 316 additions and 29 deletions
|
|
@ -620,7 +620,8 @@
|
|||
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>
|
||||
|
|
|
|||
|
|
@ -1002,8 +1002,16 @@ exprCollation(const Node *expr)
|
|||
{
|
||||
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;
|
||||
}
|
||||
|
|
@ -1264,8 +1272,11 @@ exprSetCollation(Node *expr, Oid collation)
|
|||
{
|
||||
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 */
|
||||
|
|
|
|||
|
|
@ -3244,7 +3244,6 @@ eval_const_expressions_mutator(Node *node,
|
|||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case T_JsonValueExpr:
|
||||
{
|
||||
JsonValueExpr *jve = (JsonValueExpr *) node;
|
||||
|
|
@ -3268,7 +3267,21 @@ eval_const_expressions_mutator(Node *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:
|
||||
|
||||
|
|
|
|||
|
|
@ -3792,24 +3792,53 @@ transformJsonObjectConstructor(ParseState *pstate, JsonObjectConstructor *ctor)
|
|||
}
|
||||
|
||||
/*
|
||||
* 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));
|
||||
|
|
@ -3822,14 +3851,20 @@ transformJsonArrayQueryConstructor(ParseState *pstate,
|
|||
|
||||
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;
|
||||
|
|
@ -3838,21 +3873,26 @@ transformJsonArrayQueryConstructor(ParseState *pstate,
|
|||
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;
|
||||
|
|
@ -3860,7 +3900,48 @@ transformJsonArrayQueryConstructor(ParseState *pstate,
|
|||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -12281,6 +12281,21 @@ get_json_constructor(JsonConstructorExpr *ctor, deparse_context *context,
|
|||
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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -57,6 +57,6 @@
|
|||
*/
|
||||
|
||||
/* yyyymmddN */
|
||||
#define CATALOG_VERSION_NO 202604061
|
||||
#define CATALOG_VERSION_NO 202605011
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1715,26 +1715,38 @@ typedef struct JsonValueExpr
|
|||
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;
|
||||
|
|
|
|||
|
|
@ -724,6 +724,7 @@ SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING text) FORMAT
|
|||
[[{ "a" : 123 }]]
|
||||
(1 row)
|
||||
|
||||
-- JSON_ARRAY(subquery)
|
||||
SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i));
|
||||
json_array
|
||||
------------
|
||||
|
|
@ -757,6 +758,31 @@ SELECT JSON_ARRAY(WITH x AS (SELECT 1) VALUES (TRUE));
|
|||
[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
|
||||
|
|
@ -1093,7 +1119,7 @@ SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING
|
|||
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)
|
||||
|
|
@ -1105,9 +1131,88 @@ CREATE VIEW json_array_subquery_view AS
|
|||
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);
|
||||
|
|
|
|||
|
|
@ -193,6 +193,7 @@ SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' RETURNING text));
|
|||
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);
|
||||
|
|
@ -201,6 +202,17 @@ SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL)
|
|||
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));
|
||||
|
|
@ -384,6 +396,43 @@ SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING
|
|||
|
||||
\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
|
||||
|
|
|
|||
Loading…
Reference in a new issue