diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index d0b91e881d..a9547aaef1 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -2450,6 +2450,69 @@ ExecInitExprRec(Expr *node, ExprState *state, break; } + case T_JsonConstructorExpr: + { + JsonConstructorExpr *ctor = (JsonConstructorExpr *) node; + List *args = ctor->args; + ListCell *lc; + int nargs = list_length(args); + int argno = 0; + + if (ctor->func) + { + ExecInitExprRec(ctor->func, state, resv, resnull); + } + else + { + scratch.opcode = EEOP_JSON_CONSTRUCTOR; + scratch.d.json_constructor.constructor = ctor; + scratch.d.json_constructor.arg_values = palloc(sizeof(Datum) * nargs); + scratch.d.json_constructor.arg_nulls = palloc(sizeof(bool) * nargs); + scratch.d.json_constructor.arg_types = palloc(sizeof(Oid) * nargs); + scratch.d.json_constructor.nargs = nargs; + + foreach(lc, args) + { + Expr *arg = (Expr *) lfirst(lc); + + scratch.d.json_constructor.arg_types[argno] = exprType((Node *) arg); + + if (IsA(arg, Const)) + { + /* Don't evaluate const arguments every round */ + Const *con = (Const *) arg; + + scratch.d.json_constructor.arg_values[argno] = con->constvalue; + scratch.d.json_constructor.arg_nulls[argno] = con->constisnull; + } + else + { + ExecInitExprRec(arg, state, + &scratch.d.json_constructor.arg_values[argno], + &scratch.d.json_constructor.arg_nulls[argno]); + } + argno++; + } + + ExprEvalPushStep(state, &scratch); + } + + if (ctor->coercion) + { + Datum *innermost_caseval = state->innermost_caseval; + bool *innermost_isnull = state->innermost_casenull; + + state->innermost_caseval = resv; + state->innermost_casenull = resnull; + + ExecInitExprRec(ctor->coercion, state, resv, resnull); + + state->innermost_caseval = innermost_caseval; + state->innermost_casenull = innermost_isnull; + } + } + break; + default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 64bd17b62e..f2a0821a7a 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -71,6 +71,8 @@ #include "utils/date.h" #include "utils/datum.h" #include "utils/expandedrecord.h" +#include "utils/json.h" +#include "utils/jsonb.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/timestamp.h" @@ -477,6 +479,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) &&CASE_EEOP_GROUPING_FUNC, &&CASE_EEOP_WINDOW_FUNC, &&CASE_EEOP_SUBPLAN, + &&CASE_EEOP_JSON_CONSTRUCTOR, &&CASE_EEOP_AGG_STRICT_DESERIALIZE, &&CASE_EEOP_AGG_DESERIALIZE, &&CASE_EEOP_AGG_STRICT_INPUT_CHECK_ARGS, @@ -1786,7 +1789,13 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) { /* too complex for an inline implementation */ ExecEvalAggOrderedTransTuple(state, op, econtext); + EEO_NEXT(); + } + EEO_CASE(EEOP_JSON_CONSTRUCTOR) + { + /* too complex for an inline implementation */ + ExecEvalJsonConstructor(state, op, econtext); EEO_NEXT(); } @@ -4380,3 +4389,42 @@ ExecAggPlainTransByRef(AggState *aggstate, AggStatePerTrans pertrans, MemoryContextSwitchTo(oldContext); } + +/* + * Evaluate a JSON constructor expression. + */ +void +ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op, + ExprContext *econtext) +{ + Datum res; + JsonConstructorExpr *ctor = op->d.json_constructor.constructor; + bool is_jsonb = ctor->returning->format->format_type == JS_FORMAT_JSONB; + bool isnull = false; + + if (ctor->type == JSCTOR_JSON_ARRAY) + res = (is_jsonb ? + jsonb_build_array_worker : + json_build_array_worker)(op->d.json_constructor.nargs, + op->d.json_constructor.arg_values, + op->d.json_constructor.arg_nulls, + op->d.json_constructor.arg_types, + op->d.json_constructor.constructor->absent_on_null); + else if (ctor->type == JSCTOR_JSON_OBJECT) + res = (is_jsonb ? + jsonb_build_object_worker : + json_build_object_worker)(op->d.json_constructor.nargs, + op->d.json_constructor.arg_values, + op->d.json_constructor.arg_nulls, + op->d.json_constructor.arg_types, + op->d.json_constructor.constructor->absent_on_null, + op->d.json_constructor.constructor->unique); + else + { + res = (Datum) 0; + elog(ERROR, "invalid JsonConstructorExpr type %d", ctor->type); + } + + *op->resvalue = res; + *op->resnull = isnull; +} diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c index bd86f546d7..d0c26cf58b 100644 --- a/src/backend/jit/llvm/llvmjit_expr.c +++ b/src/backend/jit/llvm/llvmjit_expr.c @@ -2348,6 +2348,12 @@ llvm_compile_expr(ExprState *state) LLVMBuildBr(b, opblocks[opno + 1]); break; + case EEOP_JSON_CONSTRUCTOR: + build_EvalXFunc(b, mod, "ExecEvalJsonConstructor", + v_state, op, v_econtext); + LLVMBuildBr(b, opblocks[opno + 1]); + break; + case EEOP_LAST: Assert(false); break; diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c index d5191cf02b..53c75dd9d6 100644 --- a/src/backend/jit/llvm/llvmjit_types.c +++ b/src/backend/jit/llvm/llvmjit_types.c @@ -131,6 +131,7 @@ void *referenced_functions[] = ExecEvalSysVar, ExecEvalWholeRowVar, ExecEvalXmlExpr, + ExecEvalJsonConstructor, MakeExpandedObjectReadOnlyInternal, slot_getmissingattrs, slot_getsomeattrs_int, diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 9d6a404760..2cbd8aa0df 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2344,6 +2344,152 @@ _copyJsonValueExpr(const JsonValueExpr *from) return newnode; } +/* + * _copyJsonConstructorExpr + */ +static JsonConstructorExpr * +_copyJsonConstructorExpr(const JsonConstructorExpr *from) +{ + JsonConstructorExpr *newnode = makeNode(JsonConstructorExpr); + + COPY_SCALAR_FIELD(type); + COPY_NODE_FIELD(args); + COPY_NODE_FIELD(func); + COPY_NODE_FIELD(coercion); + COPY_NODE_FIELD(returning); + COPY_SCALAR_FIELD(absent_on_null); + COPY_SCALAR_FIELD(unique); + COPY_LOCATION_FIELD(location); + + return newnode; +} + +/* + * _copyJsonKeyValue + */ +static JsonKeyValue * +_copyJsonKeyValue(const JsonKeyValue *from) +{ + JsonKeyValue *newnode = makeNode(JsonKeyValue); + + COPY_NODE_FIELD(key); + COPY_NODE_FIELD(value); + + return newnode; +} + +/* + * _copyJsonObjectConstructor + */ +static JsonObjectConstructor * +_copyJsonObjectConstructor(const JsonObjectConstructor *from) +{ + JsonObjectConstructor *newnode = makeNode(JsonObjectConstructor); + + COPY_NODE_FIELD(exprs); + COPY_NODE_FIELD(output); + COPY_SCALAR_FIELD(absent_on_null); + COPY_SCALAR_FIELD(unique); + COPY_LOCATION_FIELD(location); + + return newnode; +} + +/* + * _copyJsonAggConstructor + */ +static JsonAggConstructor * +_copyJsonAggConstructor(const JsonAggConstructor *from) +{ + JsonAggConstructor *newnode = makeNode(JsonAggConstructor); + + COPY_NODE_FIELD(output); + COPY_NODE_FIELD(agg_filter); + COPY_NODE_FIELD(agg_order); + COPY_NODE_FIELD(over); + COPY_LOCATION_FIELD(location); + + return newnode; +} + +/* + * _copyJsonObjectAgg + */ +static JsonObjectAgg * +_copyJsonObjectAgg(const JsonObjectAgg *from) +{ + JsonObjectAgg *newnode = makeNode(JsonObjectAgg); + + COPY_NODE_FIELD(constructor); + COPY_NODE_FIELD(arg); + COPY_SCALAR_FIELD(absent_on_null); + COPY_SCALAR_FIELD(unique); + + return newnode; +} + +/* + * _copyJsonOutput + */ +static JsonOutput * +_copyJsonOutput(const JsonOutput *from) +{ + JsonOutput *newnode = makeNode(JsonOutput); + + COPY_NODE_FIELD(typeName); + COPY_NODE_FIELD(returning); + + return newnode; +} + +/* + * _copyJsonArrayConstructor + */ +static JsonArrayConstructor * +_copyJsonArrayConstructor(const JsonArrayConstructor *from) +{ + JsonArrayConstructor *newnode = makeNode(JsonArrayConstructor); + + COPY_NODE_FIELD(exprs); + COPY_NODE_FIELD(output); + COPY_SCALAR_FIELD(absent_on_null); + COPY_LOCATION_FIELD(location); + + return newnode; +} + +/* + * _copyJsonArrayAgg + */ +static JsonArrayAgg * +_copyJsonArrayAgg(const JsonArrayAgg *from) +{ + JsonArrayAgg *newnode = makeNode(JsonArrayAgg); + + COPY_NODE_FIELD(constructor); + COPY_NODE_FIELD(arg); + COPY_SCALAR_FIELD(absent_on_null); + + return newnode; +} + +/* + * _copyJsonArrayQueryConstructor + */ +static JsonArrayQueryConstructor * +_copyJsonArrayQueryConstructor(const JsonArrayQueryConstructor *from) +{ + JsonArrayQueryConstructor *newnode = makeNode(JsonArrayQueryConstructor); + + COPY_NODE_FIELD(query); + COPY_NODE_FIELD(output); + COPY_NODE_FIELD(format); + COPY_SCALAR_FIELD(absent_on_null); + COPY_LOCATION_FIELD(location); + + return newnode; +} + /* **************************************************************** * pathnodes.h copy functions * @@ -5406,6 +5552,33 @@ copyObjectImpl(const void *from) case T_JsonValueExpr: retval = _copyJsonValueExpr(from); break; + case T_JsonKeyValue: + retval = _copyJsonKeyValue(from); + break; + case T_JsonConstructorExpr: + retval = _copyJsonConstructorExpr(from); + break; + case T_JsonObjectConstructor: + retval = _copyJsonObjectConstructor(from); + break; + case T_JsonAggConstructor: + retval = _copyJsonAggConstructor(from); + break; + case T_JsonObjectAgg: + retval = _copyJsonObjectAgg(from); + break; + case T_JsonOutput: + retval = _copyJsonOutput(from); + break; + case T_JsonArrayConstructor: + retval = _copyJsonArrayConstructor(from); + break; + case T_JsonArrayQueryConstructor: + retval = _copyJsonArrayQueryConstructor(from); + break; + case T_JsonArrayAgg: + retval = _copyJsonArrayAgg(from); + break; /* * RELATION NODES diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index e73b351acd..9f17e15e15 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -871,6 +871,111 @@ _equalJsonValueExpr(const JsonValueExpr *a, const JsonValueExpr *b) return true; } +static bool +_equalJsonConstructorExpr(const JsonConstructorExpr *a, const JsonConstructorExpr *b) +{ + COMPARE_SCALAR_FIELD(type); + COMPARE_NODE_FIELD(args); + COMPARE_NODE_FIELD(func); + COMPARE_NODE_FIELD(coercion); + COMPARE_NODE_FIELD(returning); + COMPARE_SCALAR_FIELD(absent_on_null); + COMPARE_SCALAR_FIELD(unique); + COMPARE_LOCATION_FIELD(location); + + return true; +} + +static bool +_equalJsonKeyValue(const JsonKeyValue *a, const JsonKeyValue *b) +{ + COMPARE_NODE_FIELD(key); + COMPARE_NODE_FIELD(value); + + return true; +} + +static bool +_equalJsonObjectConstructor(const JsonObjectConstructor *a, + const JsonObjectConstructor *b) +{ + COMPARE_NODE_FIELD(exprs); + COMPARE_NODE_FIELD(output); + COMPARE_SCALAR_FIELD(absent_on_null); + COMPARE_SCALAR_FIELD(unique); + COMPARE_LOCATION_FIELD(location); + + return true; +} + +static bool +_equalJsonAggConstructor(const JsonAggConstructor *a, + const JsonAggConstructor *b) +{ + COMPARE_NODE_FIELD(output); + COMPARE_NODE_FIELD(agg_filter); + COMPARE_NODE_FIELD(agg_order); + COMPARE_NODE_FIELD(over); + COMPARE_LOCATION_FIELD(location); + + return true; +} + +static bool +_equalJsonObjectAgg(const JsonObjectAgg *a, const JsonObjectAgg *b) +{ + COMPARE_NODE_FIELD(constructor); + COMPARE_NODE_FIELD(arg); + COMPARE_SCALAR_FIELD(absent_on_null); + COMPARE_SCALAR_FIELD(unique); + + return true; +} + +static bool +_equalJsonOutput(const JsonOutput *a, const JsonOutput *b) +{ + COMPARE_NODE_FIELD(typeName); + COMPARE_NODE_FIELD(returning); + + return true; +} + +static bool +_equalJsonArrayConstructor(const JsonArrayConstructor *a, + const JsonArrayConstructor *b) +{ + COMPARE_NODE_FIELD(exprs); + COMPARE_NODE_FIELD(output); + COMPARE_SCALAR_FIELD(absent_on_null); + COMPARE_LOCATION_FIELD(location); + + return true; +} + +static bool +_equalJsonArrayAgg(const JsonArrayAgg *a, const JsonArrayAgg *b) +{ + COMPARE_NODE_FIELD(constructor); + COMPARE_NODE_FIELD(arg); + COMPARE_SCALAR_FIELD(absent_on_null); + + return true; +} + +static bool +_equalJsonArrayQueryConstructor(const JsonArrayQueryConstructor *a, + const JsonArrayQueryConstructor *b) +{ + COMPARE_NODE_FIELD(query); + COMPARE_NODE_FIELD(output); + COMPARE_NODE_FIELD(format); + COMPARE_SCALAR_FIELD(absent_on_null); + COMPARE_LOCATION_FIELD(location); + + return true; +} + /* * Stuff from pathnodes.h */ @@ -3398,6 +3503,9 @@ equal(const void *a, const void *b) case T_JsonValueExpr: retval = _equalJsonValueExpr(a, b); break; + case T_JsonConstructorExpr: + retval = _equalJsonConstructorExpr(a, b); + break; /* * RELATION NODES @@ -3978,6 +4086,30 @@ equal(const void *a, const void *b) case T_PublicationTable: retval = _equalPublicationTable(a, b); break; + case T_JsonKeyValue: + retval = _equalJsonKeyValue(a, b); + break; + case T_JsonObjectConstructor: + retval = _equalJsonObjectConstructor(a, b); + break; + case T_JsonAggConstructor: + retval = _equalJsonAggConstructor(a, b); + break; + case T_JsonObjectAgg: + retval = _equalJsonObjectAgg(a, b); + break; + case T_JsonOutput: + retval = _equalJsonOutput(a, b); + break; + case T_JsonArrayConstructor: + retval = _equalJsonArrayConstructor(a, b); + break; + case T_JsonArrayQueryConstructor: + retval = _equalJsonArrayQueryConstructor(a, b); + break; + case T_JsonArrayAgg: + retval = _equalJsonArrayAgg(a, b); + break; default: elog(ERROR, "unrecognized node type: %d", diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c index 867a927e7a..7b4f7972e6 100644 --- a/src/backend/nodes/makefuncs.c +++ b/src/backend/nodes/makefuncs.c @@ -872,3 +872,18 @@ makeJsonEncoding(char *name) return JS_ENC_DEFAULT; } + +/* + * makeJsonKeyValue - + * creates a JsonKeyValue node + */ +Node * +makeJsonKeyValue(Node *key, Node *value) +{ + JsonKeyValue *n = makeNode(JsonKeyValue); + + n->key = (Expr *) key; + n->value = castNode(JsonValueExpr, value); + + return (Node *) n; +} diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 0b242c76ec..25cf282aab 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -257,6 +257,9 @@ exprType(const Node *expr) type = exprType((Node *) (jve->formatted_expr ? jve->formatted_expr : jve->raw_expr)); } break; + case T_JsonConstructorExpr: + type = ((const JsonConstructorExpr *) expr)->returning->typid; + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); type = InvalidOid; /* keep compiler quiet */ @@ -491,6 +494,8 @@ exprTypmod(const Node *expr) return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr); case T_JsonValueExpr: return exprTypmod((Node *) ((const JsonValueExpr *) expr)->formatted_expr); + case T_JsonConstructorExpr: + return -1; /* ((const JsonConstructorExpr *) expr)->returning->typmod; */ default: break; } @@ -970,6 +975,16 @@ exprCollation(const Node *expr) case T_JsonValueExpr: coll = exprCollation((Node *) ((const JsonValueExpr *) expr)->formatted_expr); break; + case T_JsonConstructorExpr: + { + const JsonConstructorExpr *ctor = (const JsonConstructorExpr *) expr; + + if (ctor->coercion) + coll = exprCollation((Node *) ctor->coercion); + else + coll = InvalidOid; + } + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); coll = InvalidOid; /* keep compiler quiet */ @@ -1186,6 +1201,16 @@ exprSetCollation(Node *expr, Oid collation) exprSetCollation((Node *) ((JsonValueExpr *) expr)->formatted_expr, collation); break; + case T_JsonConstructorExpr: + { + JsonConstructorExpr *ctor = (JsonConstructorExpr *) expr; + + if (ctor->coercion) + exprSetCollation((Node *) ctor->coercion, collation); + else + Assert(!OidIsValid(collation)); /* result is always a json[b] type */ + } + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); break; @@ -1635,6 +1660,9 @@ exprLocation(const Node *expr) case T_JsonValueExpr: loc = exprLocation((Node *) ((const JsonValueExpr *) expr)->raw_expr); break; + case T_JsonConstructorExpr: + loc = ((const JsonConstructorExpr *) expr)->location; + break; default: /* for any other node type it's just unknown... */ loc = -1; @@ -2379,6 +2407,18 @@ expression_tree_walker(Node *node, return true; } break; + case T_JsonConstructorExpr: + { + JsonConstructorExpr *ctor = (JsonConstructorExpr *) node; + + if (walker(ctor->args, context)) + return true; + if (walker(ctor->func, context)) + return true; + if (walker(ctor->coercion, context)) + return true; + } + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); @@ -3361,6 +3401,19 @@ expression_tree_mutator(Node *node, MUTATE(newnode->formatted_expr, jve->formatted_expr, Expr *); MUTATE(newnode->format, jve->format, JsonFormat *); + return (Node *) newnode; + } + case T_JsonConstructorExpr: + { + JsonConstructorExpr *jve = (JsonConstructorExpr *) node; + JsonConstructorExpr *newnode; + + FLATCOPY(newnode, jve, JsonConstructorExpr); + MUTATE(newnode->args, jve->args, List *); + MUTATE(newnode->func, jve->func, Expr *); + MUTATE(newnode->coercion, jve->coercion, Expr *); + MUTATE(newnode->returning, jve->returning, JsonReturning *); + return (Node *) newnode; } default: @@ -3637,6 +3690,7 @@ raw_expression_tree_walker(Node *node, case T_ParamRef: case T_A_Const: case T_A_Star: + case T_JsonFormat: /* primitive node types with no subnodes */ break; case T_Alias: @@ -4085,6 +4139,104 @@ raw_expression_tree_walker(Node *node, return true; } break; + case T_JsonConstructorExpr: + { + JsonConstructorExpr *ctor = (JsonConstructorExpr *) node; + + if (walker(ctor->args, context)) + return true; + if (walker(ctor->func, context)) + return true; + if (walker(ctor->coercion, context)) + return true; + if (walker(ctor->returning, context)) + return true; + } + break; + case T_JsonOutput: + { + JsonOutput *out = (JsonOutput *) node; + + if (walker(out->typeName, context)) + return true; + if (walker(out->returning, context)) + return true; + } + break; + case T_JsonKeyValue: + { + JsonKeyValue *jkv = (JsonKeyValue *) node; + + if (walker(jkv->key, context)) + return true; + if (walker(jkv->value, context)) + return true; + } + break; + case T_JsonObjectConstructor: + { + JsonObjectConstructor *joc = (JsonObjectConstructor *) node; + + if (walker(joc->output, context)) + return true; + if (walker(joc->exprs, context)) + return true; + } + break; + case T_JsonArrayConstructor: + { + JsonArrayConstructor *jac = (JsonArrayConstructor *) node; + + if (walker(jac->output, context)) + return true; + if (walker(jac->exprs, context)) + return true; + } + break; + case T_JsonAggConstructor: + { + JsonAggConstructor *ctor = (JsonAggConstructor *) node; + + if (walker(ctor->output, context)) + return true; + if (walker(ctor->agg_order, context)) + return true; + if (walker(ctor->agg_filter, context)) + return true; + if (walker(ctor->over, context)) + return true; + } + break; + case T_JsonObjectAgg: + { + JsonObjectAgg *joa = (JsonObjectAgg *) node; + + if (walker(joa->constructor, context)) + return true; + if (walker(joa->arg, context)) + return true; + } + break; + case T_JsonArrayAgg: + { + JsonArrayAgg *jaa = (JsonArrayAgg *) node; + + if (walker(jaa->constructor, context)) + return true; + if (walker(jaa->arg, context)) + return true; + } + break; + case T_JsonArrayQueryConstructor: + { + JsonArrayQueryConstructor *jaqc = (JsonArrayQueryConstructor *) node; + + if (walker(jaqc->output, context)) + return true; + if (walker(jaqc->query, context)) + return true; + } + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 449d90c8f4..c25f0bd684 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1781,6 +1781,21 @@ _outJsonValueExpr(StringInfo str, const JsonValueExpr *node) WRITE_NODE_FIELD(format); } +static void +_outJsonConstructorExpr(StringInfo str, const JsonConstructorExpr *node) +{ + WRITE_NODE_TYPE("JSONCTOREXPR"); + + WRITE_NODE_FIELD(args); + WRITE_NODE_FIELD(func); + WRITE_NODE_FIELD(coercion); + WRITE_INT_FIELD(type); + WRITE_NODE_FIELD(returning); + WRITE_BOOL_FIELD(unique); + WRITE_BOOL_FIELD(absent_on_null); + WRITE_LOCATION_FIELD(location); +} + /***************************************************************************** * * Stuff from pathnodes.h. @@ -4576,6 +4591,9 @@ outNode(StringInfo str, const void *obj) case T_JsonValueExpr: _outJsonValueExpr(str, obj); break; + case T_JsonConstructorExpr: + _outJsonConstructorExpr(str, obj); + break; default: diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 6f398cdc15..e0b3ad1ed2 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -1434,6 +1434,26 @@ _readJsonValueExpr(void) READ_DONE(); } +/* + * _readJsonConstructorExpr + */ +static JsonConstructorExpr * +_readJsonConstructorExpr(void) +{ + READ_LOCALS(JsonConstructorExpr); + + READ_NODE_FIELD(args); + READ_NODE_FIELD(func); + READ_NODE_FIELD(coercion); + READ_INT_FIELD(type); + READ_NODE_FIELD(returning); + READ_BOOL_FIELD(unique); + READ_BOOL_FIELD(absent_on_null); + READ_LOCATION_FIELD(location); + + READ_DONE(); +} + /* * Stuff from pathnodes.h. * @@ -3025,6 +3045,8 @@ parseNodeString(void) return_value = _readJsonReturning(); else if (MATCH("JSONVALUEEXPR", 13)) return_value = _readJsonValueExpr(); + else if (MATCH("JSONCTOREXPR", 12)) + return_value = _readJsonConstructorExpr(); else { elog(ERROR, "badly formatted node string \"%.32s\"...", token); diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index b9cefe8847..e1147c431e 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -50,6 +50,8 @@ #include "utils/builtins.h" #include "utils/datum.h" #include "utils/fmgroids.h" +#include "utils/json.h" +#include "utils/jsonb.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/syscache.h" @@ -382,6 +384,27 @@ contain_mutable_functions_walker(Node *node, void *context) context)) return true; + if (IsA(node, JsonConstructorExpr)) + { + const JsonConstructorExpr *ctor = (JsonConstructorExpr *) node; + ListCell *lc; + bool is_jsonb = + ctor->returning->format->format_type == JS_FORMAT_JSONB; + + /* Check argument_type => json[b] conversions */ + foreach(lc, ctor->args) + { + Oid typid = exprType(lfirst(lc)); + + if (is_jsonb ? + !to_jsonb_is_immutable(typid) : + !to_json_is_immutable(typid)) + return true; + } + + /* Check all subnodes */ + } + if (IsA(node, SQLValueFunction)) { /* all variants of SQLValueFunction are stable */ diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index a324983f5d..c6613af9fe 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -639,11 +639,31 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type json_format_clause_opt json_representation json_value_expr + json_func_expr json_output_clause_opt + json_value_constructor + json_object_constructor + json_object_constructor_args + json_object_constructor_args_opt + json_object_args + json_object_func_args + json_array_constructor + json_name_and_value + json_aggregate_func + json_object_aggregate_constructor + json_array_aggregate_constructor + +%type json_name_and_value_list + json_value_expr_list + json_array_aggregate_order_by_clause_opt %type json_encoding json_encoding_clause_opt +%type json_key_uniqueness_constraint_opt + json_object_constructor_null_clause_opt + json_array_constructor_null_clause_opt + /* * Non-keyword token types. These are hard-wired into the "flex" lexer. * They must be listed first so that their numeric codes do not depend on @@ -669,7 +689,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); */ /* ordinary key words in alphabetical order */ -%token ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER +%token ABORT_P ABSENT ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC ASENSITIVE ASSERTION ASSIGNMENT ASYMMETRIC ATOMIC AT ATTACH ATTRIBUTE AUTHORIZATION @@ -706,9 +726,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION - JOIN JSON + JOIN JSON JSON_ARRAY JSON_ARRAYAGG JSON_OBJECT JSON_OBJECTAGG - KEY + KEY KEYS LABEL LANGUAGE LARGE_P LAST_P LATERAL_P LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL @@ -772,7 +792,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); * as NOT, at least with respect to their left-hand subexpression. * NULLS_LA and WITH_LA are needed to make the grammar LALR(1). */ -%token NOT_LA NULLS_LA WITH_LA +%token NOT_LA NULLS_LA WITH_LA WITH_LA_UNIQUE WITHOUT_LA /* * The grammar likewise thinks these tokens are keywords, but they are never @@ -826,11 +846,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); * Using the same precedence as IDENT seems right for the reasons given above. */ %nonassoc UNBOUNDED /* ideally would have same precedence as IDENT */ +%nonassoc ABSENT UNIQUE %nonassoc IDENT PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP %left Op OPERATOR /* multi-character ops and user-defined operators */ %left '+' '-' %left '*' '/' '%' %left '^' +%left KEYS /* UNIQUE [ KEYS ] */ /* Unary Operators */ %left AT /* sets precedence for AT TIME ZONE */ %left COLLATE @@ -848,6 +870,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); */ %left JOIN CROSS LEFT FULL RIGHT INNER_P NATURAL +%nonassoc empty_json_unique +%left WITHOUT WITH_LA_UNIQUE + %% /* @@ -13415,7 +13440,7 @@ ConstInterval: opt_timezone: WITH_LA TIME ZONE { $$ = true; } - | WITHOUT TIME ZONE { $$ = false; } + | WITHOUT_LA TIME ZONE { $$ = false; } | /*EMPTY*/ { $$ = false; } ; @@ -14028,6 +14053,17 @@ b_expr: c_expr } ; +json_key_uniqueness_constraint_opt: + WITH_LA_UNIQUE unique_keys { $$ = true; } + | WITHOUT unique_keys { $$ = false; } + | /* EMPTY */ %prec empty_json_unique { $$ = false; } + ; + +unique_keys: + UNIQUE + | UNIQUE KEYS + ; + /* * Productions that can be used in both a_expr and b_expr. * @@ -14280,6 +14316,15 @@ func_expr: func_application within_group_clause filter_clause over_clause n->over = $4; $$ = (Node *) n; } + | json_aggregate_func filter_clause over_clause + { + JsonAggConstructor *n = IsA($1, JsonObjectAgg) ? + ((JsonObjectAgg *) $1)->constructor : + ((JsonArrayAgg *) $1)->constructor; + n->agg_filter = $2; + n->over = $3; + $$ = (Node *) $1; + } | func_expr_common_subexpr { $$ = $1; } ; @@ -14293,6 +14338,7 @@ func_expr: func_application within_group_clause filter_clause over_clause func_expr_windowless: func_application { $$ = $1; } | func_expr_common_subexpr { $$ = $1; } + | json_aggregate_func { $$ = $1; } ; /* @@ -14580,6 +14626,8 @@ func_expr_common_subexpr: n->location = @1; $$ = (Node *)n; } + | json_func_expr + { $$ = $1; } ; /* @@ -15279,11 +15327,14 @@ opt_asymmetric: ASYMMETRIC ; /* SQL/JSON support */ +json_func_expr: + json_value_constructor + ; json_value_expr: a_expr json_format_clause_opt { - $$ = (Node *) makeJsonValueExpr((Expr *) $1, $2); + $$ = (Node *) makeJsonValueExpr((Expr *) $1, castNode(JsonFormat, $2)); } ; @@ -15291,7 +15342,7 @@ json_format_clause_opt: FORMAT json_representation { $$ = $2; - $$.location = @1; + castNode(JsonFormat, $$)->location = @1; } | /* EMPTY */ { @@ -15321,10 +15372,196 @@ json_output_clause_opt: { JsonOutput *n = makeNode(JsonOutput); n->typeName = $2; - n->returning.format = $3; + n->returning = makeNode(JsonReturning); + n->returning->format = (JsonFormat *) $3; $$ = (Node *) n; } | /* EMPTY */ { $$ = NULL; } + ; + +json_value_constructor: + json_object_constructor + | json_array_constructor + ; + +json_object_constructor: + JSON_OBJECT '(' json_object_args ')' + { + $$ = $3; + } + ; + +json_object_args: + json_object_constructor_args + | json_object_func_args + ; + +json_object_func_args: + func_arg_list + { + List *func = list_make1(makeString("json_object")); + $$ = (Node *) makeFuncCall(func, $1, COERCE_EXPLICIT_CALL, @1); + } + ; + +json_object_constructor_args: + json_object_constructor_args_opt json_output_clause_opt + { + JsonObjectConstructor *n = (JsonObjectConstructor *) $1; + n->output = (JsonOutput *) $2; + n->location = @1; + $$ = (Node *) n; + } + ; + +json_object_constructor_args_opt: + json_name_and_value_list + json_object_constructor_null_clause_opt + json_key_uniqueness_constraint_opt + { + JsonObjectConstructor *n = makeNode(JsonObjectConstructor); + n->exprs = $1; + n->absent_on_null = $2; + n->unique = $3; + $$ = (Node *) n; + } + | /* EMPTY */ + { + JsonObjectConstructor *n = makeNode(JsonObjectConstructor); + n->exprs = NULL; + n->absent_on_null = false; + n->unique = false; + $$ = (Node *) n; + } + ; + +json_name_and_value_list: + json_name_and_value + { $$ = list_make1($1); } + | json_name_and_value_list ',' json_name_and_value + { $$ = lappend($1, $3); } + ; + +json_name_and_value: +/* TODO This is not supported due to conflicts + KEY c_expr VALUE_P json_value_expr %prec POSTFIXOP + { $$ = makeJsonKeyValue($2, $4); } + | +*/ + c_expr VALUE_P json_value_expr + { $$ = makeJsonKeyValue($1, $3); } + | + a_expr ':' json_value_expr + { $$ = makeJsonKeyValue($1, $3); } + ; + +json_object_constructor_null_clause_opt: + NULL_P ON NULL_P { $$ = false; } + | ABSENT ON NULL_P { $$ = true; } + | /* EMPTY */ { $$ = false; } + ; + +json_array_constructor: + JSON_ARRAY '(' + json_value_expr_list + json_array_constructor_null_clause_opt + json_output_clause_opt + ')' + { + JsonArrayConstructor *n = makeNode(JsonArrayConstructor); + n->exprs = $3; + n->absent_on_null = $4; + n->output = (JsonOutput *) $5; + n->location = @1; + $$ = (Node *) n; + } + | JSON_ARRAY '(' + select_no_parens + /* json_format_clause_opt */ + /* json_array_constructor_null_clause_opt */ + json_output_clause_opt + ')' + { + JsonArrayQueryConstructor *n = makeNode(JsonArrayQueryConstructor); + n->query = $3; + n->format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1); + /* n->format = $4; */ + n->absent_on_null = true /* $5 */; + n->output = (JsonOutput *) $4; + n->location = @1; + $$ = (Node *) n; + } + | JSON_ARRAY '(' + json_output_clause_opt + ')' + { + JsonArrayConstructor *n = makeNode(JsonArrayConstructor); + n->exprs = NIL; + n->absent_on_null = true; + n->output = (JsonOutput *) $3; + n->location = @1; + $$ = (Node *) n; + } + ; + +json_value_expr_list: + json_value_expr { $$ = list_make1($1); } + | json_value_expr_list ',' json_value_expr { $$ = lappend($1, $3);} + ; + +json_array_constructor_null_clause_opt: + NULL_P ON NULL_P { $$ = false; } + | ABSENT ON NULL_P { $$ = true; } + | /* EMPTY */ { $$ = true; } + ; + +json_aggregate_func: + json_object_aggregate_constructor + | json_array_aggregate_constructor + ; + +json_object_aggregate_constructor: + JSON_OBJECTAGG '(' + json_name_and_value + json_object_constructor_null_clause_opt + json_key_uniqueness_constraint_opt + json_output_clause_opt + ')' + { + JsonObjectAgg *n = makeNode(JsonObjectAgg); + n->arg = (JsonKeyValue *) $3; + n->absent_on_null = $4; + n->unique = $5; + n->constructor = makeNode(JsonAggConstructor); + n->constructor->output = (JsonOutput *) $6; + n->constructor->agg_order = NULL; + n->constructor->location = @1; + $$ = (Node *) n; + } + ; + +json_array_aggregate_constructor: + JSON_ARRAYAGG '(' + json_value_expr + json_array_aggregate_order_by_clause_opt + json_array_constructor_null_clause_opt + json_output_clause_opt + ')' + { + JsonArrayAgg *n = makeNode(JsonArrayAgg); + n->arg = (JsonValueExpr *) $3; + n->absent_on_null = $5; + n->constructor = makeNode(JsonAggConstructor); + n->constructor->agg_order = $4; + n->constructor->output = (JsonOutput *) $6; + n->constructor->location = @1; + $$ = (Node *) n; + } + ; + +json_array_aggregate_order_by_clause_opt: + ORDER BY sortby_list { $$ = $3; } + | /* EMPTY */ { $$ = NIL; } ; /***************************************************************************** @@ -15771,6 +16008,7 @@ BareColLabel: IDENT { $$ = $1; } */ unreserved_keyword: ABORT_P + | ABSENT | ABSOLUTE_P | ACCESS | ACTION @@ -15901,6 +16139,7 @@ unreserved_keyword: | ISOLATION | JSON | KEY + | KEYS | LABEL | LANGUAGE | LARGE_P @@ -16109,6 +16348,10 @@ col_name_keyword: | INT_P | INTEGER | INTERVAL + | JSON_ARRAY + | JSON_ARRAYAGG + | JSON_OBJECT + | JSON_OBJECTAGG | LEAST | NATIONAL | NCHAR @@ -16277,6 +16520,7 @@ reserved_keyword: */ bare_label_keyword: ABORT_P + | ABSENT | ABSOLUTE_P | ACCESS | ACTION @@ -16462,7 +16706,12 @@ bare_label_keyword: | ISOLATION | JOIN | JSON + | JSON_ARRAY + | JSON_ARRAYAGG + | JSON_OBJECT + | JSON_OBJECTAGG | KEY + | KEYS | LABEL | LANGUAGE | LARGE_P diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 985ddbedf1..6b93a76bca 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -15,6 +15,8 @@ #include "postgres.h" +#include "catalog/pg_aggregate.h" +#include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "commands/dbcommands.h" #include "miscadmin.h" @@ -75,6 +77,14 @@ static Node *transformWholeRowRef(ParseState *pstate, static Node *transformIndirection(ParseState *pstate, A_Indirection *ind); static Node *transformTypeCast(ParseState *pstate, TypeCast *tc); static Node *transformCollateClause(ParseState *pstate, CollateClause *c); +static Node *transformJsonObjectConstructor(ParseState *pstate, + JsonObjectConstructor *ctor); +static Node *transformJsonArrayConstructor(ParseState *pstate, + JsonArrayConstructor *ctor); +static Node *transformJsonArrayQueryConstructor(ParseState *pstate, + JsonArrayQueryConstructor *ctor); +static Node *transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg); +static Node *transformJsonArrayAgg(ParseState *pstate, JsonArrayAgg *agg); static Node *make_row_comparison_op(ParseState *pstate, List *opname, List *largs, List *rargs, int location); static Node *make_row_distinct_op(ParseState *pstate, List *opname, @@ -302,6 +312,26 @@ transformExprRecurse(ParseState *pstate, Node *expr) break; } + case T_JsonObjectConstructor: + result = transformJsonObjectConstructor(pstate, (JsonObjectConstructor *) expr); + break; + + case T_JsonArrayConstructor: + result = transformJsonArrayConstructor(pstate, (JsonArrayConstructor *) expr); + break; + + case T_JsonArrayQueryConstructor: + result = transformJsonArrayQueryConstructor(pstate, (JsonArrayQueryConstructor *) expr); + break; + + case T_JsonObjectAgg: + result = transformJsonObjectAgg(pstate, (JsonObjectAgg *) expr); + break; + + case T_JsonArrayAgg: + result = transformJsonArrayAgg(pstate, (JsonArrayAgg *) expr); + break; + default: /* should not reach here */ elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); @@ -3280,3 +3310,562 @@ transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve, return expr; } + +/* + * Checks specified output format for its applicability to the target type. + */ +static void +checkJsonOutputFormat(ParseState *pstate, const JsonFormat *format, + Oid targettype, bool allow_format_for_non_strings) +{ + if (!allow_format_for_non_strings && + format->format_type != JS_FORMAT_DEFAULT && + (targettype != BYTEAOID && + targettype != JSONOID && + targettype != JSONBOID)) + { + char typcategory; + bool typispreferred; + + get_type_category_preferred(targettype, &typcategory, &typispreferred); + + if (typcategory != TYPCATEGORY_STRING) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + parser_errposition(pstate, format->location), + errmsg("cannot use JSON format with non-string output types"))); + } + + if (format->format_type == JS_FORMAT_JSON) + { + JsonEncoding enc = format->encoding != JS_ENC_DEFAULT ? + format->encoding : JS_ENC_UTF8; + + if (targettype != BYTEAOID && + format->encoding != JS_ENC_DEFAULT) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + parser_errposition(pstate, format->location), + errmsg("cannot set JSON encoding for non-bytea output types"))); + + if (enc != JS_ENC_UTF8) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unsupported JSON encoding"), + errhint("only UTF8 JSON encoding is supported"), + parser_errposition(pstate, format->location))); + } +} + +/* + * Transform JSON output clause. + * + * Assigns target type oid and modifier. + * Assigns default format or checks specified format for its applicability to + * the target type. + */ +static JsonReturning * +transformJsonOutput(ParseState *pstate, const JsonOutput *output, + bool allow_format) +{ + JsonReturning *ret; + + /* if output clause is not specified, make default clause value */ + if (!output) + { + ret = makeNode(JsonReturning); + + ret->format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1); + ret->typid = InvalidOid; + ret->typmod = -1; + + return ret; + } + + ret = copyObject(output->returning); + + typenameTypeIdAndMod(pstate, output->typeName, &ret->typid, &ret->typmod); + + if (output->typeName->setof) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("returning SETOF types is not supported in SQL/JSON functions"))); + + if (ret->format->format_type == JS_FORMAT_DEFAULT) + /* assign JSONB format when returning jsonb, or JSON format otherwise */ + ret->format->format_type = + ret->typid == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON; + else + checkJsonOutputFormat(pstate, ret->format, ret->typid, allow_format); + + return ret; +} + +/* + * Transform JSON output clause of JSON contructor functions. + * + * Derive RETURNING type, if not specified, from argument types. + */ +static JsonReturning * +transformJsonConstructorOutput(ParseState *pstate, JsonOutput *output, + List *args) +{ + JsonReturning *returning = transformJsonOutput(pstate, output, true); + + if (!OidIsValid(returning->typid)) + { + ListCell *lc; + bool have_json = false; + bool have_jsonb = false; + + foreach(lc, args) + { + Node *expr = lfirst(lc); + Oid typid = exprType(expr); + + have_json |= typid == JSONOID; + have_jsonb |= typid == JSONBOID; + + if (have_jsonb) + break; + } + + if (have_jsonb) + { + returning->typid = JSONBOID; + returning->format->format_type = JS_FORMAT_JSONB; + } + else + { + /* Note: this includes the have_json case */ + + /* XXX TEXT is default by the standard, but we return JSON */ + returning->typid = JSONOID; + returning->format->format_type = JS_FORMAT_JSON; + } + + returning->typmod = -1; + } + + return returning; +} + +/* + * Coerce json[b]-valued function expression to the output type. + */ +static Node * +coerceJsonFuncExpr(ParseState *pstate, Node *expr, + const JsonReturning *returning, bool report_error) +{ + Node *res; + int location; + Oid exprtype = exprType(expr); + + /* if output type is not specified or equals to function type, return */ + if (!OidIsValid(returning->typid) || returning->typid == exprtype) + return expr; + + location = exprLocation(expr); + + if (location < 0) + location = returning ? returning->format->location : -1; + + /* special case for RETURNING bytea FORMAT json */ + if (returning->format->format_type == JS_FORMAT_JSON && + returning->typid == BYTEAOID) + { + /* encode json text into bytea using pg_convert_to() */ + Node *texpr = coerce_to_specific_type(pstate, expr, TEXTOID, + "JSON_FUNCTION"); + Const *enc = getJsonEncodingConst(returning->format); + FuncExpr *fexpr = makeFuncExpr(F_CONVERT_TO, BYTEAOID, + list_make2(texpr, enc), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + fexpr->location = location; + + return (Node *) fexpr; + } + + /* try to coerce expression to the output type */ + res = coerce_to_target_type(pstate, expr, exprtype, + returning->typid, returning->typmod, + /* XXX throwing errors when casting to char(N) */ + COERCION_EXPLICIT, + COERCE_EXPLICIT_CAST, + location); + + if (!res && report_error) + ereport(ERROR, + (errcode(ERRCODE_CANNOT_COERCE), + errmsg("cannot cast type %s to %s", + format_type_be(exprtype), + format_type_be(returning->typid)), + parser_coercion_errposition(pstate, location, expr))); + + return res; +} + +static Node * +makeJsonConstructorExpr(ParseState *pstate, JsonConstructorType type, + List *args, Expr *fexpr, JsonReturning *returning, + bool unique, bool absent_on_null, int location) +{ + JsonConstructorExpr *jsctor = makeNode(JsonConstructorExpr); + Node *placeholder; + Node *coercion; + Oid intermediate_typid = + returning->format->format_type == JS_FORMAT_JSONB ? JSONBOID : JSONOID; + + jsctor->args = args; + jsctor->func = fexpr; + jsctor->type = type; + jsctor->returning = returning; + jsctor->unique = unique; + jsctor->absent_on_null = absent_on_null; + jsctor->location = location; + + if (fexpr) + placeholder = makeCaseTestExpr((Node *) fexpr); + else + { + CaseTestExpr *cte = makeNode(CaseTestExpr); + + cte->typeId = intermediate_typid; + cte->typeMod = -1; + cte->collation = InvalidOid; + + placeholder = (Node *) cte; + } + + coercion = coerceJsonFuncExpr(pstate, placeholder, returning, true); + + if (coercion != placeholder) + jsctor->coercion = (Expr *) coercion; + + return (Node *) jsctor; +} + +/* + * Transform JSON_OBJECT() constructor. + * + * JSON_OBJECT() is transformed into json[b]_build_object[_ext]() call + * depending on the output JSON format. The first two arguments of + * json[b]_build_object_ext() are absent_on_null and check_key_uniqueness. + * + * Then function call result is coerced to the target type. + */ +static Node * +transformJsonObjectConstructor(ParseState *pstate, JsonObjectConstructor *ctor) +{ + JsonReturning *returning; + List *args = NIL; + + /* transform key-value pairs, if any */ + if (ctor->exprs) + { + ListCell *lc; + + /* transform and append key-value arguments */ + foreach(lc, ctor->exprs) + { + JsonKeyValue *kv = castNode(JsonKeyValue, lfirst(lc)); + Node *key = transformExprRecurse(pstate, (Node *) kv->key); + Node *val = transformJsonValueExpr(pstate, kv->value, + JS_FORMAT_DEFAULT); + + args = lappend(args, key); + args = lappend(args, val); + } + } + + returning = transformJsonConstructorOutput(pstate, ctor->output, args); + + return makeJsonConstructorExpr(pstate, JSCTOR_JSON_OBJECT, args, NULL, + returning, ctor->unique, + ctor->absent_on_null, ctor->location); +} + +/* + * Transform JSON_ARRAY(query [FORMAT] [RETURNING] [ON NULL]) into + * (SELECT JSON_ARRAYAGG(a [FORMAT] [RETURNING] [ON NULL]) FROM (query) q(a)) + */ +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; + + /* Transform query only for counting target list entries. */ + qpstate = make_parsestate(pstate); + + query = transformStmt(qpstate, ctor->query); + + if (count_nonjunk_tlist_entries(query->targetList) != 1) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subquery must return only one column"), + parser_errposition(pstate, ctor->location))); + + free_parsestate(qpstate); + + colref->fields = list_make2(makeString(pstrdup("q")), + makeString(pstrdup("a"))); + colref->location = ctor->location; + + agg->arg = makeJsonValueExpr((Expr *) colref, ctor->format); + agg->absent_on_null = ctor->absent_on_null; + agg->constructor = makeNode(JsonAggConstructor); + agg->constructor->agg_order = NIL; + agg->constructor->output = ctor->output; + agg->constructor->location = ctor->location; + + target->name = NULL; + target->indirection = NIL; + target->val = (Node *) agg; + target->location = ctor->location; + + alias->aliasname = pstrdup("q"); + alias->colnames = list_make1(makeString(pstrdup("a"))); + + range->lateral = false; + range->subquery = ctor->query; + range->alias = alias; + + select->targetList = list_make1(target); + select->fromClause = list_make1(range); + + sublink->subLinkType = EXPR_SUBLINK; + sublink->subLinkId = 0; + sublink->testexpr = NULL; + sublink->operName = NIL; + sublink->subselect = (Node *) select; + sublink->location = ctor->location; + + return transformExprRecurse(pstate, (Node *) sublink); +} + +/* + * Common code for JSON_OBJECTAGG and JSON_ARRAYAGG transformation. + */ +static Node * +transformJsonAggConstructor(ParseState *pstate, JsonAggConstructor *agg_ctor, + JsonReturning *returning, List *args, + const char *aggfn, Oid aggtype, + JsonConstructorType ctor_type, + bool unique, bool absent_on_null) +{ + Oid aggfnoid; + Node *node; + Expr *aggfilter = agg_ctor->agg_filter ? (Expr *) + transformWhereClause(pstate, agg_ctor->agg_filter, + EXPR_KIND_FILTER, "FILTER") : NULL; + + aggfnoid = DatumGetInt32(DirectFunctionCall1(regprocin, + CStringGetDatum(aggfn))); + + if (agg_ctor->over) + { + /* window function */ + WindowFunc *wfunc = makeNode(WindowFunc); + + wfunc->winfnoid = aggfnoid; + wfunc->wintype = aggtype; + /* wincollid and inputcollid will be set by parse_collate.c */ + wfunc->args = args; + /* winref will be set by transformWindowFuncCall */ + wfunc->winstar = false; + wfunc->winagg = true; + wfunc->aggfilter = aggfilter; + wfunc->location = agg_ctor->location; + + /* + * ordered aggs not allowed in windows yet + */ + if (agg_ctor->agg_order != NIL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("aggregate ORDER BY is not implemented for window functions"), + parser_errposition(pstate, agg_ctor->location))); + + /* parse_agg.c does additional window-func-specific processing */ + transformWindowFuncCall(pstate, wfunc, agg_ctor->over); + + node = (Node *) wfunc; + } + else + { + Aggref *aggref = makeNode(Aggref); + + aggref->aggfnoid = aggfnoid; + aggref->aggtype = aggtype; + + /* aggcollid and inputcollid will be set by parse_collate.c */ + aggref->aggtranstype = InvalidOid; /* will be set by planner */ + /* aggargtypes will be set by transformAggregateCall */ + /* aggdirectargs and args will be set by transformAggregateCall */ + /* aggorder and aggdistinct will be set by transformAggregateCall */ + aggref->aggfilter = aggfilter; + aggref->aggstar = false; + aggref->aggvariadic = false; + aggref->aggkind = AGGKIND_NORMAL; + /* agglevelsup will be set by transformAggregateCall */ + aggref->aggsplit = AGGSPLIT_SIMPLE; /* planner might change this */ + aggref->location = agg_ctor->location; + + transformAggregateCall(pstate, aggref, args, agg_ctor->agg_order, false); + + node = (Node *) aggref; + } + + return makeJsonConstructorExpr(pstate, ctor_type, NIL, (Expr *) node, + returning, unique, absent_on_null, + agg_ctor->location); +} + +/* + * Transform JSON_OBJECTAGG() aggregate function. + * + * JSON_OBJECTAGG() is transformed into + * json[b]_objectagg(key, value, absent_on_null, check_unique) call depending on + * the output JSON format. Then the function call result is coerced to the + * target output type. + */ +static Node * +transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg) +{ + JsonReturning *returning; + Node *key; + Node *val; + List *args; + const char *aggfnname; + Oid aggtype; + + key = transformExprRecurse(pstate, (Node *) agg->arg->key); + val = transformJsonValueExpr(pstate, agg->arg->value, JS_FORMAT_DEFAULT); + args = list_make2(key, val); + + returning = transformJsonConstructorOutput(pstate, agg->constructor->output, + args); + + if (returning->format->format_type == JS_FORMAT_JSONB) + { + if (agg->absent_on_null) + if (agg->unique) + aggfnname = "pg_catalog.jsonb_object_agg_unique_strict"; /* F_JSONB_OBJECT_AGG_UNIQUE_STRICT */ + else + aggfnname = "pg_catalog.jsonb_object_agg_strict"; /* F_JSONB_OBJECT_AGG_STRICT */ + else + if (agg->unique) + aggfnname = "pg_catalog.jsonb_object_agg_unique"; /* F_JSONB_OBJECT_AGG_UNIQUE */ + else + aggfnname = "pg_catalog.jsonb_object_agg"; /* F_JSONB_OBJECT_AGG */ + + aggtype = JSONBOID; + } + else + { + if (agg->absent_on_null) + if (agg->unique) + aggfnname = "pg_catalog.json_object_agg_unique_strict"; /* F_JSON_OBJECT_AGG_UNIQUE_STRICT */ + else + aggfnname = "pg_catalog.json_object_agg_strict"; /* F_JSON_OBJECT_AGG_STRICT */ + else + if (agg->unique) + aggfnname = "pg_catalog.json_object_agg_unique"; /* F_JSON_OBJECT_AGG_UNIQUE */ + else + aggfnname = "pg_catalog.json_object_agg"; /* F_JSON_OBJECT_AGG */ + + aggtype = JSONOID; + } + + return transformJsonAggConstructor(pstate, agg->constructor, returning, + args, aggfnname, aggtype, + JSCTOR_JSON_OBJECTAGG, + agg->unique, agg->absent_on_null); +} + +/* + * Transform JSON_ARRAYAGG() aggregate function. + * + * JSON_ARRAYAGG() is transformed into json[b]_agg[_strict]() call depending + * on the output JSON format and absent_on_null. Then the function call result + * is coerced to the target output type. + */ +static Node * +transformJsonArrayAgg(ParseState *pstate, JsonArrayAgg *agg) +{ + JsonReturning *returning; + Node *arg; + const char *aggfnname; + Oid aggtype; + + arg = transformJsonValueExpr(pstate, agg->arg, JS_FORMAT_DEFAULT); + + returning = transformJsonConstructorOutput(pstate, agg->constructor->output, + list_make1(arg)); + + if (returning->format->format_type == JS_FORMAT_JSONB) + { + aggfnname = agg->absent_on_null ? + "pg_catalog.jsonb_agg_strict" : "pg_catalog.jsonb_agg"; + aggtype = JSONBOID; + } + else + { + aggfnname = agg->absent_on_null ? + "pg_catalog.json_agg_strict" : "pg_catalog.json_agg"; + aggtype = JSONOID; + } + + return transformJsonAggConstructor(pstate, agg->constructor, returning, + list_make1(arg), aggfnname, aggtype, + JSCTOR_JSON_ARRAYAGG, + false, agg->absent_on_null); +} + +/* + * Transform JSON_ARRAY() constructor. + * + * JSON_ARRAY() is transformed into json[b]_build_array[_ext]() call + * depending on the output JSON format. The first argument of + * json[b]_build_array_ext() is absent_on_null. + * + * Then function call result is coerced to the target type. + */ +static Node * +transformJsonArrayConstructor(ParseState *pstate, JsonArrayConstructor *ctor) +{ + JsonReturning *returning; + List *args = NIL; + + /* transform element expressions, if any */ + if (ctor->exprs) + { + ListCell *lc; + + /* transform and append element arguments */ + foreach(lc, ctor->exprs) + { + JsonValueExpr *jsval = castNode(JsonValueExpr, lfirst(lc)); + Node *val = transformJsonValueExpr(pstate, jsval, + JS_FORMAT_DEFAULT); + + args = lappend(args, val); + } + } + + returning = transformJsonConstructorOutput(pstate, ctor->output, args); + + return makeJsonConstructorExpr(pstate, JSCTOR_JSON_ARRAY, args, NULL, + returning, false, ctor->absent_on_null, + ctor->location); +} diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 059eeb9e94..204d285773 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -1957,6 +1957,19 @@ FigureColnameInternal(Node *node, char **name) case T_XmlSerialize: *name = "xmlserialize"; return 2; + case T_JsonObjectConstructor: + *name = "json_object"; + return 2; + case T_JsonArrayConstructor: + case T_JsonArrayQueryConstructor: + *name = "json_array"; + return 2; + case T_JsonObjectAgg: + *name = "json_objectagg"; + return 2; + case T_JsonArrayAgg: + *name = "json_arrayagg"; + return 2; default: break; } diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c index 50227cc098..eee0a29c08 100644 --- a/src/backend/parser/parser.c +++ b/src/backend/parser/parser.c @@ -150,6 +150,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner) case USCONST: cur_token_length = strlen(yyextra->core_yy_extra.scanbuf + *llocp); break; + case WITHOUT: + cur_token_length = 7; + break; default: return cur_token; } @@ -221,6 +224,19 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner) case ORDINALITY: cur_token = WITH_LA; break; + case UNIQUE: + cur_token = WITH_LA_UNIQUE; + break; + } + break; + + case WITHOUT: + /* Replace WITHOUT by WITHOUT_LA if it's followed by TIME */ + switch (next_token) + { + case TIME: + cur_token = WITHOUT_LA; + break; } break; diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index 7879f342e6..d088fafc56 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -13,7 +13,9 @@ */ #include "postgres.h" +#include "catalog/pg_proc.h" #include "catalog/pg_type.h" +#include "common/hashfn.h" #include "funcapi.h" #include "libpq/pqformat.h" #include "miscadmin.h" @@ -42,6 +44,42 @@ typedef enum /* type categories for datum_to_json */ JSONTYPE_OTHER /* all else */ } JsonTypeCategory; +/* Common context for key uniqueness check */ +typedef struct HTAB *JsonUniqueCheckState; /* hash table for key names */ + +/* Hash entry for JsonUniqueCheckState */ +typedef struct JsonUniqueHashEntry +{ + const char *key; + int key_len; + int object_id; +} JsonUniqueHashEntry; + +/* Context for key uniqueness check in builder functions */ +typedef struct JsonUniqueBuilderState +{ + JsonUniqueCheckState check; /* unique check */ + StringInfoData skipped_keys; /* skipped keys with NULL values */ + MemoryContext mcxt; /* context for saving skipped keys */ +} JsonUniqueBuilderState; + +/* Element of object stack for key uniqueness check during json parsing */ +typedef struct JsonUniqueStackEntry +{ + struct JsonUniqueStackEntry *parent; + int object_id; +} JsonUniqueStackEntry; + +/* State for key uniqueness check during json parsing */ +typedef struct JsonUniqueParsingState +{ + JsonLexContext *lex; + JsonUniqueCheckState check; + JsonUniqueStackEntry *stack; + int id_counter; + bool unique; +} JsonUniqueParsingState; + typedef struct JsonAggState { StringInfo str; @@ -49,6 +87,7 @@ typedef struct JsonAggState Oid key_output_func; JsonTypeCategory val_category; Oid val_output_func; + JsonUniqueBuilderState unique_check; } JsonAggState; static void composite_to_json(Datum composite, StringInfo result, @@ -722,6 +761,38 @@ row_to_json_pretty(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len)); } +bool +to_json_is_immutable(Oid typoid) +{ + JsonTypeCategory tcategory; + Oid outfuncoid; + + json_categorize_type(typoid, &tcategory, &outfuncoid); + + switch (tcategory) + { + case JSONTYPE_BOOL: + case JSONTYPE_JSON: + return true; + + case JSONTYPE_DATE: + case JSONTYPE_TIMESTAMP: + case JSONTYPE_TIMESTAMPTZ: + return false; + + case JSONTYPE_ARRAY: + return false; /* TODO recurse into elements */ + + case JSONTYPE_COMPOSITE: + return false; /* TODO recurse into fields */ + + case JSONTYPE_NUMERIC: + case JSONTYPE_CAST: + default: + return func_volatile(outfuncoid) == PROVOLATILE_IMMUTABLE; + } +} + /* * SQL function to_json(anyvalue) */ @@ -754,8 +825,8 @@ to_json(PG_FUNCTION_ARGS) * * aggregate input column as a json array value. */ -Datum -json_agg_transfn(PG_FUNCTION_ARGS) +static Datum +json_agg_transfn_worker(FunctionCallInfo fcinfo, bool absent_on_null) { MemoryContext aggcontext, oldcontext; @@ -795,9 +866,14 @@ json_agg_transfn(PG_FUNCTION_ARGS) else { state = (JsonAggState *) PG_GETARG_POINTER(0); - appendStringInfoString(state->str, ", "); } + if (absent_on_null && PG_ARGISNULL(1)) + PG_RETURN_POINTER(state); + + if (state->str->len > 1) + appendStringInfoString(state->str, ", "); + /* fast path for NULLs */ if (PG_ARGISNULL(1)) { @@ -809,7 +885,7 @@ json_agg_transfn(PG_FUNCTION_ARGS) val = PG_GETARG_DATUM(1); /* add some whitespace if structured type and not first item */ - if (!PG_ARGISNULL(0) && + if (!PG_ARGISNULL(0) && state->str->len > 1 && (state->val_category == JSONTYPE_ARRAY || state->val_category == JSONTYPE_COMPOSITE)) { @@ -827,6 +903,25 @@ json_agg_transfn(PG_FUNCTION_ARGS) PG_RETURN_POINTER(state); } + +/* + * json_agg aggregate function + */ +Datum +json_agg_transfn(PG_FUNCTION_ARGS) +{ + return json_agg_transfn_worker(fcinfo, false); +} + +/* + * json_agg_strict aggregate function + */ +Datum +json_agg_strict_transfn(PG_FUNCTION_ARGS) +{ + return json_agg_transfn_worker(fcinfo, true); +} + /* * json_agg final function */ @@ -850,18 +945,122 @@ json_agg_finalfn(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(catenate_stringinfo_string(state->str, "]")); } +/* Functions implementing hash table for key uniqueness check */ +static uint32 +json_unique_hash(const void *key, Size keysize) +{ + const JsonUniqueHashEntry *entry = (JsonUniqueHashEntry *) key; + uint32 hash = hash_bytes_uint32(entry->object_id); + + hash ^= hash_bytes((const unsigned char *) entry->key, entry->key_len); + + return DatumGetUInt32(hash); +} + +static int +json_unique_hash_match(const void *key1, const void *key2, Size keysize) +{ + const JsonUniqueHashEntry *entry1 = (const JsonUniqueHashEntry *) key1; + const JsonUniqueHashEntry *entry2 = (const JsonUniqueHashEntry *) key2; + + if (entry1->object_id != entry2->object_id) + return entry1->object_id > entry2->object_id ? 1 : -1; + + if (entry1->key_len != entry2->key_len) + return entry1->key_len > entry2->key_len ? 1 : -1; + + return strncmp(entry1->key, entry2->key, entry1->key_len); +} + +/* Functions implementing object key uniqueness check */ +static void +json_unique_check_init(JsonUniqueCheckState *cxt) +{ + HASHCTL ctl; + + memset(&ctl, 0, sizeof(ctl)); + ctl.keysize = sizeof(JsonUniqueHashEntry); + ctl.entrysize = sizeof(JsonUniqueHashEntry); + ctl.hcxt = CurrentMemoryContext; + ctl.hash = json_unique_hash; + ctl.match = json_unique_hash_match; + + *cxt = hash_create("json object hashtable", + 32, + &ctl, + HASH_ELEM | HASH_CONTEXT | HASH_FUNCTION | HASH_COMPARE); +} + +static void +json_unique_check_free(JsonUniqueCheckState *cxt) +{ + hash_destroy(*cxt); +} + +static bool +json_unique_check_key(JsonUniqueCheckState *cxt, const char *key, int object_id) +{ + JsonUniqueHashEntry entry; + bool found; + + entry.key = key; + entry.key_len = strlen(key); + entry.object_id = object_id; + + (void) hash_search(*cxt, &entry, HASH_ENTER, &found); + + return !found; +} + +static void +json_unique_builder_init(JsonUniqueBuilderState *cxt) +{ + json_unique_check_init(&cxt->check); + cxt->mcxt = CurrentMemoryContext; + cxt->skipped_keys.data = NULL; +} + +static void +json_unique_builder_free(JsonUniqueBuilderState *cxt) +{ + json_unique_check_free(&cxt->check); + + if (cxt->skipped_keys.data) + pfree(cxt->skipped_keys.data); +} + +/* On-demand initialization of skipped_keys StringInfo structure */ +static StringInfo +json_unique_builder_get_skipped_keys(JsonUniqueBuilderState *cxt) +{ + StringInfo out = &cxt->skipped_keys; + + if (!out->data) + { + MemoryContext oldcxt = MemoryContextSwitchTo(cxt->mcxt); + initStringInfo(out); + MemoryContextSwitchTo(oldcxt); + } + + return out; +} + /* * json_object_agg transition function. * * aggregate two input columns as a single json object value. */ -Datum -json_object_agg_transfn(PG_FUNCTION_ARGS) +static Datum +json_object_agg_transfn_worker(FunctionCallInfo fcinfo, + bool absent_on_null, bool unique_keys) { MemoryContext aggcontext, oldcontext; JsonAggState *state; + StringInfo out; Datum arg; + bool skip; + int key_offset; if (!AggCheckCallContext(fcinfo, &aggcontext)) { @@ -882,6 +1081,10 @@ json_object_agg_transfn(PG_FUNCTION_ARGS) oldcontext = MemoryContextSwitchTo(aggcontext); state = (JsonAggState *) palloc(sizeof(JsonAggState)); state->str = makeStringInfo(); + if (unique_keys) + json_unique_builder_init(&state->unique_check); + else + memset(&state->unique_check, 0, sizeof(state->unique_check)); MemoryContextSwitchTo(oldcontext); arg_type = get_fn_expr_argtype(fcinfo->flinfo, 1); @@ -909,7 +1112,6 @@ json_object_agg_transfn(PG_FUNCTION_ARGS) else { state = (JsonAggState *) PG_GETARG_POINTER(0); - appendStringInfoString(state->str, ", "); } /* @@ -925,11 +1127,49 @@ json_object_agg_transfn(PG_FUNCTION_ARGS) (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("field name must not be null"))); + /* Skip null values if absent_on_null */ + skip = absent_on_null && PG_ARGISNULL(2); + + if (skip) + { + /* If key uniqueness check is needed we must save skipped keys */ + if (!unique_keys) + PG_RETURN_POINTER(state); + + out = json_unique_builder_get_skipped_keys(&state->unique_check); + } + else + { + out = state->str; + + /* + * Append comma delimiter only if we have already outputted some fields + * after the initial string "{ ". + */ + if (out->len > 2) + appendStringInfoString(out, ", "); + } + arg = PG_GETARG_DATUM(1); - datum_to_json(arg, false, state->str, state->key_category, + key_offset = out->len; + + datum_to_json(arg, false, out, state->key_category, state->key_output_func, true); + if (unique_keys) + { + const char *key = &out->data[key_offset]; + + if (!json_unique_check_key(&state->unique_check.check, key, 0)) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE), + errmsg("duplicate JSON key %s", key))); + + if (skip) + PG_RETURN_POINTER(state); + } + appendStringInfoString(state->str, " : "); if (PG_ARGISNULL(2)) @@ -943,6 +1183,42 @@ json_object_agg_transfn(PG_FUNCTION_ARGS) PG_RETURN_POINTER(state); } +/* + * json_object_agg aggregate function + */ +Datum +json_object_agg_transfn(PG_FUNCTION_ARGS) +{ + return json_object_agg_transfn_worker(fcinfo, false, false); +} + +/* + * json_object_agg_strict aggregate function + */ +Datum +json_object_agg_strict_transfn(PG_FUNCTION_ARGS) +{ + return json_object_agg_transfn_worker(fcinfo, true, false); +} + +/* + * json_object_agg_unique aggregate function + */ +Datum +json_object_agg_unique_transfn(PG_FUNCTION_ARGS) +{ + return json_object_agg_transfn_worker(fcinfo, false, true); +} + +/* + * json_object_agg_unique_strict aggregate function + */ +Datum +json_object_agg_unique_strict_transfn(PG_FUNCTION_ARGS) +{ + return json_object_agg_transfn_worker(fcinfo, true, true); +} + /* * json_object_agg final function. */ @@ -960,6 +1236,8 @@ json_object_agg_finalfn(PG_FUNCTION_ARGS) if (state == NULL) PG_RETURN_NULL(); + json_unique_builder_free(&state->unique_check); + /* Else return state with appropriate object terminator added */ PG_RETURN_TEXT_P(catenate_stringinfo_string(state->str, " }")); } @@ -984,25 +1262,14 @@ catenate_stringinfo_string(StringInfo buffer, const char *addon) return result; } -/* - * SQL function json_build_object(variadic "any") - */ Datum -json_build_object(PG_FUNCTION_ARGS) +json_build_object_worker(int nargs, Datum *args, bool *nulls, Oid *types, + bool absent_on_null, bool unique_keys) { - int nargs; int i; const char *sep = ""; StringInfo result; - Datum *args; - bool *nulls; - Oid *types; - - /* fetch argument values to build the object */ - nargs = extract_variadic_args(fcinfo, 0, false, &args, &types, &nulls); - - if (nargs < 0) - PG_RETURN_NULL(); + JsonUniqueBuilderState unique_check; if (nargs % 2 != 0) ereport(ERROR, @@ -1016,19 +1283,58 @@ json_build_object(PG_FUNCTION_ARGS) appendStringInfoChar(result, '{'); + if (unique_keys) + json_unique_builder_init(&unique_check); + for (i = 0; i < nargs; i += 2) { - appendStringInfoString(result, sep); - sep = ", "; + StringInfo out; + bool skip; + int key_offset; + + /* Skip null values if absent_on_null */ + skip = absent_on_null && nulls[i + 1]; + + if (skip) + { + /* If key uniqueness check is needed we must save skipped keys */ + if (!unique_keys) + continue; + + out = json_unique_builder_get_skipped_keys(&unique_check); + } + else + { + appendStringInfoString(result, sep); + sep = ", "; + out = result; + } /* process key */ if (nulls[i]) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("argument %d cannot be null", i + 1), + errmsg("argument %d cannot be null", i + 1), errhint("Object keys should be text."))); - add_json(args[i], false, result, types[i], true); + /* save key offset before key appending */ + key_offset = out->len; + + add_json(args[i], false, out, types[i], true); + + if (unique_keys) + { + /* check key uniqueness after key appending */ + const char *key = &out->data[key_offset]; + + if (!json_unique_check_key(&unique_check.check, key, 0)) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE), + errmsg("duplicate JSON key %s", key))); + + if (skip) + continue; + } appendStringInfoString(result, " : "); @@ -1038,7 +1344,29 @@ json_build_object(PG_FUNCTION_ARGS) appendStringInfoChar(result, '}'); - PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len)); + if (unique_keys) + json_unique_builder_free(&unique_check); + + return PointerGetDatum(cstring_to_text_with_len(result->data, result->len)); +} + +/* + * SQL function json_build_object(variadic "any") + */ +Datum +json_build_object(PG_FUNCTION_ARGS) +{ + Datum *args; + bool *nulls; + Oid *types; + /* build argument values to build the object */ + int nargs = extract_variadic_args(fcinfo, 0, true, + &args, &types, &nulls); + + if (nargs < 0) + PG_RETURN_NULL(); + + PG_RETURN_DATUM(json_build_object_worker(nargs, args, nulls, types, false, false)); } /* @@ -1050,25 +1378,13 @@ json_build_object_noargs(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2)); } -/* - * SQL function json_build_array(variadic "any") - */ Datum -json_build_array(PG_FUNCTION_ARGS) +json_build_array_worker(int nargs, Datum *args, bool *nulls, Oid *types, + bool absent_on_null) { - int nargs; int i; const char *sep = ""; StringInfo result; - Datum *args; - bool *nulls; - Oid *types; - - /* fetch argument values to build the array */ - nargs = extract_variadic_args(fcinfo, 0, false, &args, &types, &nulls); - - if (nargs < 0) - PG_RETURN_NULL(); result = makeStringInfo(); @@ -1076,6 +1392,9 @@ json_build_array(PG_FUNCTION_ARGS) for (i = 0; i < nargs; i++) { + if (absent_on_null && nulls[i]) + continue; + appendStringInfoString(result, sep); sep = ", "; add_json(args[i], nulls[i], result, types[i], false); @@ -1083,7 +1402,26 @@ json_build_array(PG_FUNCTION_ARGS) appendStringInfoChar(result, ']'); - PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len)); + return PointerGetDatum(cstring_to_text_with_len(result->data, result->len)); +} + +/* + * SQL function json_build_array(variadic "any") + */ +Datum +json_build_array(PG_FUNCTION_ARGS) +{ + Datum *args; + bool *nulls; + Oid *types; + /* build argument values to build the object */ + int nargs = extract_variadic_args(fcinfo, 0, true, + &args, &types, &nulls); + + if (nargs < 0) + PG_RETURN_NULL(); + + PG_RETURN_DATUM(json_build_array_worker(nargs, args, nulls, types, false)); } /* diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index f5f40a94bd..a103cbc7c6 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -14,6 +14,7 @@ #include "access/htup_details.h" #include "access/transam.h" +#include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "funcapi.h" #include "libpq/pqformat.h" @@ -1126,6 +1127,39 @@ add_jsonb(Datum val, bool is_null, JsonbInState *result, datum_to_jsonb(val, is_null, result, tcategory, outfuncoid, key_scalar); } +bool +to_jsonb_is_immutable(Oid typoid) +{ + JsonbTypeCategory tcategory; + Oid outfuncoid; + + jsonb_categorize_type(typoid, &tcategory, &outfuncoid); + + switch (tcategory) + { + case JSONBTYPE_BOOL: + case JSONBTYPE_JSON: + case JSONBTYPE_JSONB: + return true; + + case JSONBTYPE_DATE: + case JSONBTYPE_TIMESTAMP: + case JSONBTYPE_TIMESTAMPTZ: + return false; + + case JSONBTYPE_ARRAY: + return false; /* TODO recurse into elements */ + + case JSONBTYPE_COMPOSITE: + return false; /* TODO recurse into fields */ + + case JSONBTYPE_NUMERIC: + case JSONBTYPE_JSONCAST: + default: + return func_volatile(outfuncoid) == PROVOLATILE_IMMUTABLE; + } +} + /* * SQL function to_jsonb(anyvalue) */ @@ -1153,24 +1187,12 @@ to_jsonb(PG_FUNCTION_ARGS) PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); } -/* - * SQL function jsonb_build_object(variadic "any") - */ Datum -jsonb_build_object(PG_FUNCTION_ARGS) +jsonb_build_object_worker(int nargs, Datum *args, bool *nulls, Oid *types, + bool absent_on_null, bool unique_keys) { - int nargs; int i; JsonbInState result; - Datum *args; - bool *nulls; - Oid *types; - - /* build argument values to build the object */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); - - if (nargs < 0) - PG_RETURN_NULL(); if (nargs % 2 != 0) ereport(ERROR, @@ -1183,15 +1205,26 @@ jsonb_build_object(PG_FUNCTION_ARGS) memset(&result, 0, sizeof(JsonbInState)); result.res = pushJsonbValue(&result.parseState, WJB_BEGIN_OBJECT, NULL); + result.parseState->unique_keys = unique_keys; + result.parseState->skip_nulls = absent_on_null; for (i = 0; i < nargs; i += 2) { /* process key */ + bool skip; + if (nulls[i]) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("argument %d: key must not be null", i + 1))); + /* skip null values if absent_on_null */ + skip = absent_on_null && nulls[i + 1]; + + /* we need to save skipped keys for the key uniqueness check */ + if (skip && !unique_keys) + continue; + add_jsonb(args[i], false, &result, types[i], true); /* process value */ @@ -1200,7 +1233,26 @@ jsonb_build_object(PG_FUNCTION_ARGS) result.res = pushJsonbValue(&result.parseState, WJB_END_OBJECT, NULL); - PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); + return JsonbPGetDatum(JsonbValueToJsonb(result.res)); +} + +/* + * SQL function jsonb_build_object(variadic "any") + */ +Datum +jsonb_build_object(PG_FUNCTION_ARGS) +{ + Datum *args; + bool *nulls; + Oid *types; + /* build argument values to build the object */ + int nargs = extract_variadic_args(fcinfo, 0, true, + &args, &types, &nulls); + + if (nargs < 0) + PG_RETURN_NULL(); + + PG_RETURN_DATUM(jsonb_build_object_worker(nargs, args, nulls, types, false, false)); } /* @@ -1219,37 +1271,50 @@ jsonb_build_object_noargs(PG_FUNCTION_ARGS) PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); } -/* - * SQL function jsonb_build_array(variadic "any") - */ Datum -jsonb_build_array(PG_FUNCTION_ARGS) +jsonb_build_array_worker(int nargs, Datum *args, bool *nulls, Oid *types, + bool absent_on_null) { - int nargs; int i; JsonbInState result; - Datum *args; - bool *nulls; - Oid *types; - - /* build argument values to build the array */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); - - if (nargs < 0) - PG_RETURN_NULL(); memset(&result, 0, sizeof(JsonbInState)); result.res = pushJsonbValue(&result.parseState, WJB_BEGIN_ARRAY, NULL); for (i = 0; i < nargs; i++) + { + if (absent_on_null && nulls[i]) + continue; + add_jsonb(args[i], nulls[i], &result, types[i], false); + } result.res = pushJsonbValue(&result.parseState, WJB_END_ARRAY, NULL); - PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); + return JsonbPGetDatum(JsonbValueToJsonb(result.res)); } +/* + * SQL function jsonb_build_array(variadic "any") + */ +Datum +jsonb_build_array(PG_FUNCTION_ARGS) +{ + Datum *args; + bool *nulls; + Oid *types; + /* build argument values to build the object */ + int nargs = extract_variadic_args(fcinfo, 0, true, + &args, &types, &nulls); + + if (nargs < 0) + PG_RETURN_NULL(); + + PG_RETURN_DATUM(jsonb_build_array_worker(nargs, args, nulls, types, false)); +} + + /* * degenerate case of jsonb_build_array where it gets 0 arguments. */ @@ -1490,6 +1555,8 @@ clone_parse_state(JsonbParseState *state) { ocursor->contVal = icursor->contVal; ocursor->size = icursor->size; + ocursor->unique_keys = icursor->unique_keys; + ocursor->skip_nulls = icursor->skip_nulls; icursor = icursor->next; if (icursor == NULL) break; @@ -1501,12 +1568,8 @@ clone_parse_state(JsonbParseState *state) return result; } - -/* - * jsonb_agg aggregate function - */ -Datum -jsonb_agg_transfn(PG_FUNCTION_ARGS) +static Datum +jsonb_agg_transfn_worker(FunctionCallInfo fcinfo, bool absent_on_null) { MemoryContext oldcontext, aggcontext; @@ -1554,6 +1617,9 @@ jsonb_agg_transfn(PG_FUNCTION_ARGS) result = state->res; } + if (absent_on_null && PG_ARGISNULL(1)) + PG_RETURN_POINTER(state); + /* turn the argument into jsonb in the normal function context */ val = PG_ARGISNULL(1) ? (Datum) 0 : PG_GETARG_DATUM(1); @@ -1623,6 +1689,24 @@ jsonb_agg_transfn(PG_FUNCTION_ARGS) PG_RETURN_POINTER(state); } +/* + * jsonb_agg aggregate function + */ +Datum +jsonb_agg_transfn(PG_FUNCTION_ARGS) +{ + return jsonb_agg_transfn_worker(fcinfo, false); +} + +/* + * jsonb_agg_strict aggregate function + */ +Datum +jsonb_agg_strict_transfn(PG_FUNCTION_ARGS) +{ + return jsonb_agg_transfn_worker(fcinfo, true); +} + Datum jsonb_agg_finalfn(PG_FUNCTION_ARGS) { @@ -1655,11 +1739,9 @@ jsonb_agg_finalfn(PG_FUNCTION_ARGS) PG_RETURN_POINTER(out); } -/* - * jsonb_object_agg aggregate function - */ -Datum -jsonb_object_agg_transfn(PG_FUNCTION_ARGS) +static Datum +jsonb_object_agg_transfn_worker(FunctionCallInfo fcinfo, + bool absent_on_null, bool unique_keys) { MemoryContext oldcontext, aggcontext; @@ -1673,6 +1755,7 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS) *jbval; JsonbValue v; JsonbIteratorToken type; + bool skip; if (!AggCheckCallContext(fcinfo, &aggcontext)) { @@ -1692,6 +1775,9 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS) state->res = result; result->res = pushJsonbValue(&result->parseState, WJB_BEGIN_OBJECT, NULL); + result->parseState->unique_keys = unique_keys; + result->parseState->skip_nulls = absent_on_null; + MemoryContextSwitchTo(oldcontext); arg_type = get_fn_expr_argtype(fcinfo->flinfo, 1); @@ -1727,6 +1813,15 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS) (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("field name must not be null"))); + /* + * Skip null values if absent_on_null unless key uniqueness check is + * needed (because we must save keys in this case). + */ + skip = absent_on_null && PG_ARGISNULL(2); + + if (skip && !unique_keys) + PG_RETURN_POINTER(state); + val = PG_GETARG_DATUM(1); memset(&elem, 0, sizeof(JsonbInState)); @@ -1782,6 +1877,16 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS) } result->res = pushJsonbValue(&result->parseState, WJB_KEY, &v); + + if (skip) + { + v.type = jbvNull; + result->res = pushJsonbValue(&result->parseState, + WJB_VALUE, &v); + MemoryContextSwitchTo(oldcontext); + PG_RETURN_POINTER(state); + } + break; case WJB_END_ARRAY: break; @@ -1854,6 +1959,43 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS) PG_RETURN_POINTER(state); } +/* + * jsonb_object_agg aggregate function + */ +Datum +jsonb_object_agg_transfn(PG_FUNCTION_ARGS) +{ + return jsonb_object_agg_transfn_worker(fcinfo, false, false); +} + + +/* + * jsonb_object_agg_strict aggregate function + */ +Datum +jsonb_object_agg_strict_transfn(PG_FUNCTION_ARGS) +{ + return jsonb_object_agg_transfn_worker(fcinfo, true, false); +} + +/* + * jsonb_object_agg_unique aggregate function + */ +Datum +jsonb_object_agg_unique_transfn(PG_FUNCTION_ARGS) +{ + return jsonb_object_agg_transfn_worker(fcinfo, false, true); +} + +/* + * jsonb_object_agg_unique_strict aggregate function + */ +Datum +jsonb_object_agg_unique_strict_transfn(PG_FUNCTION_ARGS) +{ + return jsonb_object_agg_transfn_worker(fcinfo, true, true); +} + Datum jsonb_object_agg_finalfn(PG_FUNCTION_ARGS) { diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c index 60442758b3..aa151a53d6 100644 --- a/src/backend/utils/adt/jsonb_util.c +++ b/src/backend/utils/adt/jsonb_util.c @@ -64,7 +64,8 @@ static int lengthCompareJsonbStringValue(const void *a, const void *b); static int lengthCompareJsonbString(const char *val1, int len1, const char *val2, int len2); static int lengthCompareJsonbPair(const void *a, const void *b, void *arg); -static void uniqueifyJsonbObject(JsonbValue *object); +static void uniqueifyJsonbObject(JsonbValue *object, bool unique_keys, + bool skip_nulls); static JsonbValue *pushJsonbValueScalar(JsonbParseState **pstate, JsonbIteratorToken seq, JsonbValue *scalarVal); @@ -689,7 +690,9 @@ pushJsonbValueScalar(JsonbParseState **pstate, JsonbIteratorToken seq, appendElement(*pstate, scalarVal); break; case WJB_END_OBJECT: - uniqueifyJsonbObject(&(*pstate)->contVal); + uniqueifyJsonbObject(&(*pstate)->contVal, + (*pstate)->unique_keys, + (*pstate)->skip_nulls); /* fall through! */ case WJB_END_ARRAY: /* Steps here common to WJB_END_OBJECT case */ @@ -732,6 +735,9 @@ pushState(JsonbParseState **pstate) JsonbParseState *ns = palloc(sizeof(JsonbParseState)); ns->next = *pstate; + ns->unique_keys = false; + ns->skip_nulls = false; + return ns; } @@ -1936,7 +1942,7 @@ lengthCompareJsonbPair(const void *a, const void *b, void *binequal) * Sort and unique-ify pairs in JsonbValue object */ static void -uniqueifyJsonbObject(JsonbValue *object) +uniqueifyJsonbObject(JsonbValue *object, bool unique_keys, bool skip_nulls) { bool hasNonUniq = false; @@ -1946,15 +1952,21 @@ uniqueifyJsonbObject(JsonbValue *object) qsort_arg(object->val.object.pairs, object->val.object.nPairs, sizeof(JsonbPair), lengthCompareJsonbPair, &hasNonUniq); - if (hasNonUniq) + if (hasNonUniq && unique_keys) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE), + errmsg("duplicate JSON object key value"))); + + if (hasNonUniq || skip_nulls) { JsonbPair *ptr = object->val.object.pairs + 1, *res = object->val.object.pairs; while (ptr - object->val.object.pairs < object->val.object.nPairs) { - /* Avoid copying over duplicate */ - if (lengthCompareJsonbStringValue(ptr, res) != 0) + /* Avoid copying over duplicate or null */ + if (lengthCompareJsonbStringValue(ptr, res) != 0 && + (!skip_nulls || ptr->value.type != jbvNull)) { res++; if (ptr != res) diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index c7860a7580..6db6c008dd 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -457,6 +457,12 @@ static void get_coercion_expr(Node *arg, deparse_context *context, Node *parentNode); static void get_const_expr(Const *constval, deparse_context *context, int showtype); +static void get_json_constructor(JsonConstructorExpr *ctor, + deparse_context *context, bool showimplicit); +static void get_json_agg_constructor(JsonConstructorExpr *ctor, + deparse_context *context, + const char *funcname, + bool is_json_objectagg); static void get_const_collation(Const *constval, deparse_context *context); static void simple_quote_literal(StringInfo buf, const char *val); static void get_sublink_expr(SubLink *sublink, deparse_context *context); @@ -6245,7 +6251,8 @@ get_rule_sortgroupclause(Index ref, List *tlist, bool force_colno, bool need_paren = (PRETTY_PAREN(context) || IsA(expr, FuncExpr) || IsA(expr, Aggref) - || IsA(expr, WindowFunc)); + || IsA(expr, WindowFunc) + || IsA(expr, JsonConstructorExpr)); if (need_paren) appendStringInfoChar(context->buf, '('); @@ -8093,6 +8100,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags) case T_GroupingFunc: case T_WindowFunc: case T_FuncExpr: + case T_JsonConstructorExpr: /* function-like: name(..) or name[..] */ return true; @@ -8380,12 +8388,12 @@ get_rule_expr_paren(Node *node, deparse_context *context, * get_json_format - Parse back a JsonFormat node */ static void -get_json_format(JsonFormat *format, deparse_context *context) +get_json_format(JsonFormat *format, StringInfo buf) { if (format->format_type == JS_FORMAT_DEFAULT) return; - appendStringInfoString(context->buf, + appendStringInfoString(buf, format->format_type == JS_FORMAT_JSONB ? " FORMAT JSONB" : " FORMAT JSON"); @@ -8395,7 +8403,7 @@ get_json_format(JsonFormat *format, deparse_context *context) format->encoding == JS_ENC_UTF16 ? "UTF16" : format->encoding == JS_ENC_UTF32 ? "UTF32" : "UTF8"; - appendStringInfo(context->buf, " ENCODING %s", encoding); + appendStringInfo(buf, " ENCODING %s", encoding); } } @@ -8403,20 +8411,20 @@ get_json_format(JsonFormat *format, deparse_context *context) * get_json_returning - Parse back a JsonReturning structure */ static void -get_json_returning(JsonReturning *returning, deparse_context *context, +get_json_returning(JsonReturning *returning, StringInfo buf, bool json_format_by_default) { if (!OidIsValid(returning->typid)) return; - appendStringInfo(context->buf, " RETURNING %s", + appendStringInfo(buf, " RETURNING %s", format_type_with_typemod(returning->typid, returning->typmod)); if (!json_format_by_default || returning->format->format_type != (returning->typid == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON)) - get_json_format(returning->format, context); + get_json_format(returning->format, buf); } /* ---------- @@ -9583,10 +9591,14 @@ get_rule_expr(Node *node, deparse_context *context, JsonValueExpr *jve = (JsonValueExpr *) node; get_rule_expr((Node *) jve->raw_expr, context, false); - get_json_format(jve->format, context); + get_json_format(jve->format, context->buf); } break; + case T_JsonConstructorExpr: + get_json_constructor((JsonConstructorExpr *) node, context, false); + break; + case T_List: { char *sep; @@ -9855,17 +9867,89 @@ get_func_expr(FuncExpr *expr, deparse_context *context, appendStringInfoChar(buf, ')'); } +static void +get_json_constructor_options(JsonConstructorExpr *ctor, StringInfo buf) +{ + if (ctor->absent_on_null) + { + if (ctor->type == JSCTOR_JSON_OBJECT || + ctor->type == JSCTOR_JSON_OBJECTAGG) + appendStringInfoString(buf, " ABSENT ON NULL"); + } + else + { + if (ctor->type == JSCTOR_JSON_ARRAY || + ctor->type == JSCTOR_JSON_ARRAYAGG) + appendStringInfoString(buf, " NULL ON NULL"); + } + + if (ctor->unique) + appendStringInfoString(buf, " WITH UNIQUE KEYS"); + + get_json_returning(ctor->returning, buf, true); +} + +static void +get_json_constructor(JsonConstructorExpr *ctor, deparse_context *context, + bool showimplicit) +{ + StringInfo buf = context->buf; + const char *funcname; + int nargs; + ListCell *lc; + + switch (ctor->type) + { + case JSCTOR_JSON_OBJECT: + funcname = "JSON_OBJECT"; + break; + case JSCTOR_JSON_ARRAY: + funcname = "JSON_ARRAY"; + break; + case JSCTOR_JSON_OBJECTAGG: + return get_json_agg_constructor(ctor, context, "JSON_OBJECTAGG", true); + case JSCTOR_JSON_ARRAYAGG: + return get_json_agg_constructor(ctor, context, "JSON_ARRAYAGG", false); + default: + elog(ERROR, "invalid JsonConstructorExprType %d", ctor->type); + } + + appendStringInfo(buf, "%s(", funcname); + + nargs = 0; + foreach(lc, ctor->args) + { + if (nargs > 0) + { + const char *sep = ctor->type == JSCTOR_JSON_OBJECT && + (nargs % 2) != 0 ? " : " : ", "; + + appendStringInfoString(buf, sep); + } + + get_rule_expr((Node *) lfirst(lc), context, true); + + nargs++; + } + + get_json_constructor_options(ctor, buf); + + appendStringInfo(buf, ")"); +} + + /* - * get_agg_expr - Parse back an Aggref node + * get_agg_expr_helper - Parse back an Aggref node */ static void -get_agg_expr(Aggref *aggref, deparse_context *context, - Aggref *original_aggref) +get_agg_expr_helper(Aggref *aggref, deparse_context *context, + Aggref *original_aggref, const char *funcname, + const char *options, bool is_json_objectagg) { StringInfo buf = context->buf; Oid argtypes[FUNC_MAX_ARGS]; int nargs; - bool use_variadic; + bool use_variadic = false; /* * For a combining aggregate, we look up and deparse the corresponding @@ -9895,13 +9979,14 @@ get_agg_expr(Aggref *aggref, deparse_context *context, /* Extract the argument types as seen by the parser */ nargs = get_aggregate_argtypes(aggref, argtypes); + if (!funcname) + funcname = generate_function_name(aggref->aggfnoid, nargs, NIL, + argtypes, aggref->aggvariadic, + &use_variadic, + context->special_exprkind); + /* Print the aggregate name, schema-qualified if needed */ - appendStringInfo(buf, "%s(%s", - generate_function_name(aggref->aggfnoid, nargs, - NIL, argtypes, - aggref->aggvariadic, - &use_variadic, - context->special_exprkind), + appendStringInfo(buf, "%s(%s", funcname, (aggref->aggdistinct != NIL) ? "DISTINCT " : ""); if (AGGKIND_IS_ORDERED_SET(aggref->aggkind)) @@ -9937,7 +10022,17 @@ get_agg_expr(Aggref *aggref, deparse_context *context, if (tle->resjunk) continue; if (i++ > 0) - appendStringInfoString(buf, ", "); + { + if (is_json_objectagg) + { + if (i > 2) + break; /* skip ABSENT ON NULL and WITH UNIQUE args */ + + appendStringInfoString(buf, " : "); + } + else + appendStringInfoString(buf, ", "); + } if (use_variadic && i == nargs) appendStringInfoString(buf, "VARIADIC "); get_rule_expr(arg, context, true); @@ -9951,6 +10046,9 @@ get_agg_expr(Aggref *aggref, deparse_context *context, } } + if (options) + appendStringInfoString(buf, options); + if (aggref->aggfilter != NULL) { appendStringInfoString(buf, ") FILTER (WHERE "); @@ -9960,6 +10058,16 @@ get_agg_expr(Aggref *aggref, deparse_context *context, appendStringInfoChar(buf, ')'); } +/* + * get_agg_expr - Parse back an Aggref node + */ +static void +get_agg_expr(Aggref *aggref, deparse_context *context, Aggref *original_aggref) +{ + return get_agg_expr_helper(aggref, context, original_aggref, NULL, NULL, + false); +} + /* * This is a helper function for get_agg_expr(). It's used when we deparse * a combining Aggref; resolve_special_varno locates the corresponding partial @@ -9979,10 +10087,12 @@ get_agg_combine_expr(Node *node, deparse_context *context, void *callback_arg) } /* - * get_windowfunc_expr - Parse back a WindowFunc node + * get_windowfunc_expr_helper - Parse back a WindowFunc node */ static void -get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context) +get_windowfunc_expr_helper(WindowFunc *wfunc, deparse_context *context, + const char *funcname, const char *options, + bool is_json_objectagg) { StringInfo buf = context->buf; Oid argtypes[FUNC_MAX_ARGS]; @@ -10006,16 +10116,30 @@ get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context) nargs++; } - appendStringInfo(buf, "%s(", - generate_function_name(wfunc->winfnoid, nargs, - argnames, argtypes, - false, NULL, - context->special_exprkind)); + if (!funcname) + funcname = generate_function_name(wfunc->winfnoid, nargs, argnames, + argtypes, false, NULL, + context->special_exprkind); + + appendStringInfo(buf, "%s(", funcname); + /* winstar can be set only in zero-argument aggregates */ if (wfunc->winstar) appendStringInfoChar(buf, '*'); else - get_rule_expr((Node *) wfunc->args, context, true); + { + if (is_json_objectagg) + { + get_rule_expr((Node *) linitial(wfunc->args), context, false); + appendStringInfoString(buf, " : "); + get_rule_expr((Node *) lsecond(wfunc->args), context, false); + } + else + get_rule_expr((Node *) wfunc->args, context, true); + } + + if (options) + appendStringInfoString(buf, options); if (wfunc->aggfilter != NULL) { @@ -10052,6 +10176,15 @@ get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context) } } +/* + * get_windowfunc_expr - Parse back a WindowFunc node + */ +static void +get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context) +{ + return get_windowfunc_expr_helper(wfunc, context, NULL, NULL, false); +} + /* * get_func_sql_syntax - Parse back a SQL-syntax function call * @@ -10292,6 +10425,31 @@ get_func_sql_syntax(FuncExpr *expr, deparse_context *context) return false; } +/* + * get_json_agg_constructor - Parse back an aggregate JsonConstructorExpr node + */ +static void +get_json_agg_constructor(JsonConstructorExpr *ctor, deparse_context *context, + const char *funcname, bool is_json_objectagg) +{ + StringInfoData options; + + initStringInfo(&options); + get_json_constructor_options(ctor, &options); + + if (IsA(ctor->func, Aggref)) + return get_agg_expr_helper((Aggref *) ctor->func, context, + (Aggref *) ctor->func, + funcname, options.data, is_json_objectagg); + else if (IsA(ctor->func, WindowFunc)) + return get_windowfunc_expr_helper((WindowFunc *) ctor->func, context, + funcname, options.data, + is_json_objectagg); + else + elog(ERROR, "invalid JsonConstructorExpr underlying node type: %d", + nodeTag(ctor->func)); +} + /* ---------- * get_coercion_expr * diff --git a/src/backend/utils/misc/queryjumble.c b/src/backend/utils/misc/queryjumble.c index 84435420e4..d14b751058 100644 --- a/src/backend/utils/misc/queryjumble.c +++ b/src/backend/utils/misc/queryjumble.c @@ -763,6 +763,18 @@ JumbleExpr(JumbleState *jstate, Node *node) JumbleExpr(jstate, (Node *) expr->format); } break; + case T_JsonConstructorExpr: + { + JsonConstructorExpr *ctor = (JsonConstructorExpr *) node; + + JumbleExpr(jstate, (Node *) ctor->func); + JumbleExpr(jstate, (Node *) ctor->coercion); + JumbleExpr(jstate, (Node *) ctor->returning); + APP_JUMB(ctor->type); + APP_JUMB(ctor->unique); + APP_JUMB(ctor->absent_on_null); + } + break; case T_List: foreach(temp, (List *) node) { diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat index 2843f4b415..1934f19335 100644 --- a/src/include/catalog/pg_aggregate.dat +++ b/src/include/catalog/pg_aggregate.dat @@ -567,14 +567,36 @@ # json { aggfnoid => 'json_agg', aggtransfn => 'json_agg_transfn', aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' }, +{ aggfnoid => 'json_agg_strict', aggtransfn => 'json_agg_strict_transfn', + aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' }, { aggfnoid => 'json_object_agg', aggtransfn => 'json_object_agg_transfn', aggfinalfn => 'json_object_agg_finalfn', aggtranstype => 'internal' }, +{ aggfnoid => 'json_object_agg_unique', + aggtransfn => 'json_object_agg_unique_transfn', + aggfinalfn => 'json_object_agg_finalfn', aggtranstype => 'internal' }, +{ aggfnoid => 'json_object_agg_strict', + aggtransfn => 'json_object_agg_strict_transfn', + aggfinalfn => 'json_object_agg_finalfn', aggtranstype => 'internal' }, +{ aggfnoid => 'json_object_agg_unique_strict', + aggtransfn => 'json_object_agg_unique_strict_transfn', + aggfinalfn => 'json_object_agg_finalfn', aggtranstype => 'internal' }, # jsonb { aggfnoid => 'jsonb_agg', aggtransfn => 'jsonb_agg_transfn', aggfinalfn => 'jsonb_agg_finalfn', aggtranstype => 'internal' }, +{ aggfnoid => 'jsonb_agg_strict', aggtransfn => 'jsonb_agg_strict_transfn', + aggfinalfn => 'jsonb_agg_finalfn', aggtranstype => 'internal' }, { aggfnoid => 'jsonb_object_agg', aggtransfn => 'jsonb_object_agg_transfn', aggfinalfn => 'jsonb_object_agg_finalfn', aggtranstype => 'internal' }, +{ aggfnoid => 'jsonb_object_agg_unique', + aggtransfn => 'jsonb_object_agg_unique_transfn', + aggfinalfn => 'jsonb_object_agg_finalfn', aggtranstype => 'internal' }, +{ aggfnoid => 'jsonb_object_agg_strict', + aggtransfn => 'jsonb_object_agg_strict_transfn', + aggfinalfn => 'jsonb_object_agg_finalfn', aggtranstype => 'internal' }, +{ aggfnoid => 'jsonb_object_agg_unique_strict', + aggtransfn => 'jsonb_object_agg_unique_strict_transfn', + aggfinalfn => 'jsonb_object_agg_finalfn', aggtranstype => 'internal' }, # ordered-set and hypothetical-set aggregates { aggfnoid => 'percentile_disc(float8,anyelement)', aggkind => 'o', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 699bd0aa3e..5e612a6b67 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8737,6 +8737,10 @@ proname => 'json_agg_transfn', proisstrict => 'f', provolatile => 's', prorettype => 'internal', proargtypes => 'internal anyelement', prosrc => 'json_agg_transfn' }, +{ oid => '8173', descr => 'json aggregate transition function', + proname => 'json_agg_strict_transfn', proisstrict => 'f', provolatile => 's', + prorettype => 'internal', proargtypes => 'internal anyelement', + prosrc => 'json_agg_strict_transfn' }, { oid => '3174', descr => 'json aggregate final function', proname => 'json_agg_finalfn', proisstrict => 'f', prorettype => 'json', proargtypes => 'internal', prosrc => 'json_agg_finalfn' }, @@ -8744,10 +8748,26 @@ proname => 'json_agg', prokind => 'a', proisstrict => 'f', provolatile => 's', prorettype => 'json', proargtypes => 'anyelement', prosrc => 'aggregate_dummy' }, +{ oid => '8174', descr => 'aggregate input into json', + proname => 'json_agg_strict', prokind => 'a', proisstrict => 'f', + provolatile => 's', prorettype => 'json', proargtypes => 'anyelement', + prosrc => 'aggregate_dummy' }, { oid => '3180', descr => 'json object aggregate transition function', proname => 'json_object_agg_transfn', proisstrict => 'f', provolatile => 's', prorettype => 'internal', proargtypes => 'internal any any', prosrc => 'json_object_agg_transfn' }, +{ oid => '8175', descr => 'json object aggregate transition function', + proname => 'json_object_agg_strict_transfn', proisstrict => 'f', + provolatile => 's', prorettype => 'internal', proargtypes => 'internal any any', + prosrc => 'json_object_agg_strict_transfn' }, +{ oid => '8176', descr => 'json object aggregate transition function', + proname => 'json_object_agg_unique_transfn', proisstrict => 'f', + provolatile => 's', prorettype => 'internal', proargtypes => 'internal any any', + prosrc => 'json_object_agg_unique_transfn' }, +{ oid => '8177', descr => 'json object aggregate transition function', + proname => 'json_object_agg_unique_strict_transfn', proisstrict => 'f', + provolatile => 's', prorettype => 'internal', proargtypes => 'internal any any', + prosrc => 'json_object_agg_unique_strict_transfn' }, { oid => '3196', descr => 'json object aggregate final function', proname => 'json_object_agg_finalfn', proisstrict => 'f', prorettype => 'json', proargtypes => 'internal', @@ -8756,6 +8776,19 @@ proname => 'json_object_agg', prokind => 'a', proisstrict => 'f', provolatile => 's', prorettype => 'json', proargtypes => 'any any', prosrc => 'aggregate_dummy' }, +{ oid => '8178', descr => 'aggregate non-NULL input into a json object', + proname => 'json_object_agg_strict', prokind => 'a', proisstrict => 'f', + provolatile => 's', prorettype => 'json', proargtypes => 'any any', + prosrc => 'aggregate_dummy' }, +{ oid => '8179', descr => 'aggregate input into a json object with unique keys', + proname => 'json_object_agg_unique', prokind => 'a', proisstrict => 'f', + provolatile => 's', prorettype => 'json', proargtypes => 'any any', + prosrc => 'aggregate_dummy' }, +{ oid => '8180', + descr => 'aggregate non-NULL input into a json object with unique keys', + proname => 'json_object_agg_unique_strict', prokind => 'a', + proisstrict => 'f', provolatile => 's', prorettype => 'json', + proargtypes => 'any any', prosrc => 'aggregate_dummy' }, { oid => '3198', descr => 'build a json array from any inputs', proname => 'json_build_array', provariadic => 'any', proisstrict => 'f', provolatile => 's', prorettype => 'json', proargtypes => 'any', @@ -9628,6 +9661,10 @@ proname => 'jsonb_agg_transfn', proisstrict => 'f', provolatile => 's', prorettype => 'internal', proargtypes => 'internal anyelement', prosrc => 'jsonb_agg_transfn' }, +{ oid => '8181', descr => 'jsonb aggregate transition function', + proname => 'jsonb_agg_strict_transfn', proisstrict => 'f', provolatile => 's', + prorettype => 'internal', proargtypes => 'internal anyelement', + prosrc => 'jsonb_agg_strict_transfn' }, { oid => '3266', descr => 'jsonb aggregate final function', proname => 'jsonb_agg_finalfn', proisstrict => 'f', provolatile => 's', prorettype => 'jsonb', proargtypes => 'internal', @@ -9636,10 +9673,29 @@ proname => 'jsonb_agg', prokind => 'a', proisstrict => 'f', provolatile => 's', prorettype => 'jsonb', proargtypes => 'anyelement', prosrc => 'aggregate_dummy' }, +{ oid => '8182', descr => 'aggregate input into jsonb skipping nulls', + proname => 'jsonb_agg_strict', prokind => 'a', proisstrict => 'f', + provolatile => 's', prorettype => 'jsonb', proargtypes => 'anyelement', + prosrc => 'aggregate_dummy' }, { oid => '3268', descr => 'jsonb object aggregate transition function', proname => 'jsonb_object_agg_transfn', proisstrict => 'f', provolatile => 's', prorettype => 'internal', proargtypes => 'internal any any', prosrc => 'jsonb_object_agg_transfn' }, +{ oid => '8183', descr => 'jsonb object aggregate transition function', + proname => 'jsonb_object_agg_strict_transfn', proisstrict => 'f', + provolatile => 's', prorettype => 'internal', + proargtypes => 'internal any any', + prosrc => 'jsonb_object_agg_strict_transfn' }, +{ oid => '8184', descr => 'jsonb object aggregate transition function', + proname => 'jsonb_object_agg_unique_transfn', proisstrict => 'f', + provolatile => 's', prorettype => 'internal', + proargtypes => 'internal any any', + prosrc => 'jsonb_object_agg_unique_transfn' }, +{ oid => '8185', descr => 'jsonb object aggregate transition function', + proname => 'jsonb_object_agg_unique_strict_transfn', proisstrict => 'f', + provolatile => 's', prorettype => 'internal', + proargtypes => 'internal any any', + prosrc => 'jsonb_object_agg_unique_strict_transfn' }, { oid => '3269', descr => 'jsonb object aggregate final function', proname => 'jsonb_object_agg_finalfn', proisstrict => 'f', provolatile => 's', prorettype => 'jsonb', proargtypes => 'internal', @@ -9648,6 +9704,20 @@ proname => 'jsonb_object_agg', prokind => 'a', proisstrict => 'f', prorettype => 'jsonb', proargtypes => 'any any', prosrc => 'aggregate_dummy' }, +{ oid => '8186', descr => 'aggregate non-NULL inputs into jsonb object', + proname => 'jsonb_object_agg_strict', prokind => 'a', proisstrict => 'f', + prorettype => 'jsonb', proargtypes => 'any any', + prosrc => 'aggregate_dummy' }, +{ oid => '8187', + descr => 'aggregate inputs into jsonb object checking key uniqueness', + proname => 'jsonb_object_agg_unique', prokind => 'a', proisstrict => 'f', + prorettype => 'jsonb', proargtypes => 'any any', + prosrc => 'aggregate_dummy' }, +{ oid => '8188', + descr => 'aggregate non-NULL inputs into jsonb object checking key uniqueness', + proname => 'jsonb_object_agg_unique_strict', prokind => 'a', + proisstrict => 'f', prorettype => 'jsonb', proargtypes => 'any any', + prosrc => 'aggregate_dummy' }, { oid => '3271', descr => 'build a jsonb array from any inputs', proname => 'jsonb_build_array', provariadic => 'any', proisstrict => 'f', provolatile => 's', prorettype => 'jsonb', proargtypes => 'any', diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index 56a89ebafb..c830fcf726 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -239,6 +239,7 @@ typedef enum ExprEvalOp EEOP_GROUPING_FUNC, EEOP_WINDOW_FUNC, EEOP_SUBPLAN, + EEOP_JSON_CONSTRUCTOR, /* aggregation related nodes */ EEOP_AGG_STRICT_DESERIALIZE, @@ -668,6 +669,17 @@ typedef struct ExprEvalStep int transno; int setoff; } agg_trans; + + /* for EEOP_JSON_CONSTRUCTOR */ + struct + { + JsonConstructorExpr *constructor; + Datum *arg_values; + bool *arg_nulls; + Oid *arg_types; + int nargs; + } json_constructor; + } d; } ExprEvalStep; @@ -769,6 +781,8 @@ extern void ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext); extern void ExecEvalSysVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext, TupleTableSlot *slot); +extern void ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op, + ExprContext *econtext); extern void ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup, ExprContext *aggcontext); diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h index ec8b71a685..e50b933288 100644 --- a/src/include/nodes/makefuncs.h +++ b/src/include/nodes/makefuncs.h @@ -109,6 +109,7 @@ extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_ extern JsonFormat *makeJsonFormat(JsonFormatType type, JsonEncoding encoding, int location); extern JsonValueExpr *makeJsonValueExpr(Expr *expr, JsonFormat *format); +extern Node *makeJsonKeyValue(Node *key, Node *value); extern JsonEncoding makeJsonEncoding(char *name); #endif /* MAKEFUNC_H */ diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 59737f1034..05f0b79e82 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -204,6 +204,7 @@ typedef enum NodeTag T_JsonFormat, T_JsonReturning, T_JsonValueExpr, + T_JsonConstructorExpr, /* * TAGS FOR EXPRESSION STATE NODES (execnodes.h) @@ -494,6 +495,13 @@ typedef enum NodeTag T_VacuumRelation, T_PublicationObjSpec, T_PublicationTable, + T_JsonObjectConstructor, + T_JsonArrayConstructor, + T_JsonArrayQueryConstructor, + T_JsonAggConstructor, + T_JsonObjectAgg, + T_JsonArrayAgg, + T_JsonKeyValue, T_JsonOutput, /* diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index e06d43d4df..3b12a708ec 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1563,9 +1563,103 @@ typedef struct JsonOutput { NodeTag type; TypeName *typeName; /* RETURNING type name, if specified */ - JsonReturning returning; /* RETURNING FORMAT clause and type Oids */ + JsonReturning *returning; /* RETURNING FORMAT clause and type Oids */ } JsonOutput; +/* + * JsonKeyValue - + * untransformed representation of JSON object key-value pair for + * JSON_OBJECT() and JSON_OBJECTAGG() + */ +typedef struct JsonKeyValue +{ + NodeTag type; + Expr *key; /* key expression */ + JsonValueExpr *value; /* JSON value expression */ +} JsonKeyValue; + +/* + * JsonObjectConstructor - + * untransformed representation of JSON_OBJECT() constructor + */ +typedef struct JsonObjectConstructor +{ + NodeTag type; + List *exprs; /* list of JsonKeyValue pairs */ + JsonOutput *output; /* RETURNING clause, if specified */ + bool absent_on_null; /* skip NULL values? */ + bool unique; /* check key uniqueness? */ + int location; /* token location, or -1 if unknown */ +} JsonObjectConstructor; + +/* + * JsonArrayConstructor - + * untransformed representation of JSON_ARRAY(element,...) constructor + */ +typedef struct JsonArrayConstructor +{ + NodeTag type; + List *exprs; /* list of JsonValueExpr elements */ + JsonOutput *output; /* RETURNING clause, if specified */ + bool absent_on_null; /* skip NULL elements? */ + int location; /* token location, or -1 if unknown */ +} JsonArrayConstructor; + +/* + * JsonArrayQueryConstructor - + * untransformed representation of JSON_ARRAY(subquery) constructor + */ +typedef struct JsonArrayQueryConstructor +{ + NodeTag type; + Node *query; /* subquery */ + JsonOutput *output; /* RETURNING clause, if specified */ + JsonFormat *format; /* FORMAT clause for subquery, if specified */ + bool absent_on_null; /* skip NULL elements? */ + int location; /* token location, or -1 if unknown */ +} JsonArrayQueryConstructor; + +/* + * JsonAggConstructor - + * common fields of untransformed representation of + * JSON_ARRAYAGG() and JSON_OBJECTAGG() + */ +typedef struct JsonAggConstructor +{ + NodeTag type; + JsonOutput *output; /* RETURNING clause, if any */ + Node *agg_filter; /* FILTER clause, if any */ + List *agg_order; /* ORDER BY clause, if any */ + struct WindowDef *over; /* OVER clause, if any */ + int location; /* token location, or -1 if unknown */ +} JsonAggConstructor; + +/* + * JsonObjectAgg - + * untransformed representation of JSON_OBJECTAGG() + */ +typedef struct JsonObjectAgg +{ + NodeTag type; + JsonAggConstructor *constructor; /* common fields */ + JsonKeyValue *arg; /* object key-value pair */ + bool absent_on_null; /* skip NULL values? */ + bool unique; /* check key uniqueness? */ +} JsonObjectAgg; + +/* + * JsonArrayAgg - + * untransformed representation of JSON_ARRRAYAGG() + */ +typedef struct JsonArrayAgg +{ + NodeTag type; + JsonAggConstructor *constructor; /* common fields */ + JsonValueExpr *arg; /* array element expression */ + bool absent_on_null; /* skip NULL elements? */ +} JsonArrayAgg; + + /***************************************************************************** * Raw Grammar Output Statements *****************************************************************************/ diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 8e3c99bdb5..c48527e998 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -1292,6 +1292,31 @@ typedef struct JsonValueExpr JsonFormat *format; /* FORMAT clause, if specified */ } JsonValueExpr; +typedef enum JsonConstructorType +{ + JSCTOR_JSON_OBJECT = 1, + JSCTOR_JSON_ARRAY = 2, + JSCTOR_JSON_OBJECTAGG = 3, + JSCTOR_JSON_ARRAYAGG = 4 +} JsonConstructorType; + +/* + * JsonConstructorExpr - + * wrapper over FuncExpr/Aggref/WindowFunc for SQL/JSON constructors + */ +typedef struct JsonConstructorExpr +{ + Expr xpr; + JsonConstructorType type; /* constructor type */ + List *args; + Expr *func; /* underlying json[b]_xxx() function call */ + Expr *coercion; /* coercion to RETURNING type */ + JsonReturning *returning; /* RETURNING clause */ + bool absent_on_null; /* ABSENT ON NULL? */ + bool unique; /* WITH UNIQUE KEYS? (JSON_OBJECT[AGG] only) */ + int location; +} JsonConstructorExpr; + /* ---------------- * NullTest * diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index f3502b8be4..f44440d4a9 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -26,6 +26,7 @@ /* name, value, category, is-bare-label */ PG_KEYWORD("abort", ABORT_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("absent", ABSENT, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("absolute", ABSOLUTE_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("access", ACCESS, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("action", ACTION, UNRESERVED_KEYWORD, BARE_LABEL) @@ -229,7 +230,12 @@ PG_KEYWORD("isnull", ISNULL, TYPE_FUNC_NAME_KEYWORD, AS_LABEL) PG_KEYWORD("isolation", ISOLATION, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("join", JOIN, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("json", JSON, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("json_array", JSON_ARRAY, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("json_arrayagg", JSON_ARRAYAGG, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("json_object", JSON_OBJECT, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("json_objectagg", JSON_OBJECTAGG, COL_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("key", KEY, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("keys", KEYS, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("label", LABEL, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("language", LANGUAGE, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("large", LARGE_P, UNRESERVED_KEYWORD, BARE_LABEL) diff --git a/src/include/utils/json.h b/src/include/utils/json.h index 8a84a0cdb4..63d83b815f 100644 --- a/src/include/utils/json.h +++ b/src/include/utils/json.h @@ -20,5 +20,11 @@ extern void escape_json(StringInfo buf, const char *str); extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid, const int *tzp); +extern bool to_json_is_immutable(Oid typoid); +extern Datum json_build_object_worker(int nargs, Datum *args, bool *nulls, + Oid *types, bool absent_on_null, + bool unique_keys); +extern Datum json_build_array_worker(int nargs, Datum *args, bool *nulls, + Oid *types, bool absent_on_null); #endif /* JSON_H */ diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h index 4cbe6edf21..6bcf35dd0a 100644 --- a/src/include/utils/jsonb.h +++ b/src/include/utils/jsonb.h @@ -329,6 +329,8 @@ typedef struct JsonbParseState JsonbValue contVal; Size size; struct JsonbParseState *next; + bool unique_keys; /* Check object key uniqueness */ + bool skip_nulls; /* Skip null object fields */ } JsonbParseState; /* @@ -412,4 +414,11 @@ extern Datum jsonb_set_element(Jsonb *jb, Datum *path, int path_len, JsonbValue *newval); extern Datum jsonb_get_element(Jsonb *jb, Datum *path, int npath, bool *isnull, bool as_text); +extern bool to_jsonb_is_immutable(Oid typoid); +extern Datum jsonb_build_object_worker(int nargs, Datum *args, bool *nulls, + Oid *types, bool absent_on_null, + bool unique_keys); +extern Datum jsonb_build_array_worker(int nargs, Datum *args, bool *nulls, + Oid *types, bool absent_on_null); + #endif /* __JSONB_H__ */ diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl index dee6b8200d..5ec511fd01 100644 --- a/src/interfaces/ecpg/preproc/parse.pl +++ b/src/interfaces/ecpg/preproc/parse.pl @@ -47,6 +47,8 @@ my %replace_string = ( 'NOT_LA' => 'not', 'NULLS_LA' => 'nulls', 'WITH_LA' => 'with', + 'WITH_LA_UNIQUE' => 'with', + 'WITHOUT_LA' => 'without', 'TYPECAST' => '::', 'DOT_DOT' => '..', 'COLON_EQUALS' => ':=', diff --git a/src/interfaces/ecpg/preproc/parser.c b/src/interfaces/ecpg/preproc/parser.c index a44e07a17a..5e2b606f9b 100644 --- a/src/interfaces/ecpg/preproc/parser.c +++ b/src/interfaces/ecpg/preproc/parser.c @@ -83,6 +83,7 @@ filtered_base_yylex(void) case WITH: case UIDENT: case USCONST: + case WITHOUT: break; default: return cur_token; @@ -143,6 +144,19 @@ filtered_base_yylex(void) case ORDINALITY: cur_token = WITH_LA; break; + case UNIQUE: + cur_token = WITH_LA_UNIQUE; + break; + } + break; + + case WITHOUT: + /* Replace WITHOUT by WITHOUT_LA if it's followed by TIME */ + switch (next_token) + { + case TIME: + cur_token = WITHOUT_LA; + break; } break; case UIDENT: diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 4ce6c039b4..15e4016836 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1473,8 +1473,10 @@ WHERE a.aggfnoid = p.oid AND NOT binary_coercible(p.proargtypes[1], ptr.proargtypes[2])) OR (p.pronargs > 2 AND NOT binary_coercible(p.proargtypes[2], ptr.proargtypes[3])) - -- we could carry the check further, but 3 args is enough for now - OR (p.pronargs > 3) + OR (p.pronargs > 3 AND + NOT binary_coercible(p.proargtypes[3], ptr.proargtypes[4])) + -- we could carry the check further, but 4 args is enough for now + OR (p.pronargs > 4) ); aggfnoid | proname | oid | proname ----------+---------+-----+--------- diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out new file mode 100644 index 0000000000..7dca5a8a30 --- /dev/null +++ b/src/test/regress/expected/sqljson.out @@ -0,0 +1,746 @@ +-- JSON_OBJECT() +SELECT JSON_OBJECT(); + json_object +------------- + {} +(1 row) + +SELECT JSON_OBJECT(RETURNING json); + json_object +------------- + {} +(1 row) + +SELECT JSON_OBJECT(RETURNING json FORMAT JSON); + json_object +------------- + {} +(1 row) + +SELECT JSON_OBJECT(RETURNING jsonb); + json_object +------------- + {} +(1 row) + +SELECT JSON_OBJECT(RETURNING jsonb FORMAT JSON); + json_object +------------- + {} +(1 row) + +SELECT JSON_OBJECT(RETURNING text); + json_object +------------- + {} +(1 row) + +SELECT JSON_OBJECT(RETURNING text FORMAT JSON); + json_object +------------- + {} +(1 row) + +SELECT JSON_OBJECT(RETURNING text FORMAT JSON ENCODING UTF8); +ERROR: cannot set JSON encoding for non-bytea output types +LINE 1: SELECT JSON_OBJECT(RETURNING text FORMAT JSON ENCODING UTF8)... + ^ +SELECT JSON_OBJECT(RETURNING text FORMAT JSON ENCODING INVALID_ENCODING); +ERROR: unrecognized JSON encoding: invalid_encoding +SELECT JSON_OBJECT(RETURNING bytea); + json_object +------------- + \x7b7d +(1 row) + +SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON); + json_object +------------- + \x7b7d +(1 row) + +SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF8); + json_object +------------- + \x7b7d +(1 row) + +SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF16); +ERROR: unsupported JSON encoding +LINE 1: SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF1... + ^ +HINT: only UTF8 JSON encoding is supported +SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF32); +ERROR: unsupported JSON encoding +LINE 1: SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF3... + ^ +HINT: only UTF8 JSON encoding is supported +SELECT JSON_OBJECT('foo': NULL::int FORMAT JSON); +ERROR: cannot use non-string types with explicit FORMAT JSON clause +LINE 1: SELECT JSON_OBJECT('foo': NULL::int FORMAT JSON); + ^ +SELECT JSON_OBJECT('foo': NULL::int FORMAT JSON ENCODING UTF8); +ERROR: JSON ENCODING clause is only allowed for bytea input type +LINE 1: SELECT JSON_OBJECT('foo': NULL::int FORMAT JSON ENCODING UTF... + ^ +SELECT JSON_OBJECT('foo': NULL::json FORMAT JSON); +WARNING: FORMAT JSON has no effect for json and jsonb types +LINE 1: SELECT JSON_OBJECT('foo': NULL::json FORMAT JSON); + ^ + json_object +---------------- + {"foo" : null} +(1 row) + +SELECT JSON_OBJECT('foo': NULL::json FORMAT JSON ENCODING UTF8); +ERROR: JSON ENCODING clause is only allowed for bytea input type +LINE 1: SELECT JSON_OBJECT('foo': NULL::json FORMAT JSON ENCODING UT... + ^ +SELECT JSON_OBJECT('foo': NULL::jsonb FORMAT JSON); +WARNING: FORMAT JSON has no effect for json and jsonb types +LINE 1: SELECT JSON_OBJECT('foo': NULL::jsonb FORMAT JSON); + ^ + json_object +--------------- + {"foo": null} +(1 row) + +SELECT JSON_OBJECT('foo': NULL::jsonb FORMAT JSON ENCODING UTF8); +ERROR: JSON ENCODING clause is only allowed for bytea input type +LINE 1: SELECT JSON_OBJECT('foo': NULL::jsonb FORMAT JSON ENCODING U... + ^ +SELECT JSON_OBJECT(NULL: 1); +ERROR: argument 1 cannot be null +HINT: Object keys should be text. +SELECT JSON_OBJECT('a': 2 + 3); + json_object +------------- + {"a" : 5} +(1 row) + +SELECT JSON_OBJECT('a' VALUE 2 + 3); + json_object +------------- + {"a" : 5} +(1 row) + +--SELECT JSON_OBJECT(KEY 'a' VALUE 2 + 3); +SELECT JSON_OBJECT('a' || 2: 1); + json_object +------------- + {"a2" : 1} +(1 row) + +SELECT JSON_OBJECT(('a' || 2) VALUE 1); + json_object +------------- + {"a2" : 1} +(1 row) + +--SELECT JSON_OBJECT('a' || 2 VALUE 1); +--SELECT JSON_OBJECT(KEY 'a' || 2 VALUE 1); +SELECT JSON_OBJECT('a': 2::text); + json_object +------------- + {"a" : "2"} +(1 row) + +SELECT JSON_OBJECT('a' VALUE 2::text); + json_object +------------- + {"a" : "2"} +(1 row) + +--SELECT JSON_OBJECT(KEY 'a' VALUE 2::text); +SELECT JSON_OBJECT(1::text: 2); + json_object +------------- + {"1" : 2} +(1 row) + +SELECT JSON_OBJECT((1::text) VALUE 2); + json_object +------------- + {"1" : 2} +(1 row) + +--SELECT JSON_OBJECT(1::text VALUE 2); +--SELECT JSON_OBJECT(KEY 1::text VALUE 2); +SELECT JSON_OBJECT(json '[1]': 123); +ERROR: key value must be scalar, not array, composite, or json +SELECT JSON_OBJECT(ARRAY[1,2,3]: 'aaa'); +ERROR: key value must be scalar, not array, composite, or json +SELECT JSON_OBJECT( + 'a': '123', + 1.23: 123, + 'c': json '[ 1,true,{ } ]', + 'd': jsonb '{ "x" : 123.45 }' +); + json_object +------------------------------------------------------------------- + {"a": "123", "c": [1, true, {}], "d": {"x": 123.45}, "1.23": 123} +(1 row) + +SELECT JSON_OBJECT( + 'a': '123', + 1.23: 123, + 'c': json '[ 1,true,{ } ]', + 'd': jsonb '{ "x" : 123.45 }' + RETURNING jsonb +); + json_object +------------------------------------------------------------------- + {"a": "123", "c": [1, true, {}], "d": {"x": 123.45}, "1.23": 123} +(1 row) + +/* +SELECT JSON_OBJECT( + 'a': '123', + KEY 1.23 VALUE 123, + 'c' VALUE json '[1, true, {}]' +); +*/ +SELECT JSON_OBJECT('a': '123', 'b': JSON_OBJECT('a': 111, 'b': 'aaa')); + json_object +----------------------------------------------- + {"a" : "123", "b" : {"a" : 111, "b" : "aaa"}} +(1 row) + +SELECT JSON_OBJECT('a': '123', 'b': JSON_OBJECT('a': 111, 'b': 'aaa' RETURNING jsonb)); + json_object +------------------------------------------- + {"a": "123", "b": {"a": 111, "b": "aaa"}} +(1 row) + +SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING text)); + json_object +----------------------- + {"a" : "{\"b\" : 1}"} +(1 row) + +SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING text) FORMAT JSON); + json_object +------------------- + {"a" : {"b" : 1}} +(1 row) + +SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING bytea)); + json_object +--------------------------------- + {"a" : "\\x7b226222203a20317d"} +(1 row) + +SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING bytea) FORMAT JSON); + json_object +------------------- + {"a" : {"b" : 1}} +(1 row) + +SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2); + json_object +---------------------------------- + {"a" : "1", "b" : null, "c" : 2} +(1 row) + +SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2 NULL ON NULL); + json_object +---------------------------------- + {"a" : "1", "b" : null, "c" : 2} +(1 row) + +SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2 ABSENT ON NULL); + json_object +---------------------- + {"a" : "1", "c" : 2} +(1 row) + +SELECT JSON_OBJECT(1: 1, '1': NULL WITH UNIQUE); +ERROR: duplicate JSON key "1" +SELECT JSON_OBJECT(1: 1, '1': NULL ABSENT ON NULL WITH UNIQUE); +ERROR: duplicate JSON key "1" +SELECT JSON_OBJECT(1: 1, '1': NULL NULL ON NULL WITH UNIQUE RETURNING jsonb); +ERROR: duplicate JSON object key value +SELECT JSON_OBJECT(1: 1, '1': NULL ABSENT ON NULL WITH UNIQUE RETURNING jsonb); +ERROR: duplicate JSON object key value +SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 NULL ON NULL WITH UNIQUE); +ERROR: duplicate JSON key "1" +SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITH UNIQUE); +ERROR: duplicate JSON key "1" +SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITHOUT UNIQUE); + json_object +-------------------- + {"1" : 1, "1" : 1} +(1 row) + +SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITH UNIQUE RETURNING jsonb); +ERROR: duplicate JSON object key value +SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITHOUT UNIQUE RETURNING jsonb); + json_object +------------- + {"1": 1} +(1 row) + +SELECT JSON_OBJECT(1: 1, '2': NULL, '3': 1, 4: NULL, '5': 'a' ABSENT ON NULL WITH UNIQUE RETURNING jsonb); + json_object +---------------------------- + {"1": 1, "3": 1, "5": "a"} +(1 row) + +-- JSON_ARRAY() +SELECT JSON_ARRAY(); + json_array +------------ + [] +(1 row) + +SELECT JSON_ARRAY(RETURNING json); + json_array +------------ + [] +(1 row) + +SELECT JSON_ARRAY(RETURNING json FORMAT JSON); + json_array +------------ + [] +(1 row) + +SELECT JSON_ARRAY(RETURNING jsonb); + json_array +------------ + [] +(1 row) + +SELECT JSON_ARRAY(RETURNING jsonb FORMAT JSON); + json_array +------------ + [] +(1 row) + +SELECT JSON_ARRAY(RETURNING text); + json_array +------------ + [] +(1 row) + +SELECT JSON_ARRAY(RETURNING text FORMAT JSON); + json_array +------------ + [] +(1 row) + +SELECT JSON_ARRAY(RETURNING text FORMAT JSON ENCODING UTF8); +ERROR: cannot set JSON encoding for non-bytea output types +LINE 1: SELECT JSON_ARRAY(RETURNING text FORMAT JSON ENCODING UTF8); + ^ +SELECT JSON_ARRAY(RETURNING text FORMAT JSON ENCODING INVALID_ENCODING); +ERROR: unrecognized JSON encoding: invalid_encoding +SELECT JSON_ARRAY(RETURNING bytea); + json_array +------------ + \x5b5d +(1 row) + +SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON); + json_array +------------ + \x5b5d +(1 row) + +SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF8); + json_array +------------ + \x5b5d +(1 row) + +SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF16); +ERROR: unsupported JSON encoding +LINE 1: SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF16... + ^ +HINT: only UTF8 JSON encoding is supported +SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF32); +ERROR: unsupported JSON encoding +LINE 1: SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF32... + ^ +HINT: only UTF8 JSON encoding is supported +SELECT JSON_ARRAY('aaa', 111, true, array[1,2,3], NULL, json '{"a": [1]}', jsonb '["a",3]'); + json_array +----------------------------------------------------- + ["aaa", 111, true, [1, 2, 3], {"a": [1]}, ["a", 3]] +(1 row) + +SELECT JSON_ARRAY('a', NULL, 'b' NULL ON NULL); + json_array +------------------ + ["a", null, "b"] +(1 row) + +SELECT JSON_ARRAY('a', NULL, 'b' ABSENT ON NULL); + json_array +------------ + ["a", "b"] +(1 row) + +SELECT JSON_ARRAY(NULL, NULL, 'b' ABSENT ON NULL); + json_array +------------ + ["b"] +(1 row) + +SELECT JSON_ARRAY('a', NULL, 'b' NULL ON NULL RETURNING jsonb); + json_array +------------------ + ["a", null, "b"] +(1 row) + +SELECT JSON_ARRAY('a', NULL, 'b' ABSENT ON NULL RETURNING jsonb); + json_array +------------ + ["a", "b"] +(1 row) + +SELECT JSON_ARRAY(NULL, NULL, 'b' ABSENT ON NULL RETURNING jsonb); + json_array +------------ + ["b"] +(1 row) + +SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' RETURNING text)); + json_array +------------------------------- + ["[\"{ \\\"a\\\" : 123 }\"]"] +(1 row) + +SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING text)); + json_array +----------------------- + ["[{ \"a\" : 123 }]"] +(1 row) + +SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING text) FORMAT JSON); + json_array +------------------- + [[{ "a" : 123 }]] +(1 row) + +SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i)); + json_array +------------ + [1, 2, 4] +(1 row) + +SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i)); + json_array +------------ + [[1,2], + + [3,4]] +(1 row) + +SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i) RETURNING jsonb); + json_array +------------------ + [[1, 2], [3, 4]] +(1 row) + +--SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i) NULL ON NULL); +--SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i) NULL ON NULL RETURNING jsonb); +SELECT JSON_ARRAY(SELECT i FROM (VALUES (3), (1), (NULL), (2)) foo(i) ORDER BY i); + json_array +------------ + [1, 2, 3] +(1 row) + +-- Should fail +SELECT JSON_ARRAY(SELECT FROM (VALUES (1)) foo(i)); +ERROR: subquery must return only one column +LINE 1: SELECT JSON_ARRAY(SELECT FROM (VALUES (1)) foo(i)); + ^ +SELECT JSON_ARRAY(SELECT i, i FROM (VALUES (1)) foo(i)); +ERROR: subquery must return only one column +LINE 1: SELECT JSON_ARRAY(SELECT i, i FROM (VALUES (1)) foo(i)); + ^ +SELECT JSON_ARRAY(SELECT * FROM (VALUES (1, 2)) foo(i, j)); +ERROR: subquery must return only one column +LINE 1: SELECT JSON_ARRAY(SELECT * FROM (VALUES (1, 2)) foo(i, j)); + ^ +-- JSON_ARRAYAGG() +SELECT JSON_ARRAYAGG(i) IS NULL, + JSON_ARRAYAGG(i RETURNING jsonb) IS NULL +FROM generate_series(1, 0) i; + ?column? | ?column? +----------+---------- + t | t +(1 row) + +SELECT JSON_ARRAYAGG(i), + JSON_ARRAYAGG(i RETURNING jsonb) +FROM generate_series(1, 5) i; + json_arrayagg | json_arrayagg +-----------------+----------------- + [1, 2, 3, 4, 5] | [1, 2, 3, 4, 5] +(1 row) + +SELECT JSON_ARRAYAGG(i ORDER BY i DESC) +FROM generate_series(1, 5) i; + json_arrayagg +----------------- + [5, 4, 3, 2, 1] +(1 row) + +SELECT JSON_ARRAYAGG(i::text::json) +FROM generate_series(1, 5) i; + json_arrayagg +----------------- + [1, 2, 3, 4, 5] +(1 row) + +SELECT JSON_ARRAYAGG(JSON_ARRAY(i, i + 1 RETURNING text) FORMAT JSON) +FROM generate_series(1, 5) i; + json_arrayagg +------------------------------------------ + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6]] +(1 row) + +SELECT JSON_ARRAYAGG(NULL), + JSON_ARRAYAGG(NULL RETURNING jsonb) +FROM generate_series(1, 5); + json_arrayagg | json_arrayagg +---------------+--------------- + [] | [] +(1 row) + +SELECT JSON_ARRAYAGG(NULL NULL ON NULL), + JSON_ARRAYAGG(NULL NULL ON NULL RETURNING jsonb) +FROM generate_series(1, 5); + json_arrayagg | json_arrayagg +--------------------------------+-------------------------------- + [null, null, null, null, null] | [null, null, null, null, null] +(1 row) + +SELECT + JSON_ARRAYAGG(bar), + JSON_ARRAYAGG(bar RETURNING jsonb), + JSON_ARRAYAGG(bar ABSENT ON NULL), + JSON_ARRAYAGG(bar ABSENT ON NULL RETURNING jsonb), + JSON_ARRAYAGG(bar NULL ON NULL), + JSON_ARRAYAGG(bar NULL ON NULL RETURNING jsonb), + JSON_ARRAYAGG(foo), + JSON_ARRAYAGG(foo RETURNING jsonb), + JSON_ARRAYAGG(foo ORDER BY bar) FILTER (WHERE bar > 2), + JSON_ARRAYAGG(foo ORDER BY bar RETURNING jsonb) FILTER (WHERE bar > 2) +FROM + (VALUES (NULL), (3), (1), (NULL), (NULL), (5), (2), (4), (NULL)) foo(bar); + json_arrayagg | json_arrayagg | json_arrayagg | json_arrayagg | json_arrayagg | json_arrayagg | json_arrayagg | json_arrayagg | json_arrayagg | json_arrayagg +-----------------+-----------------+-----------------+-----------------+-----------------------------------------+-----------------------------------------+-----------------+--------------------------------------------------------------------------------------------------------------------------+---------------+-------------------------------------- + [3, 1, 5, 2, 4] | [3, 1, 5, 2, 4] | [3, 1, 5, 2, 4] | [3, 1, 5, 2, 4] | [null, 3, 1, null, null, 5, 2, 4, null] | [null, 3, 1, null, null, 5, 2, 4, null] | [{"bar":null}, +| [{"bar": null}, {"bar": 3}, {"bar": 1}, {"bar": null}, {"bar": null}, {"bar": 5}, {"bar": 2}, {"bar": 4}, {"bar": null}] | [{"bar":3}, +| [{"bar": 3}, {"bar": 4}, {"bar": 5}] + | | | | | | {"bar":3}, +| | {"bar":4}, +| + | | | | | | {"bar":1}, +| | {"bar":5}] | + | | | | | | {"bar":null}, +| | | + | | | | | | {"bar":null}, +| | | + | | | | | | {"bar":5}, +| | | + | | | | | | {"bar":2}, +| | | + | | | | | | {"bar":4}, +| | | + | | | | | | {"bar":null}] | | | +(1 row) + +SELECT + bar, JSON_ARRAYAGG(bar) FILTER (WHERE bar > 2) OVER (PARTITION BY foo.bar % 2) +FROM + (VALUES (NULL), (3), (1), (NULL), (NULL), (5), (2), (4), (NULL), (5), (4)) foo(bar); + bar | json_arrayagg +-----+--------------- + 4 | [4, 4] + 4 | [4, 4] + 2 | [4, 4] + 5 | [5, 3, 5] + 3 | [5, 3, 5] + 1 | [5, 3, 5] + 5 | [5, 3, 5] + | + | + | + | +(11 rows) + +-- JSON_OBJECTAGG() +SELECT JSON_OBJECTAGG('key': 1) IS NULL, + JSON_OBJECTAGG('key': 1 RETURNING jsonb) IS NULL +WHERE FALSE; + ?column? | ?column? +----------+---------- + t | t +(1 row) + +SELECT JSON_OBJECTAGG(NULL: 1); +ERROR: field name must not be null +SELECT JSON_OBJECTAGG(NULL: 1 RETURNING jsonb); +ERROR: field name must not be null +SELECT + JSON_OBJECTAGG(i: i), +-- JSON_OBJECTAGG(i VALUE i), +-- JSON_OBJECTAGG(KEY i VALUE i), + JSON_OBJECTAGG(i: i RETURNING jsonb) +FROM + generate_series(1, 5) i; + json_objectagg | json_objectagg +-------------------------------------------------+------------------------------------------ + { "1" : 1, "2" : 2, "3" : 3, "4" : 4, "5" : 5 } | {"1": 1, "2": 2, "3": 3, "4": 4, "5": 5} +(1 row) + +SELECT + JSON_OBJECTAGG(k: v), + JSON_OBJECTAGG(k: v NULL ON NULL), + JSON_OBJECTAGG(k: v ABSENT ON NULL), + JSON_OBJECTAGG(k: v RETURNING jsonb), + JSON_OBJECTAGG(k: v NULL ON NULL RETURNING jsonb), + JSON_OBJECTAGG(k: v ABSENT ON NULL RETURNING jsonb) +FROM + (VALUES (1, 1), (1, NULL), (2, NULL), (3, 3)) foo(k, v); + json_objectagg | json_objectagg | json_objectagg | json_objectagg | json_objectagg | json_objectagg +----------------------------------------------+----------------------------------------------+----------------------+--------------------------------+--------------------------------+------------------ + { "1" : 1, "1" : null, "2" : null, "3" : 3 } | { "1" : 1, "1" : null, "2" : null, "3" : 3 } | { "1" : 1, "3" : 3 } | {"1": null, "2": null, "3": 3} | {"1": null, "2": null, "3": 3} | {"1": 1, "3": 3} +(1 row) + +SELECT JSON_OBJECTAGG(k: v WITH UNIQUE KEYS) +FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v); +ERROR: duplicate JSON key "1" +SELECT JSON_OBJECTAGG(k: v ABSENT ON NULL WITH UNIQUE KEYS) +FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v); +ERROR: duplicate JSON key "1" +SELECT JSON_OBJECTAGG(k: v ABSENT ON NULL WITH UNIQUE KEYS) +FROM (VALUES (1, 1), (0, NULL), (3, NULL), (2, 2), (4, NULL)) foo(k, v); + json_objectagg +---------------------- + { "1" : 1, "2" : 2 } +(1 row) + +SELECT JSON_OBJECTAGG(k: v WITH UNIQUE KEYS RETURNING jsonb) +FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v); +ERROR: duplicate JSON object key value +SELECT JSON_OBJECTAGG(k: v ABSENT ON NULL WITH UNIQUE KEYS RETURNING jsonb) +FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v); +ERROR: duplicate JSON object key value +-- Test JSON_OBJECT deparsing +EXPLAIN (VERBOSE, COSTS OFF) +SELECT JSON_OBJECT('foo' : '1' FORMAT JSON, 'bar' : 'baz' RETURNING json); + QUERY PLAN +------------------------------------------------------------------------------ + Result + Output: JSON_OBJECT('foo' : '1'::json, 'bar' : 'baz'::text RETURNING json) +(2 rows) + +CREATE VIEW json_object_view AS +SELECT JSON_OBJECT('foo' : '1' FORMAT JSON, 'bar' : 'baz' RETURNING json); +\sv json_object_view +CREATE OR REPLACE VIEW public.json_object_view AS + SELECT JSON_OBJECT('foo' : '1'::text FORMAT JSON, 'bar' : 'baz'::text RETURNING json) AS "json_object" +DROP VIEW json_object_view; +-- Test JSON_ARRAY deparsing +EXPLAIN (VERBOSE, COSTS OFF) +SELECT JSON_ARRAY('1' FORMAT JSON, 2 RETURNING json); + QUERY PLAN +--------------------------------------------------- + Result + Output: JSON_ARRAY('1'::json, 2 RETURNING json) +(2 rows) + +CREATE VIEW json_array_view AS +SELECT JSON_ARRAY('1' FORMAT JSON, 2 RETURNING json); +\sv json_array_view +CREATE OR REPLACE VIEW public.json_array_view AS + SELECT JSON_ARRAY('1'::text FORMAT JSON, 2 RETURNING json) AS "json_array" +DROP VIEW json_array_view; +-- Test JSON_OBJECTAGG deparsing +EXPLAIN (VERBOSE, COSTS OFF) +SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING text) FILTER (WHERE i > 3) +FROM generate_series(1,5) i; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------- + Aggregate + Output: JSON_OBJECTAGG(i : (('111'::text || (i)::text))::bytea FORMAT JSON WITH UNIQUE KEYS RETURNING text) FILTER (WHERE (i > 3)) + -> Function Scan on pg_catalog.generate_series i + Output: i + Function Call: generate_series(1, 5) +(5 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING text) OVER (PARTITION BY i % 2) +FROM generate_series(1,5) i; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------- + WindowAgg + Output: JSON_OBJECTAGG(i : (('111'::text || (i)::text))::bytea FORMAT JSON WITH UNIQUE KEYS RETURNING text) OVER (?), ((i % 2)) + -> Sort + Output: ((i % 2)), i + Sort Key: ((i.i % 2)) + -> Function Scan on pg_catalog.generate_series i + Output: (i % 2), i + Function Call: generate_series(1, 5) +(8 rows) + +CREATE VIEW json_objectagg_view AS +SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING text) FILTER (WHERE i > 3) +FROM generate_series(1,5) i; +\sv json_objectagg_view +CREATE OR REPLACE VIEW public.json_objectagg_view AS + SELECT JSON_OBJECTAGG(i.i : ('111'::text || i.i)::bytea FORMAT JSON WITH UNIQUE KEYS RETURNING text) FILTER (WHERE i.i > 3) AS "json_objectagg" + FROM generate_series(1, 5) i(i) +DROP VIEW json_objectagg_view; +-- Test JSON_ARRAYAGG deparsing +EXPLAIN (VERBOSE, COSTS OFF) +SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i > 3) +FROM generate_series(1,5) i; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------- + Aggregate + Output: JSON_ARRAYAGG((('111'::text || (i)::text))::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE (i > 3)) + -> Function Scan on pg_catalog.generate_series i + Output: i + Function Call: generate_series(1, 5) +(5 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) OVER (PARTITION BY i % 2) +FROM generate_series(1,5) i; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------- + WindowAgg + Output: JSON_ARRAYAGG((('111'::text || (i)::text))::bytea FORMAT JSON NULL ON NULL RETURNING text) OVER (?), ((i % 2)) + -> Sort + Output: ((i % 2)), i + Sort Key: ((i.i % 2)) + -> Function Scan on pg_catalog.generate_series i + Output: (i % 2), i + Function Call: generate_series(1, 5) +(8 rows) + +CREATE VIEW json_arrayagg_view AS +SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i > 3) +FROM generate_series(1,5) i; +\sv json_arrayagg_view +CREATE OR REPLACE VIEW public.json_arrayagg_view AS + SELECT JSON_ARRAYAGG(('111'::text || i.i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i.i > 3) AS "json_arrayagg" + FROM generate_series(1, 5) i(i) +DROP VIEW json_arrayagg_view; +-- Test JSON_ARRAY(subquery) deparsing +EXPLAIN (VERBOSE, COSTS OFF) +SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING jsonb); + QUERY PLAN +--------------------------------------------------------------------- + Result + Output: $0 + InitPlan 1 (returns $0) + -> Aggregate + Output: JSON_ARRAYAGG("*VALUES*".column1 RETURNING jsonb) + -> 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 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" +DROP VIEW json_array_subquery_view; diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 6d8f524ae9..3ce701a588 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -111,7 +111,7 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo # ---------- # Another group of parallel tests (JSON related) # ---------- -test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath +test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath sqljson # ---------- # Another group of parallel tests diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql index 2b292851e3..63fe114fed 100644 --- a/src/test/regress/sql/opr_sanity.sql +++ b/src/test/regress/sql/opr_sanity.sql @@ -854,8 +854,10 @@ WHERE a.aggfnoid = p.oid AND NOT binary_coercible(p.proargtypes[1], ptr.proargtypes[2])) OR (p.pronargs > 2 AND NOT binary_coercible(p.proargtypes[2], ptr.proargtypes[3])) - -- we could carry the check further, but 3 args is enough for now - OR (p.pronargs > 3) + OR (p.pronargs > 3 AND + NOT binary_coercible(p.proargtypes[3], ptr.proargtypes[4])) + -- we could carry the check further, but 4 args is enough for now + OR (p.pronargs > 4) ); -- Cross-check finalfn (if present) against its entry in pg_proc. diff --git a/src/test/regress/sql/sqljson.sql b/src/test/regress/sql/sqljson.sql new file mode 100644 index 0000000000..aaef2d8aab --- /dev/null +++ b/src/test/regress/sql/sqljson.sql @@ -0,0 +1,282 @@ +-- JSON_OBJECT() +SELECT JSON_OBJECT(); +SELECT JSON_OBJECT(RETURNING json); +SELECT JSON_OBJECT(RETURNING json FORMAT JSON); +SELECT JSON_OBJECT(RETURNING jsonb); +SELECT JSON_OBJECT(RETURNING jsonb FORMAT JSON); +SELECT JSON_OBJECT(RETURNING text); +SELECT JSON_OBJECT(RETURNING text FORMAT JSON); +SELECT JSON_OBJECT(RETURNING text FORMAT JSON ENCODING UTF8); +SELECT JSON_OBJECT(RETURNING text FORMAT JSON ENCODING INVALID_ENCODING); +SELECT JSON_OBJECT(RETURNING bytea); +SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON); +SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF8); +SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF16); +SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF32); + +SELECT JSON_OBJECT('foo': NULL::int FORMAT JSON); +SELECT JSON_OBJECT('foo': NULL::int FORMAT JSON ENCODING UTF8); +SELECT JSON_OBJECT('foo': NULL::json FORMAT JSON); +SELECT JSON_OBJECT('foo': NULL::json FORMAT JSON ENCODING UTF8); +SELECT JSON_OBJECT('foo': NULL::jsonb FORMAT JSON); +SELECT JSON_OBJECT('foo': NULL::jsonb FORMAT JSON ENCODING UTF8); + +SELECT JSON_OBJECT(NULL: 1); +SELECT JSON_OBJECT('a': 2 + 3); +SELECT JSON_OBJECT('a' VALUE 2 + 3); +--SELECT JSON_OBJECT(KEY 'a' VALUE 2 + 3); +SELECT JSON_OBJECT('a' || 2: 1); +SELECT JSON_OBJECT(('a' || 2) VALUE 1); +--SELECT JSON_OBJECT('a' || 2 VALUE 1); +--SELECT JSON_OBJECT(KEY 'a' || 2 VALUE 1); +SELECT JSON_OBJECT('a': 2::text); +SELECT JSON_OBJECT('a' VALUE 2::text); +--SELECT JSON_OBJECT(KEY 'a' VALUE 2::text); +SELECT JSON_OBJECT(1::text: 2); +SELECT JSON_OBJECT((1::text) VALUE 2); +--SELECT JSON_OBJECT(1::text VALUE 2); +--SELECT JSON_OBJECT(KEY 1::text VALUE 2); +SELECT JSON_OBJECT(json '[1]': 123); +SELECT JSON_OBJECT(ARRAY[1,2,3]: 'aaa'); + +SELECT JSON_OBJECT( + 'a': '123', + 1.23: 123, + 'c': json '[ 1,true,{ } ]', + 'd': jsonb '{ "x" : 123.45 }' +); + +SELECT JSON_OBJECT( + 'a': '123', + 1.23: 123, + 'c': json '[ 1,true,{ } ]', + 'd': jsonb '{ "x" : 123.45 }' + RETURNING jsonb +); + +/* +SELECT JSON_OBJECT( + 'a': '123', + KEY 1.23 VALUE 123, + 'c' VALUE json '[1, true, {}]' +); +*/ + +SELECT JSON_OBJECT('a': '123', 'b': JSON_OBJECT('a': 111, 'b': 'aaa')); +SELECT JSON_OBJECT('a': '123', 'b': JSON_OBJECT('a': 111, 'b': 'aaa' RETURNING jsonb)); + +SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING text)); +SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING text) FORMAT JSON); +SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING bytea)); +SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING bytea) FORMAT JSON); + +SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2); +SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2 NULL ON NULL); +SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2 ABSENT ON NULL); + +SELECT JSON_OBJECT(1: 1, '1': NULL WITH UNIQUE); +SELECT JSON_OBJECT(1: 1, '1': NULL ABSENT ON NULL WITH UNIQUE); +SELECT JSON_OBJECT(1: 1, '1': NULL NULL ON NULL WITH UNIQUE RETURNING jsonb); +SELECT JSON_OBJECT(1: 1, '1': NULL ABSENT ON NULL WITH UNIQUE RETURNING jsonb); + +SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 NULL ON NULL WITH UNIQUE); +SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITH UNIQUE); +SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITHOUT UNIQUE); +SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITH UNIQUE RETURNING jsonb); +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); + + +-- JSON_ARRAY() +SELECT JSON_ARRAY(); +SELECT JSON_ARRAY(RETURNING json); +SELECT JSON_ARRAY(RETURNING json FORMAT JSON); +SELECT JSON_ARRAY(RETURNING jsonb); +SELECT JSON_ARRAY(RETURNING jsonb FORMAT JSON); +SELECT JSON_ARRAY(RETURNING text); +SELECT JSON_ARRAY(RETURNING text FORMAT JSON); +SELECT JSON_ARRAY(RETURNING text FORMAT JSON ENCODING UTF8); +SELECT JSON_ARRAY(RETURNING text FORMAT JSON ENCODING INVALID_ENCODING); +SELECT JSON_ARRAY(RETURNING bytea); +SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON); +SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF8); +SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF16); +SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF32); + +SELECT JSON_ARRAY('aaa', 111, true, array[1,2,3], NULL, json '{"a": [1]}', jsonb '["a",3]'); + +SELECT JSON_ARRAY('a', NULL, 'b' NULL ON NULL); +SELECT JSON_ARRAY('a', NULL, 'b' ABSENT ON NULL); +SELECT JSON_ARRAY(NULL, NULL, 'b' ABSENT ON NULL); +SELECT JSON_ARRAY('a', NULL, 'b' NULL ON NULL RETURNING jsonb); +SELECT JSON_ARRAY('a', NULL, 'b' ABSENT ON NULL RETURNING jsonb); +SELECT JSON_ARRAY(NULL, NULL, 'b' ABSENT ON NULL RETURNING jsonb); + +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); + +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 (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i) NULL ON NULL); +--SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i) NULL ON NULL RETURNING jsonb); +SELECT JSON_ARRAY(SELECT i FROM (VALUES (3), (1), (NULL), (2)) foo(i) ORDER BY i); +-- Should fail +SELECT JSON_ARRAY(SELECT FROM (VALUES (1)) foo(i)); +SELECT JSON_ARRAY(SELECT i, i FROM (VALUES (1)) foo(i)); +SELECT JSON_ARRAY(SELECT * FROM (VALUES (1, 2)) foo(i, j)); + +-- JSON_ARRAYAGG() +SELECT JSON_ARRAYAGG(i) IS NULL, + JSON_ARRAYAGG(i RETURNING jsonb) IS NULL +FROM generate_series(1, 0) i; + +SELECT JSON_ARRAYAGG(i), + JSON_ARRAYAGG(i RETURNING jsonb) +FROM generate_series(1, 5) i; + +SELECT JSON_ARRAYAGG(i ORDER BY i DESC) +FROM generate_series(1, 5) i; + +SELECT JSON_ARRAYAGG(i::text::json) +FROM generate_series(1, 5) i; + +SELECT JSON_ARRAYAGG(JSON_ARRAY(i, i + 1 RETURNING text) FORMAT JSON) +FROM generate_series(1, 5) i; + +SELECT JSON_ARRAYAGG(NULL), + JSON_ARRAYAGG(NULL RETURNING jsonb) +FROM generate_series(1, 5); + +SELECT JSON_ARRAYAGG(NULL NULL ON NULL), + JSON_ARRAYAGG(NULL NULL ON NULL RETURNING jsonb) +FROM generate_series(1, 5); + +SELECT + JSON_ARRAYAGG(bar), + JSON_ARRAYAGG(bar RETURNING jsonb), + JSON_ARRAYAGG(bar ABSENT ON NULL), + JSON_ARRAYAGG(bar ABSENT ON NULL RETURNING jsonb), + JSON_ARRAYAGG(bar NULL ON NULL), + JSON_ARRAYAGG(bar NULL ON NULL RETURNING jsonb), + JSON_ARRAYAGG(foo), + JSON_ARRAYAGG(foo RETURNING jsonb), + JSON_ARRAYAGG(foo ORDER BY bar) FILTER (WHERE bar > 2), + JSON_ARRAYAGG(foo ORDER BY bar RETURNING jsonb) FILTER (WHERE bar > 2) +FROM + (VALUES (NULL), (3), (1), (NULL), (NULL), (5), (2), (4), (NULL)) foo(bar); + +SELECT + bar, JSON_ARRAYAGG(bar) FILTER (WHERE bar > 2) OVER (PARTITION BY foo.bar % 2) +FROM + (VALUES (NULL), (3), (1), (NULL), (NULL), (5), (2), (4), (NULL), (5), (4)) foo(bar); + +-- JSON_OBJECTAGG() +SELECT JSON_OBJECTAGG('key': 1) IS NULL, + JSON_OBJECTAGG('key': 1 RETURNING jsonb) IS NULL +WHERE FALSE; + +SELECT JSON_OBJECTAGG(NULL: 1); + +SELECT JSON_OBJECTAGG(NULL: 1 RETURNING jsonb); + +SELECT + JSON_OBJECTAGG(i: i), +-- JSON_OBJECTAGG(i VALUE i), +-- JSON_OBJECTAGG(KEY i VALUE i), + JSON_OBJECTAGG(i: i RETURNING jsonb) +FROM + generate_series(1, 5) i; + +SELECT + JSON_OBJECTAGG(k: v), + JSON_OBJECTAGG(k: v NULL ON NULL), + JSON_OBJECTAGG(k: v ABSENT ON NULL), + JSON_OBJECTAGG(k: v RETURNING jsonb), + JSON_OBJECTAGG(k: v NULL ON NULL RETURNING jsonb), + JSON_OBJECTAGG(k: v ABSENT ON NULL RETURNING jsonb) +FROM + (VALUES (1, 1), (1, NULL), (2, NULL), (3, 3)) foo(k, v); + +SELECT JSON_OBJECTAGG(k: v WITH UNIQUE KEYS) +FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v); + +SELECT JSON_OBJECTAGG(k: v ABSENT ON NULL WITH UNIQUE KEYS) +FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v); + +SELECT JSON_OBJECTAGG(k: v ABSENT ON NULL WITH UNIQUE KEYS) +FROM (VALUES (1, 1), (0, NULL), (3, NULL), (2, 2), (4, NULL)) foo(k, v); + +SELECT JSON_OBJECTAGG(k: v WITH UNIQUE KEYS RETURNING jsonb) +FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v); + +SELECT JSON_OBJECTAGG(k: v ABSENT ON NULL WITH UNIQUE KEYS RETURNING jsonb) +FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v); + +-- Test JSON_OBJECT deparsing +EXPLAIN (VERBOSE, COSTS OFF) +SELECT JSON_OBJECT('foo' : '1' FORMAT JSON, 'bar' : 'baz' RETURNING json); + +CREATE VIEW json_object_view AS +SELECT JSON_OBJECT('foo' : '1' FORMAT JSON, 'bar' : 'baz' RETURNING json); + +\sv json_object_view + +DROP VIEW json_object_view; + +-- Test JSON_ARRAY deparsing +EXPLAIN (VERBOSE, COSTS OFF) +SELECT JSON_ARRAY('1' FORMAT JSON, 2 RETURNING json); + +CREATE VIEW json_array_view AS +SELECT JSON_ARRAY('1' FORMAT JSON, 2 RETURNING json); + +\sv json_array_view + +DROP VIEW json_array_view; + +-- Test JSON_OBJECTAGG deparsing +EXPLAIN (VERBOSE, COSTS OFF) +SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING text) FILTER (WHERE i > 3) +FROM generate_series(1,5) i; + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING text) OVER (PARTITION BY i % 2) +FROM generate_series(1,5) i; + +CREATE VIEW json_objectagg_view AS +SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING text) FILTER (WHERE i > 3) +FROM generate_series(1,5) i; + +\sv json_objectagg_view + +DROP VIEW json_objectagg_view; + +-- Test JSON_ARRAYAGG deparsing +EXPLAIN (VERBOSE, COSTS OFF) +SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i > 3) +FROM generate_series(1,5) i; + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) OVER (PARTITION BY i % 2) +FROM generate_series(1,5) i; + +CREATE VIEW json_arrayagg_view AS +SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i > 3) +FROM generate_series(1,5) i; + +\sv json_arrayagg_view + +DROP VIEW json_arrayagg_view; + +-- Test JSON_ARRAY(subquery) deparsing +EXPLAIN (VERBOSE, COSTS OFF) +SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING jsonb); + +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 + +DROP VIEW json_array_subquery_view;