From d8e9ebb464e4a3f1ea6b5ff36d1576278599b66c Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 4 Dec 2018 02:05:11 +0300 Subject: [PATCH 09/13] SQL/JSON functions --- contrib/pg_stat_statements/pg_stat_statements.c | 41 + src/backend/executor/execExpr.c | 220 +++- src/backend/executor/execExprInterp.c | 419 ++++++++ src/backend/jit/llvm/llvmjit_expr.c | 7 + src/backend/nodes/copyfuncs.c | 367 +++++++ src/backend/nodes/equalfuncs.c | 120 +++ src/backend/nodes/makefuncs.c | 85 ++ src/backend/nodes/nodeFuncs.c | 289 ++++++ src/backend/nodes/outfuncs.c | 110 ++ src/backend/nodes/readfuncs.c | 133 +++ src/backend/optimizer/path/costsize.c | 3 +- src/backend/optimizer/util/clauses.c | 11 + src/backend/parser/gram.y | 697 ++++++++++++- src/backend/parser/parse_collate.c | 4 + src/backend/parser/parse_expr.c | 1256 +++++++++++++++++++++++ src/backend/parser/parse_target.c | 28 + src/backend/parser/parser.c | 18 +- src/backend/utils/adt/json.c | 505 ++++++++- src/backend/utils/adt/jsonb.c | 391 ++++++- src/backend/utils/adt/jsonfuncs.c | 44 + src/backend/utils/adt/jsonpath_exec.c | 101 ++ src/backend/utils/adt/ruleutils.c | 363 ++++++- src/include/catalog/pg_aggregate.dat | 8 + src/include/catalog/pg_proc.dat | 68 ++ src/include/executor/execExpr.h | 58 ++ src/include/executor/executor.h | 2 + src/include/nodes/makefuncs.h | 7 + src/include/nodes/nodes.h | 19 + src/include/nodes/parsenodes.h | 215 ++++ src/include/nodes/primnodes.h | 166 +++ src/include/parser/kwlist.h | 20 + src/include/utils/jsonapi.h | 4 + src/include/utils/jsonb.h | 3 + src/include/utils/jsonpath.h | 19 +- src/interfaces/ecpg/preproc/parse.pl | 2 + src/interfaces/ecpg/preproc/parser.c | 17 + src/test/regress/expected/json_sqljson.out | 15 + src/test/regress/expected/jsonb_sqljson.out | 988 ++++++++++++++++++ src/test/regress/expected/opr_sanity.out | 9 +- src/test/regress/expected/sqljson.out | 940 +++++++++++++++++ src/test/regress/parallel_schedule | 2 +- src/test/regress/serial_schedule | 3 + src/test/regress/sql/json_sqljson.sql | 11 + src/test/regress/sql/jsonb_sqljson.sql | 303 ++++++ src/test/regress/sql/opr_sanity.sql | 6 +- src/test/regress/sql/sqljson.sql | 378 +++++++ 46 files changed, 8332 insertions(+), 143 deletions(-) create mode 100644 src/test/regress/expected/json_sqljson.out create mode 100644 src/test/regress/expected/jsonb_sqljson.out create mode 100644 src/test/regress/expected/sqljson.out create mode 100644 src/test/regress/sql/json_sqljson.sql create mode 100644 src/test/regress/sql/jsonb_sqljson.sql create mode 100644 src/test/regress/sql/sqljson.sql diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index 7f770d2..832f7e3 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -2812,6 +2812,47 @@ JumbleExpr(pgssJumbleState *jstate, Node *node) JumbleExpr(jstate, (Node *) conf->exclRelTlist); } break; + case T_JsonValueExpr: + { + JsonValueExpr *expr = (JsonValueExpr *) node; + + JumbleExpr(jstate, (Node *) expr->expr); + APP_JUMB(expr->format.type); + APP_JUMB(expr->format.encoding); + } + break; + case T_JsonCtorOpts: + { + JsonCtorOpts *opts = (JsonCtorOpts *) node; + + APP_JUMB(opts->returning.format.type); + APP_JUMB(opts->returning.format.encoding); + APP_JUMB(opts->returning.typid); + APP_JUMB(opts->returning.typmod); + APP_JUMB(opts->unique); + APP_JUMB(opts->absent_on_null); + } + break; + case T_JsonIsPredicateOpts: + { + JsonIsPredicateOpts *opts = (JsonIsPredicateOpts *) node; + + APP_JUMB(opts->unique_keys); + APP_JUMB(opts->value_type); + } + break; + case T_JsonExpr: + { + JsonExpr *jexpr = (JsonExpr *) node; + + APP_JUMB(jexpr->op); + JumbleExpr(jstate, jexpr->raw_expr); + JumbleExpr(jstate, jexpr->path_spec); + JumbleExpr(jstate, (Node *) jexpr->passing.values); + JumbleExpr(jstate, jexpr->on_empty.default_expr); + JumbleExpr(jstate, jexpr->on_error.default_expr); + } + break; case T_List: foreach(temp, (List *) node) { diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index d9087ca..d8dac35 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -45,6 +45,7 @@ #include "pgstat.h" #include "utils/builtins.h" #include "utils/datum.h" +#include "utils/jsonpath.h" #include "utils/lsyscache.h" #include "utils/typcache.h" @@ -81,6 +82,40 @@ static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate, int transno, int setno, int setoff, bool ishash); +static ExprState * +ExecInitExprInternal(Expr *node, PlanState *parent, ParamListInfo ext_params, + Datum *caseval, bool *casenull) +{ + ExprState *state; + ExprEvalStep scratch = {0}; + + /* Special case: NULL expression produces a NULL ExprState pointer */ + if (node == NULL) + return NULL; + + /* Initialize ExprState with empty step list */ + state = makeNode(ExprState); + state->expr = node; + state->parent = parent; + state->ext_params = ext_params; + state->innermost_caseval = caseval; + state->innermost_casenull = casenull; + + /* Insert EEOP_*_FETCHSOME steps as needed */ + ExecInitExprSlots(state, (Node *) node); + + /* Compile the expression proper */ + ExecInitExprRec(node, state, &state->resvalue, &state->resnull); + + /* Finally, append a DONE step */ + scratch.opcode = EEOP_DONE; + ExprEvalPushStep(state, &scratch); + + ExecReadyExpr(state); + + return state; +} + /* * ExecInitExpr: prepare an expression tree for execution * @@ -119,32 +154,7 @@ static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate, ExprState * ExecInitExpr(Expr *node, PlanState *parent) { - ExprState *state; - ExprEvalStep scratch = {0}; - - /* Special case: NULL expression produces a NULL ExprState pointer */ - if (node == NULL) - return NULL; - - /* Initialize ExprState with empty step list */ - state = makeNode(ExprState); - state->expr = node; - state->parent = parent; - state->ext_params = NULL; - - /* Insert EEOP_*_FETCHSOME steps as needed */ - ExecInitExprSlots(state, (Node *) node); - - /* Compile the expression proper */ - ExecInitExprRec(node, state, &state->resvalue, &state->resnull); - - /* Finally, append a DONE step */ - scratch.opcode = EEOP_DONE; - ExprEvalPushStep(state, &scratch); - - ExecReadyExpr(state); - - return state; + return ExecInitExprInternal(node, parent, NULL, NULL, NULL); } /* @@ -156,32 +166,20 @@ ExecInitExpr(Expr *node, PlanState *parent) ExprState * ExecInitExprWithParams(Expr *node, ParamListInfo ext_params) { - ExprState *state; - ExprEvalStep scratch = {0}; - - /* Special case: NULL expression produces a NULL ExprState pointer */ - if (node == NULL) - return NULL; - - /* Initialize ExprState with empty step list */ - state = makeNode(ExprState); - state->expr = node; - state->parent = NULL; - state->ext_params = ext_params; - - /* Insert EEOP_*_FETCHSOME steps as needed */ - ExecInitExprSlots(state, (Node *) node); - - /* Compile the expression proper */ - ExecInitExprRec(node, state, &state->resvalue, &state->resnull); - - /* Finally, append a DONE step */ - scratch.opcode = EEOP_DONE; - ExprEvalPushStep(state, &scratch); - - ExecReadyExpr(state); + return ExecInitExprInternal(node, NULL, ext_params, NULL, NULL); +} - return state; +/* + * ExecInitExprWithCaseValue: prepare an expression tree for execution + * + * This is the same as ExecInitExpr, except that a pointer to the value for + * CasTestExpr is passed here. + */ +ExprState * +ExecInitExprWithCaseValue(Expr *node, PlanState *parent, + Datum *caseval, bool *casenull) +{ + return ExecInitExprInternal(node, parent, NULL, caseval, casenull); } /* @@ -2115,6 +2113,126 @@ ExecInitExprRec(Expr *node, ExprState *state, break; } + case T_JsonValueExpr: + ExecInitExprRec(((JsonValueExpr *) node)->expr, state, resv, + resnull); + break; + + case T_JsonExpr: + { + JsonExpr *jexpr = castNode(JsonExpr, node); + ListCell *argexprlc; + ListCell *argnamelc; + + scratch.opcode = EEOP_JSONEXPR; + scratch.d.jsonexpr.jsexpr = jexpr; + + scratch.d.jsonexpr.raw_expr = + palloc(sizeof(*scratch.d.jsonexpr.raw_expr)); + + ExecInitExprRec((Expr *) jexpr->raw_expr, state, + &scratch.d.jsonexpr.raw_expr->value, + &scratch.d.jsonexpr.raw_expr->isnull); + + scratch.d.jsonexpr.pathspec = + palloc(sizeof(*scratch.d.jsonexpr.pathspec)); + + ExecInitExprRec((Expr *) jexpr->path_spec, state, + &scratch.d.jsonexpr.pathspec->value, + &scratch.d.jsonexpr.pathspec->isnull); + + scratch.d.jsonexpr.formatted_expr = + ExecInitExprWithCaseValue((Expr *) jexpr->formatted_expr, + state->parent, + &scratch.d.jsonexpr.raw_expr->value, + &scratch.d.jsonexpr.raw_expr->isnull); + + scratch.d.jsonexpr.res_expr = + palloc(sizeof(*scratch.d.jsonexpr.res_expr)); + + + scratch.d.jsonexpr.result_expr = jexpr->result_coercion + ? ExecInitExprWithCaseValue((Expr *) jexpr->result_coercion->expr, + state->parent, + &scratch.d.jsonexpr.res_expr->value, + &scratch.d.jsonexpr.res_expr->isnull) + : NULL; + + scratch.d.jsonexpr.default_on_empty = + ExecInitExpr((Expr *) jexpr->on_empty.default_expr, + state->parent); + + scratch.d.jsonexpr.default_on_error = + ExecInitExpr((Expr *) jexpr->on_error.default_expr, + state->parent); + + if (jexpr->omit_quotes || + (jexpr->result_coercion && jexpr->result_coercion->via_io)) + { + Oid typinput; + + /* lookup the result type's input function */ + getTypeInputInfo(jexpr->returning.typid, &typinput, + &scratch.d.jsonexpr.input.typioparam); + fmgr_info(typinput, &scratch.d.jsonexpr.input.func); + } + + scratch.d.jsonexpr.args = NIL; + + forboth(argexprlc, jexpr->passing.values, + argnamelc, jexpr->passing.names) + { + Expr *argexpr = (Expr *) lfirst(argexprlc); + Value *argname = (Value *) lfirst(argnamelc); + JsonPathVariableEvalContext *var = palloc(sizeof(*var)); + + var->var.varName = cstring_to_text(argname->val.str); + var->var.typid = exprType((Node *) argexpr); + var->var.typmod = exprTypmod((Node *) argexpr); + var->var.cb = EvalJsonPathVar; + var->var.cb_arg = var; + var->estate = ExecInitExpr(argexpr, state->parent); + var->econtext = NULL; + var->evaluated = false; + var->value = (Datum) 0; + var->isnull = true; + + scratch.d.jsonexpr.args = + lappend(scratch.d.jsonexpr.args, var); + } + + scratch.d.jsonexpr.cache = NULL; + + if (jexpr->coercions) + { + JsonCoercion **coercion; + struct JsonCoercionState *cstate; + Datum *caseval; + bool *casenull; + + scratch.d.jsonexpr.coercion_expr = + palloc(sizeof(*scratch.d.jsonexpr.coercion_expr)); + + caseval = &scratch.d.jsonexpr.coercion_expr->value; + casenull = &scratch.d.jsonexpr.coercion_expr->isnull; + + for (cstate = &scratch.d.jsonexpr.coercions.null, + coercion = &jexpr->coercions->null; + coercion <= &jexpr->coercions->composite; + coercion++, cstate++) + { + cstate->coercion = *coercion; + cstate->estate = *coercion ? + ExecInitExprWithCaseValue((Expr *)(*coercion)->expr, + state->parent, + caseval, casenull) : NULL; + } + } + + ExprEvalPushStep(state, &scratch); + } + 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 ec4a250..b9cc889 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -57,6 +57,8 @@ #include "postgres.h" #include "access/tuptoaster.h" +#include "access/xact.h" +#include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "commands/sequence.h" #include "executor/execExpr.h" @@ -64,14 +66,20 @@ #include "funcapi.h" #include "utils/memutils.h" #include "miscadmin.h" +#include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "parser/parsetree.h" +#include "parser/parse_expr.h" #include "pgstat.h" #include "utils/builtins.h" #include "utils/date.h" #include "utils/datum.h" #include "utils/expandedrecord.h" +#include "utils/jsonapi.h" +#include "utils/jsonb.h" +#include "utils/jsonpath.h" #include "utils/lsyscache.h" +#include "utils/resowner.h" #include "utils/timestamp.h" #include "utils/typcache.h" #include "utils/xml.h" @@ -385,6 +393,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) &&CASE_EEOP_WINDOW_FUNC, &&CASE_EEOP_SUBPLAN, &&CASE_EEOP_ALTERNATIVE_SUBPLAN, + &&CASE_EEOP_JSONEXPR, &&CASE_EEOP_AGG_STRICT_DESERIALIZE, &&CASE_EEOP_AGG_DESERIALIZE, &&CASE_EEOP_AGG_STRICT_INPUT_CHECK, @@ -1718,7 +1727,13 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) { /* too complex for an inline implementation */ ExecEvalAggOrderedTransTuple(state, op, econtext); + EEO_NEXT(); + } + EEO_CASE(EEOP_JSONEXPR) + { + /* too complex for an inline implementation */ + ExecEvalJson(state, op, econtext); EEO_NEXT(); } @@ -4129,3 +4144,407 @@ ExecEvalAggOrderedTransTuple(ExprState *state, ExprEvalStep *op, ExecStoreVirtualTuple(pertrans->sortslot); tuplesort_puttupleslot(pertrans->sortstates[setno], pertrans->sortslot); } + +/* + * Evaluate a JSON error/empty behavior result. + */ +static Datum +ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior, + ExprState *default_estate, bool *is_null) +{ + *is_null = false; + + switch (behavior->btype) + { + case JSON_BEHAVIOR_EMPTY_ARRAY: + return JsonbPGetDatum(JsonbMakeEmptyArray()); + + case JSON_BEHAVIOR_EMPTY_OBJECT: + return JsonbPGetDatum(JsonbMakeEmptyObject()); + + case JSON_BEHAVIOR_TRUE: + return BoolGetDatum(true); + + case JSON_BEHAVIOR_FALSE: + return BoolGetDatum(false); + + case JSON_BEHAVIOR_NULL: + case JSON_BEHAVIOR_UNKNOWN: + *is_null = true; + return (Datum) 0; + + case JSON_BEHAVIOR_DEFAULT: + return ExecEvalExpr(default_estate, econtext, is_null); + + default: + elog(ERROR, "unrecognized SQL/JSON behavior %d", behavior->btype); + return (Datum) 0; + } +} + +/* + * Evaluate a coercion of a JSON item to the target type. + */ +static Datum +ExecEvalJsonExprCoercion(ExprEvalStep *op, ExprContext *econtext, + Datum res, bool *isNull) +{ + JsonExpr *jexpr = op->d.jsonexpr.jsexpr; + JsonCoercion *coercion = jexpr->result_coercion; + Jsonb *jb = *isNull ? NULL : DatumGetJsonbP(res); + + if ((coercion && coercion->via_io) || + (jexpr->omit_quotes && !*isNull && JB_ROOT_IS_SCALAR(jb))) + { + /* strip quotes and call typinput function */ + char *str = *isNull ? NULL : JsonbUnquote(jb); + + res = InputFunctionCall(&op->d.jsonexpr.input.func, str, + op->d.jsonexpr.input.typioparam, + jexpr->returning.typmod); + } + else if (op->d.jsonexpr.result_expr) + { + op->d.jsonexpr.res_expr->value = res; + op->d.jsonexpr.res_expr->isnull = *isNull; + + res = ExecEvalExpr(op->d.jsonexpr.result_expr, econtext, isNull); + } + else if (coercion && coercion->via_populate) + res = json_populate_type(res, JSONBOID, + jexpr->returning.typid, + jexpr->returning.typmod, + &op->d.jsonexpr.cache, + econtext->ecxt_per_query_memory, + isNull); + /* else no coercion, simply return item */ + + return res; +} + +/* + * Evaluate a JSON path variable caching computed value. + */ +Datum +EvalJsonPathVar(void *cxt, bool *isnull) +{ + JsonPathVariableEvalContext *ecxt = cxt; + + if (!ecxt->evaluated) + { + ecxt->value = ExecEvalExpr(ecxt->estate, ecxt->econtext, &ecxt->isnull); + ecxt->evaluated = true; + } + + *isnull = ecxt->isnull; + return ecxt->value; +} + +/* + * Prepare SQL/JSON item coercion to the output type. Returned a datum of the + * corresponding SQL type and a pointer to the coercion state. + */ +Datum +ExecPrepareJsonItemCoercion(JsonbValue *item, + JsonReturning *returning, + struct JsonCoercionsState *coercions, + struct JsonCoercionState **pcoercion) +{ + struct JsonCoercionState *coercion; + Datum res; + JsonbValue jbvbuf; + + if (item->type == jbvBinary && JsonContainerIsScalar(item->val.binary.data)) + item = JsonbExtractScalar(item->val.binary.data, &jbvbuf); + + /* get coercion state reference and datum of the corresponding SQL type */ + switch (item->type) + { + case jbvNull: + coercion = &coercions->null; + res = (Datum) 0; + break; + + case jbvString: + coercion = &coercions->string; + res = PointerGetDatum( + cstring_to_text_with_len(item->val.string.val, + item->val.string.len)); + break; + + case jbvNumeric: + coercion = &coercions->numeric; + res = NumericGetDatum(item->val.numeric); + break; + + case jbvBool: + coercion = &coercions->boolean; + res = BoolGetDatum(item->val.boolean); + break; + + case jbvDatetime: + res = item->val.datetime.value; + switch (item->val.datetime.typid) + { + case DATEOID: + coercion = &coercions->date; + break; + case TIMEOID: + coercion = &coercions->time; + break; + case TIMETZOID: + coercion = &coercions->timetz; + break; + case TIMESTAMPOID: + coercion = &coercions->timestamp; + break; + case TIMESTAMPTZOID: + coercion = &coercions->timestamptz; + break; + default: + elog(ERROR, "unexpected jsonb datetime type oid %d", + item->val.datetime.typid); + return (Datum) 0; + } + break; + + case jbvArray: + case jbvObject: + case jbvBinary: + coercion = &coercions->composite; + res = JsonbPGetDatum(JsonbValueToJsonb(item)); + break; + + default: + elog(ERROR, "unexpected jsonb value type %d", item->type); + return (Datum) 0; + } + + *pcoercion = coercion; + + return res; +} + +static Datum +ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext, + JsonExpr *jexpr, JsonPath *path, Datum item, bool *resnull) +{ + bool empty = false; + Datum res = (Datum) 0; + + if (op->d.jsonexpr.formatted_expr) + { + bool isnull; + + op->d.jsonexpr.raw_expr->value = item; + op->d.jsonexpr.raw_expr->isnull = false; + + item = ExecEvalExpr(op->d.jsonexpr.formatted_expr, econtext, &isnull); + if (isnull) + { + /* execute domain checks for NULLs */ + (void) ExecEvalJsonExprCoercion(op, econtext, res, resnull); + *resnull = true; + return (Datum) 0; + } + } + + switch (jexpr->op) + { + case IS_JSON_QUERY: + res = JsonbPathQuery(item, path, jexpr->wrapper, &empty, + op->d.jsonexpr.args); + *resnull = !DatumGetPointer(res); + break; + + case IS_JSON_VALUE: + { + JsonbValue *jbv = JsonbPathValue(item, path, &empty, + op->d.jsonexpr.args); + struct JsonCoercionState *jcstate; + + if (!jbv) /* NULL or empty */ + break; + + Assert(!empty); + + *resnull = false; + + /* coerce item datum to the output type */ + if (jexpr->returning.typid == JSONOID || + jexpr->returning.typid == JSONBOID) + { + /* Use result coercion from json[b] to the output type */ + res = JsonbPGetDatum(JsonbValueToJsonb(jbv)); + break; + } + + /* Use coercion from SQL/JSON item type to the output type */ + res = ExecPrepareJsonItemCoercion(jbv, + &op->d.jsonexpr.jsexpr->returning, + &op->d.jsonexpr.coercions, + &jcstate); + + if (jcstate->coercion && + (jcstate->coercion->via_io || + jcstate->coercion->via_populate)) + { + /* + * Coercion via I/O means here that the cast to the target + * type simply does not exist. + */ + ereport(ERROR, + /* + * XXX Standard says about a separate error code + * ERRCODE_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE + * but does not define its number. + */ + (errcode(ERRCODE_JSON_SCALAR_REQUIRED), + errmsg("SQL/JSON item cannot be cast to target type"))); + } + else if (jcstate->estate) + { + op->d.jsonexpr.coercion_expr->value = res; + op->d.jsonexpr.coercion_expr->isnull = false; + + res = ExecEvalExpr(jcstate->estate, econtext, resnull); + } + /* else no coercion */ + + return res; + } + + case IS_JSON_EXISTS: + *resnull = false; + return BoolGetDatum(JsonbPathExists(item, path, op->d.jsonexpr.args)); + + default: + elog(ERROR, "unrecognized SQL/JSON expression op %d", jexpr->op); + return (Datum) 0; + } + + if (empty) + { + if (jexpr->on_empty.btype == JSON_BEHAVIOR_ERROR) + ereport(ERROR, + (errcode(ERRCODE_NO_JSON_ITEM), + errmsg("no SQL/JSON item"))); + + /* execute ON EMPTY behavior */ + res = ExecEvalJsonBehavior(econtext, &jexpr->on_empty, + op->d.jsonexpr.default_on_empty, resnull); + + /* result is already coerced in DEFAULT behavior case */ + if (jexpr->on_empty.btype == JSON_BEHAVIOR_DEFAULT) + return res; + } + + return ExecEvalJsonExprCoercion(op, econtext, res, resnull); +} + +bool +ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr) +{ + return jsexpr->on_error.btype != JSON_BEHAVIOR_ERROR; +} + +/* ---------------------------------------------------------------- + * ExecEvalJson + * ---------------------------------------------------------------- + */ +void +ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext) +{ + JsonExpr *jexpr = op->d.jsonexpr.jsexpr; + Datum item; + Datum res = (Datum) 0; + JsonPath *path; + ListCell *lc; + + *op->resnull = true; /* until we get a result */ + *op->resvalue = (Datum) 0; + + if (op->d.jsonexpr.raw_expr->isnull || op->d.jsonexpr.pathspec->isnull) + { + /* execute domain checks for NULLs */ + (void) ExecEvalJsonExprCoercion(op, econtext, res, op->resnull); + + Assert(*op->resnull); + *op->resnull = true; + + return; + } + + item = op->d.jsonexpr.raw_expr->value; + path = DatumGetJsonPathP(op->d.jsonexpr.pathspec->value); + + /* reset JSON path variable contexts */ + foreach(lc, op->d.jsonexpr.args) + { + JsonPathVariableEvalContext *var = lfirst(lc); + + var->econtext = econtext; + var->evaluated = false; + } + + if (!ExecEvalJsonNeedsSubTransaction(jexpr)) + { + /* No need to use PG_TRY/PG_CATCH with subtransactions. */ + res = ExecEvalJsonExpr(state, op, econtext, jexpr, path, item, + op->resnull); + } + else + { + /* + * We should catch exceptions of category ERRCODE_DATA_EXCEPTION and + * execute corresponding ON ERROR behavior. + */ + MemoryContext oldcontext = CurrentMemoryContext; + ResourceOwner oldowner = CurrentResourceOwner; + + BeginInternalSubTransaction(NULL); + /* Want to execute expressions inside function's memory context */ + MemoryContextSwitchTo(oldcontext); + + PG_TRY(); + { + res = ExecEvalJsonExpr(state, op, econtext, jexpr, path, item, + op->resnull); + + /* Commit the inner transaction, return to outer xact context */ + ReleaseCurrentSubTransaction(); + MemoryContextSwitchTo(oldcontext); + CurrentResourceOwner = oldowner; + } + PG_CATCH(); + { + ErrorData *edata; + + /* Save error info in oldcontext */ + MemoryContextSwitchTo(oldcontext); + edata = CopyErrorData(); + FlushErrorState(); + + /* Abort the inner transaction */ + RollbackAndReleaseCurrentSubTransaction(); + MemoryContextSwitchTo(oldcontext); + CurrentResourceOwner = oldowner; + + if (ERRCODE_TO_CATEGORY(edata->sqlerrcode) != ERRCODE_DATA_EXCEPTION) + ReThrowError(edata); + + /* Execute ON ERROR behavior. */ + res = ExecEvalJsonBehavior(econtext, &jexpr->on_error, + op->d.jsonexpr.default_on_error, + op->resnull); + + if (jexpr->op != IS_JSON_EXISTS && + /* result is already coerced in DEFAULT behavior case */ + jexpr->on_error.btype != JSON_BEHAVIOR_DEFAULT) + res = ExecEvalJsonExprCoercion(op, econtext, res, op->resnull); + } + PG_END_TRY(); + } + + *op->resvalue = res; +} diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c index 1725f6d..a7daf1d 100644 --- a/src/backend/jit/llvm/llvmjit_expr.c +++ b/src/backend/jit/llvm/llvmjit_expr.c @@ -2496,6 +2496,13 @@ llvm_compile_expr(ExprState *state) LLVMBuildBr(b, opblocks[i + 1]); break; + + case EEOP_JSONEXPR: + build_EvalXFunc(b, mod, "ExecEvalJson", + v_state, v_econtext, op); + LLVMBuildBr(b, opblocks[i + 1]); + break; + case EEOP_LAST: Assert(false); break; diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 30c234e..175645c 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2201,6 +2201,319 @@ _copyOnConflictExpr(const OnConflictExpr *from) return newnode; } +/* + * _copyJsonValueExpr + */ +static JsonValueExpr * +_copyJsonValueExpr(const JsonValueExpr *from) +{ + JsonValueExpr *newnode = makeNode(JsonValueExpr); + + COPY_NODE_FIELD(expr); + COPY_SCALAR_FIELD(format); + + return newnode; +} + +/* + * _copyJsonKeyValue + */ +static JsonKeyValue * +_copyJsonKeyValue(const JsonKeyValue *from) +{ + JsonKeyValue *newnode = makeNode(JsonKeyValue); + + COPY_NODE_FIELD(key); + COPY_NODE_FIELD(value); + + return newnode; +} + +/* + * _copyJsonObjectCtor + */ +static JsonObjectCtor * +_copyJsonObjectCtor(const JsonObjectCtor *from) +{ + JsonObjectCtor *newnode = makeNode(JsonObjectCtor); + + COPY_NODE_FIELD(exprs); + COPY_NODE_FIELD(output); + COPY_SCALAR_FIELD(absent_on_null); + COPY_SCALAR_FIELD(unique); + COPY_LOCATION_FIELD(location); + + return newnode; +} + +/* + * _copyJsonCtorOpts + */ +static JsonCtorOpts * +_copyJsonCtorOpts(const JsonCtorOpts *from) +{ + JsonCtorOpts *newnode = makeNode(JsonCtorOpts); + + COPY_SCALAR_FIELD(returning.format.type); + COPY_SCALAR_FIELD(returning.format.encoding); + COPY_LOCATION_FIELD(returning.format.location); + COPY_SCALAR_FIELD(returning.typid); + COPY_SCALAR_FIELD(returning.typmod); + COPY_SCALAR_FIELD(unique); + COPY_SCALAR_FIELD(absent_on_null); + + return newnode; +} + +/* + * _copyJsonObjectAgg + */ +static JsonObjectAgg * +_copyJsonObjectAgg(const JsonObjectAgg *from) +{ + JsonObjectAgg *newnode = makeNode(JsonObjectAgg); + + COPY_NODE_FIELD(ctor.output); + COPY_NODE_FIELD(ctor.agg_filter); + COPY_NODE_FIELD(ctor.agg_order); + COPY_NODE_FIELD(ctor.over); + COPY_LOCATION_FIELD(ctor.location); + 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_SCALAR_FIELD(returning); + + return newnode; +} + +/* + * _copyJsonArrayCtor + */ +static JsonArrayCtor * +_copyJsonArrayCtor(const JsonArrayCtor *from) +{ + JsonArrayCtor *newnode = makeNode(JsonArrayCtor); + + 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(ctor.output); + COPY_NODE_FIELD(ctor.agg_filter); + COPY_NODE_FIELD(ctor.agg_order); + COPY_NODE_FIELD(ctor.over); + COPY_LOCATION_FIELD(ctor.location); + COPY_NODE_FIELD(arg); + COPY_SCALAR_FIELD(absent_on_null); + + return newnode; +} + +/* + * _copyJsonArrayQueryCtor + */ +static JsonArrayQueryCtor * +_copyJsonArrayQueryCtor(const JsonArrayQueryCtor *from) +{ + JsonArrayQueryCtor *newnode = makeNode(JsonArrayQueryCtor); + + COPY_NODE_FIELD(query); + COPY_NODE_FIELD(output); + COPY_SCALAR_FIELD(format); + COPY_SCALAR_FIELD(absent_on_null); + COPY_LOCATION_FIELD(location); + + return newnode; +} + +/* + * _copyJsonExpr + */ +static JsonExpr * +_copyJsonExpr(const JsonExpr *from) +{ + JsonExpr *newnode = makeNode(JsonExpr); + + COPY_SCALAR_FIELD(op); + COPY_NODE_FIELD(raw_expr); + COPY_NODE_FIELD(formatted_expr); + COPY_NODE_FIELD(result_coercion); + COPY_SCALAR_FIELD(format); + COPY_NODE_FIELD(path_spec); + COPY_NODE_FIELD(passing.values); + COPY_NODE_FIELD(passing.names); + COPY_SCALAR_FIELD(returning); + COPY_SCALAR_FIELD(on_error); + COPY_NODE_FIELD(on_error.default_expr); + COPY_SCALAR_FIELD(on_empty); + COPY_NODE_FIELD(on_empty.default_expr); + COPY_NODE_FIELD(coercions); + COPY_SCALAR_FIELD(wrapper); + COPY_SCALAR_FIELD(omit_quotes); + COPY_LOCATION_FIELD(location); + + return newnode; +} + +/* + * _copyJsonCoercion + */ +static JsonCoercion * +_copyJsonCoercion(const JsonCoercion *from) +{ + JsonCoercion *newnode = makeNode(JsonCoercion); + + COPY_NODE_FIELD(expr); + COPY_SCALAR_FIELD(via_populate); + COPY_SCALAR_FIELD(via_io); + COPY_SCALAR_FIELD(collation); + + return newnode; +} + +/* + * _copylJsonItemCoercions + */ +static JsonItemCoercions * +_copyJsonItemCoercions(const JsonItemCoercions *from) +{ + JsonItemCoercions *newnode = makeNode(JsonItemCoercions); + + COPY_NODE_FIELD(null); + COPY_NODE_FIELD(string); + COPY_NODE_FIELD(numeric); + COPY_NODE_FIELD(boolean); + COPY_NODE_FIELD(date); + COPY_NODE_FIELD(time); + COPY_NODE_FIELD(timetz); + COPY_NODE_FIELD(timestamp); + COPY_NODE_FIELD(timestamptz); + COPY_NODE_FIELD(composite); + + return newnode; +} + + +/* + * _copyJsonFuncExpr + */ +static JsonFuncExpr * +_copyJsonFuncExpr(const JsonFuncExpr *from) +{ + JsonFuncExpr *newnode = makeNode(JsonFuncExpr); + + COPY_SCALAR_FIELD(op); + COPY_NODE_FIELD(common); + COPY_NODE_FIELD(output); + COPY_NODE_FIELD(on_empty); + COPY_NODE_FIELD(on_error); + COPY_SCALAR_FIELD(wrapper); + COPY_SCALAR_FIELD(omit_quotes); + COPY_LOCATION_FIELD(location); + + return newnode; +} + +/* + * _copyJsonIsPredicate + */ +static JsonIsPredicate * +_copyJsonIsPredicate(const JsonIsPredicate *from) +{ + JsonIsPredicate *newnode = makeNode(JsonIsPredicate); + + COPY_NODE_FIELD(expr); + COPY_SCALAR_FIELD(format); + COPY_SCALAR_FIELD(vtype); + COPY_SCALAR_FIELD(unique_keys); + + return newnode; +} + +/* + * _copyJsonIsPredicateOpts + */ +static JsonIsPredicateOpts * +_copyJsonIsPredicateOpts(const JsonIsPredicateOpts *from) +{ + JsonIsPredicateOpts *newnode = makeNode(JsonIsPredicateOpts); + + COPY_SCALAR_FIELD(value_type); + COPY_SCALAR_FIELD(unique_keys); + + return newnode; +} + +/* + * _copyJsonBehavior + */ +static JsonBehavior * +_copyJsonBehavior(const JsonBehavior *from) +{ + JsonBehavior *newnode = makeNode(JsonBehavior); + + COPY_SCALAR_FIELD(btype); + COPY_NODE_FIELD(default_expr); + + return newnode; +} + +/* + * _copyJsonCommon + */ +static JsonCommon * +_copyJsonCommon(const JsonCommon *from) +{ + JsonCommon *newnode = makeNode(JsonCommon); + + COPY_NODE_FIELD(expr); + COPY_NODE_FIELD(pathspec); + COPY_STRING_FIELD(pathname); + COPY_NODE_FIELD(passing); + COPY_LOCATION_FIELD(location); + + return newnode; +} + +/* + * _copyJsonArgument + */ +static JsonArgument * +_copyJsonArgument(const JsonArgument *from) +{ + JsonArgument *newnode = makeNode(JsonArgument); + + COPY_NODE_FIELD(val); + COPY_STRING_FIELD(name); + + return newnode; +} + /* **************************************************************** * relation.h copy functions * @@ -5092,6 +5405,60 @@ copyObjectImpl(const void *from) case T_OnConflictExpr: retval = _copyOnConflictExpr(from); break; + case T_JsonValueExpr: + retval = _copyJsonValueExpr(from); + break; + case T_JsonKeyValue: + retval = _copyJsonKeyValue(from); + break; + case T_JsonCtorOpts: + retval = _copyJsonCtorOpts(from); + break; + case T_JsonObjectCtor: + retval = _copyJsonObjectCtor(from); + break; + case T_JsonObjectAgg: + retval = _copyJsonObjectAgg(from); + break; + case T_JsonOutput: + retval = _copyJsonOutput(from); + break; + case T_JsonArrayCtor: + retval = _copyJsonArrayCtor(from); + break; + case T_JsonArrayQueryCtor: + retval = _copyJsonArrayQueryCtor(from); + break; + case T_JsonArrayAgg: + retval = _copyJsonArrayAgg(from); + break; + case T_JsonIsPredicate: + retval = _copyJsonIsPredicate(from); + break; + case T_JsonIsPredicateOpts: + retval = _copyJsonIsPredicateOpts(from); + break; + case T_JsonFuncExpr: + retval = _copyJsonFuncExpr(from); + break; + case T_JsonExpr: + retval = _copyJsonExpr(from); + break; + case T_JsonCommon: + retval = _copyJsonCommon(from); + break; + case T_JsonBehavior: + retval = _copyJsonBehavior(from); + break; + case T_JsonArgument: + retval = _copyJsonArgument(from); + break; + case T_JsonCoercion: + retval = _copyJsonCoercion(from); + break; + case T_JsonItemCoercions: + retval = _copyJsonItemCoercions(from); + break; /* * RELATION NODES diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index edfcb78..190e48e 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -818,6 +818,108 @@ _equalOnConflictExpr(const OnConflictExpr *a, const OnConflictExpr *b) return true; } +static bool +_equalJsonValueExpr(const JsonValueExpr *a, const JsonValueExpr *b) +{ + COMPARE_NODE_FIELD(expr); + COMPARE_SCALAR_FIELD(format.type); + COMPARE_SCALAR_FIELD(format.encoding); + COMPARE_LOCATION_FIELD(format.location); + + return true; +} + +static bool +_equalJsonCtorOpts(const JsonCtorOpts *a, const JsonCtorOpts *b) +{ + COMPARE_SCALAR_FIELD(returning.format.type); + COMPARE_SCALAR_FIELD(returning.format.encoding); + COMPARE_LOCATION_FIELD(returning.format.location); + COMPARE_SCALAR_FIELD(returning.typid); + COMPARE_SCALAR_FIELD(returning.typmod); + COMPARE_SCALAR_FIELD(absent_on_null); + COMPARE_SCALAR_FIELD(unique); + + return true; +} + +static bool +_equalJsonIsPredicateOpts(const JsonIsPredicateOpts *a, + const JsonIsPredicateOpts *b) +{ + COMPARE_SCALAR_FIELD(value_type); + COMPARE_SCALAR_FIELD(unique_keys); + + return true; +} + +/* + * _equalJsonExpr + */ +static bool +_equalJsonExpr(const JsonExpr *a, const JsonExpr *b) +{ + COMPARE_SCALAR_FIELD(op); + COMPARE_NODE_FIELD(raw_expr); + COMPARE_NODE_FIELD(formatted_expr); + COMPARE_NODE_FIELD(result_coercion); + COMPARE_SCALAR_FIELD(format.type); + COMPARE_SCALAR_FIELD(format.encoding); + COMPARE_LOCATION_FIELD(format.location); + COMPARE_NODE_FIELD(path_spec); + COMPARE_NODE_FIELD(passing.values); + COMPARE_NODE_FIELD(passing.names); + COMPARE_SCALAR_FIELD(returning.format.type); + COMPARE_SCALAR_FIELD(returning.format.encoding); + COMPARE_LOCATION_FIELD(returning.format.location); + COMPARE_SCALAR_FIELD(returning.typid); + COMPARE_SCALAR_FIELD(returning.typmod); + COMPARE_SCALAR_FIELD(on_error.btype); + COMPARE_NODE_FIELD(on_error.default_expr); + COMPARE_SCALAR_FIELD(on_empty.btype); + COMPARE_NODE_FIELD(on_empty.default_expr); + COMPARE_NODE_FIELD(coercions); + COMPARE_SCALAR_FIELD(wrapper); + COMPARE_SCALAR_FIELD(omit_quotes); + COMPARE_LOCATION_FIELD(location); + + return true; +} + +/* + * _equalJsonCoercion + */ +static bool +_equalJsonCoercion(const JsonCoercion *a, const JsonCoercion *b) +{ + COMPARE_NODE_FIELD(expr); + COMPARE_SCALAR_FIELD(via_populate); + COMPARE_SCALAR_FIELD(via_io); + COMPARE_SCALAR_FIELD(collation); + + return true; +} + +/* + * _equalJsonItemCoercions + */ +static bool +_equalJsonItemCoercions(const JsonItemCoercions *a, const JsonItemCoercions *b) +{ + COMPARE_NODE_FIELD(null); + COMPARE_NODE_FIELD(string); + COMPARE_NODE_FIELD(numeric); + COMPARE_NODE_FIELD(boolean); + COMPARE_NODE_FIELD(date); + COMPARE_NODE_FIELD(time); + COMPARE_NODE_FIELD(timetz); + COMPARE_NODE_FIELD(timestamp); + COMPARE_NODE_FIELD(timestamptz); + COMPARE_NODE_FIELD(composite); + + return true; +} + /* * Stuff from relation.h */ @@ -3166,6 +3268,24 @@ equal(const void *a, const void *b) case T_JoinExpr: retval = _equalJoinExpr(a, b); break; + case T_JsonValueExpr: + retval = _equalJsonValueExpr(a, b); + break; + case T_JsonCtorOpts: + retval = _equalJsonCtorOpts(a, b); + break; + case T_JsonIsPredicateOpts: + retval = _equalJsonIsPredicateOpts(a, b); + break; + case T_JsonExpr: + retval = _equalJsonExpr(a, b); + break; + case T_JsonCoercion: + retval = _equalJsonCoercion(a, b); + break; + case T_JsonItemCoercions: + retval = _equalJsonItemCoercions(a, b); + break; /* * RELATION NODES diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c index 4a2e669..a1e825e 100644 --- a/src/backend/nodes/makefuncs.c +++ b/src/backend/nodes/makefuncs.c @@ -20,6 +20,7 @@ #include "fmgr.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "utils/errcodes.h" #include "utils/lsyscache.h" @@ -627,3 +628,87 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols) v->va_cols = va_cols; return v; } + +/* + * makeJsonValueExpr - + * creates a JsonValueExpr node + */ +JsonValueExpr * +makeJsonValueExpr(Expr *expr, JsonFormat format) +{ + JsonValueExpr *jve = makeNode(JsonValueExpr); + + jve->expr = expr; + jve->format = format; + + return jve; +} + +/* + * makeJsonBehavior - + * creates a JsonBehavior node + */ +JsonBehavior * +makeJsonBehavior(JsonBehaviorType type, Node *default_expr) +{ + JsonBehavior *behavior = makeNode(JsonBehavior); + + behavior->btype = type; + behavior->default_expr = default_expr; + + return behavior; +} + +/* + * makeJsonEncoding - + * converts JSON encoding name to enum JsonEncoding + */ +JsonEncoding +makeJsonEncoding(char *name) +{ + if (!pg_strcasecmp(name, "utf8")) + return JS_ENC_UTF8; + if (!pg_strcasecmp(name, "utf16")) + return JS_ENC_UTF16; + if (!pg_strcasecmp(name, "utf32")) + return JS_ENC_UTF32; + + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized JSON encoding: %s", 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; +} + +/* + * makeJsonIsPredicate - + * creates a JsonIsPredicate node + */ +Node * +makeJsonIsPredicate(Node *expr, JsonFormat format, JsonValueType vtype, + bool unique_keys) +{ + JsonIsPredicate *n = makeNode(JsonIsPredicate); + + n->expr = expr; + n->format = format; + n->vtype = vtype; + n->unique_keys = unique_keys; + + return (Node *) n; +} diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index a10014f..b158d12 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -259,6 +259,15 @@ exprType(const Node *expr) case T_PlaceHolderVar: type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr); break; + case T_JsonValueExpr: + type = exprType((Node *) ((const JsonValueExpr *) expr)->expr); + break; + case T_JsonExpr: + type = ((const JsonExpr *) expr)->returning.typid; + break; + case T_JsonCoercion: + type = exprType(((const JsonCoercion *) expr)->expr); + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); type = InvalidOid; /* keep compiler quiet */ @@ -492,6 +501,12 @@ exprTypmod(const Node *expr) return ((const SetToDefault *) expr)->typeMod; case T_PlaceHolderVar: return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr); + case T_JsonValueExpr: + return exprTypmod((Node *) ((const JsonValueExpr *) expr)->expr); + case T_JsonExpr: + return ((JsonExpr *) expr)->returning.typmod; + case T_JsonCoercion: + return exprTypmod(((const JsonCoercion *) expr)->expr); default: break; } @@ -903,6 +918,24 @@ exprCollation(const Node *expr) case T_PlaceHolderVar: coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr); break; + case T_JsonValueExpr: + coll = exprCollation((Node *) ((const JsonValueExpr *) expr)->expr); + break; + case T_JsonExpr: + { + JsonExpr *jexpr = (JsonExpr *) expr; + JsonCoercion *coercion = jexpr->result_coercion; + + if (!coercion) + coll = InvalidOid; + else if (coercion->expr) + coll = exprCollation(coercion->expr); + else if (coercion->via_io || coercion->via_populate) + coll = coercion->collation; + else + coll = InvalidOid; + } + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); coll = InvalidOid; /* keep compiler quiet */ @@ -1104,6 +1137,25 @@ exprSetCollation(Node *expr, Oid collation) Assert(!OidIsValid(collation)); /* result is always an integer * type */ break; + case T_JsonValueExpr: + exprSetCollation((Node *) ((const JsonValueExpr *) expr)->expr, + collation); + break; + case T_JsonExpr: + { + JsonExpr *jexpr = (JsonExpr *) expr; + JsonCoercion *coercion = jexpr->result_coercion; + + if (!coercion) + Assert(!OidIsValid(collation)); + else if (coercion->expr) + exprSetCollation(coercion->expr, collation); + else if (coercion->via_io || coercion->via_populate) + coercion->collation = collation; + else + Assert(!OidIsValid(collation)); + } + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); break; @@ -1544,6 +1596,18 @@ exprLocation(const Node *expr) case T_PartitionRangeDatum: loc = ((const PartitionRangeDatum *) expr)->location; break; + case T_JsonValueExpr: + loc = exprLocation((Node *) ((const JsonValueExpr *) expr)->expr); + break; + case T_JsonExpr: + { + const JsonExpr *jsexpr = (const JsonExpr *) expr; + + /* consider both function name and leftmost arg */ + loc = leftmostLoc(jsexpr->location, + exprLocation(jsexpr->raw_expr)); + } + break; default: /* for any other node type it's just unknown... */ loc = -1; @@ -2229,6 +2293,57 @@ expression_tree_walker(Node *node, return true; } break; + case T_JsonValueExpr: + return walker(((JsonValueExpr *) node)->expr, context); + case T_JsonExpr: + { + JsonExpr *jexpr = (JsonExpr *) node; + + if (walker(jexpr->raw_expr, context)) + return true; + if (walker(jexpr->formatted_expr, context)) + return true; + if (walker(jexpr->result_coercion, context)) + return true; + if (walker(jexpr->passing.values, context)) + return true; + /* we assume walker doesn't care about passing.names */ + if (walker(jexpr->on_empty.default_expr, context)) + return true; + if (walker(jexpr->on_error.default_expr, context)) + return true; + if (walker(jexpr->coercions, context)) + return true; + } + break; + case T_JsonCoercion: + return walker(((JsonCoercion *) node)->expr, context); + case T_JsonItemCoercions: + { + JsonItemCoercions *coercions = (JsonItemCoercions *) node; + + if (walker(coercions->null, context)) + return true; + if (walker(coercions->string, context)) + return true; + if (walker(coercions->numeric, context)) + return true; + if (walker(coercions->boolean, context)) + return true; + if (walker(coercions->date, context)) + return true; + if (walker(coercions->time, context)) + return true; + if (walker(coercions->timetz, context)) + return true; + if (walker(coercions->timestamp, context)) + return true; + if (walker(coercions->timestamptz, context)) + return true; + if (walker(coercions->composite, context)) + return true; + } + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); @@ -3060,6 +3175,65 @@ expression_tree_mutator(Node *node, return (Node *) newnode; } break; + case T_JsonValueExpr: + { + JsonValueExpr *jve = (JsonValueExpr *) node; + JsonValueExpr *newnode; + + FLATCOPY(newnode, jve, JsonValueExpr); + MUTATE(newnode->expr, jve->expr, Expr *); + + return (Node *) newnode; + } + break; + case T_JsonExpr: + { + JsonExpr *jexpr = (JsonExpr *) node; + JsonExpr *newnode; + + FLATCOPY(newnode, jexpr, JsonExpr); + MUTATE(newnode->raw_expr, jexpr->path_spec, Node *); + MUTATE(newnode->raw_expr, jexpr->raw_expr, Node *); + MUTATE(newnode->formatted_expr, jexpr->formatted_expr, Node *); + MUTATE(newnode->result_coercion, jexpr->result_coercion, JsonCoercion *); + MUTATE(newnode->passing.values, jexpr->passing.values, List *); + /* assume mutator does not care about passing.names */ + MUTATE(newnode->on_empty.default_expr, + jexpr->on_empty.default_expr, Node *); + MUTATE(newnode->on_error.default_expr, + jexpr->on_error.default_expr, Node *); + return (Node *) newnode; + } + break; + case T_JsonCoercion: + { + JsonCoercion *coercion = (JsonCoercion *) node; + JsonCoercion *newnode; + + FLATCOPY(newnode, coercion, JsonCoercion); + MUTATE(newnode->expr, coercion->expr, Node *); + return (Node *) newnode; + } + break; + case T_JsonItemCoercions: + { + JsonItemCoercions *coercions = (JsonItemCoercions *) node; + JsonItemCoercions *newnode; + + FLATCOPY(newnode, coercions, JsonItemCoercions); + MUTATE(newnode->null, coercions->null, JsonCoercion *); + MUTATE(newnode->string, coercions->string, JsonCoercion *); + MUTATE(newnode->numeric, coercions->numeric, JsonCoercion *); + MUTATE(newnode->boolean, coercions->boolean, JsonCoercion *); + MUTATE(newnode->date, coercions->date, JsonCoercion *); + MUTATE(newnode->time, coercions->time, JsonCoercion *); + MUTATE(newnode->timetz, coercions->timetz, JsonCoercion *); + MUTATE(newnode->timestamp, coercions->timestamp, JsonCoercion *); + MUTATE(newnode->timestamptz, coercions->timestamptz, JsonCoercion *); + MUTATE(newnode->composite, coercions->composite, JsonCoercion *); + return (Node *) newnode; + } + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); @@ -3704,6 +3878,121 @@ raw_expression_tree_walker(Node *node, break; case T_CommonTableExpr: return walker(((CommonTableExpr *) node)->ctequery, context); + case T_JsonValueExpr: + return walker(((JsonValueExpr *) node)->expr, context); + case T_JsonOutput: + return walker(((JsonOutput *) node)->typeName, context); + case T_JsonKeyValue: + { + JsonKeyValue *jkv = (JsonKeyValue *) node; + + if (walker(jkv->key, context)) + return true; + if (walker(jkv->value, context)) + return true; + } + break; + case T_JsonObjectCtor: + { + JsonObjectCtor *joc = (JsonObjectCtor *) node; + + if (walker(joc->output, context)) + return true; + if (walker(joc->exprs, context)) + return true; + } + break; + case T_JsonArrayCtor: + { + JsonArrayCtor *jac = (JsonArrayCtor *) node; + + if (walker(jac->output, context)) + return true; + if (walker(jac->exprs, context)) + return true; + } + break; + case T_JsonObjectAgg: + { + JsonObjectAgg *joa = (JsonObjectAgg *) node; + + if (walker(joa->ctor.output, context)) + return true; + if (walker(joa->ctor.agg_order, context)) + return true; + if (walker(joa->ctor.agg_filter, context)) + return true; + if (walker(joa->ctor.over, context)) + return true; + if (walker(joa->arg, context)) + return true; + } + break; + case T_JsonArrayAgg: + { + JsonArrayAgg *jaa = (JsonArrayAgg *) node; + + if (walker(jaa->ctor.output, context)) + return true; + if (walker(jaa->ctor.agg_order, context)) + return true; + if (walker(jaa->ctor.agg_filter, context)) + return true; + if (walker(jaa->ctor.over, context)) + return true; + if (walker(jaa->arg, context)) + return true; + } + break; + case T_JsonArrayQueryCtor: + { + JsonArrayQueryCtor *jaqc = (JsonArrayQueryCtor *) node; + + if (walker(jaqc->output, context)) + return true; + if (walker(jaqc->query, context)) + return true; + } + break; + case T_JsonIsPredicate: + return walker(((JsonIsPredicate *) node)->expr, context); + case T_JsonArgument: + return walker(((JsonArgument *) node)->val, context); + case T_JsonCommon: + { + JsonCommon *jc = (JsonCommon *) node; + + if (walker(jc->expr, context)) + return true; + if (walker(jc->pathspec, context)) + return true; + if (walker(jc->passing, context)) + return true; + } + break; + case T_JsonBehavior: + { + JsonBehavior *jb = (JsonBehavior *) node; + + if (jb->btype == JSON_BEHAVIOR_DEFAULT && + walker(jb->default_expr, context)) + return true; + } + break; + case T_JsonFuncExpr: + { + JsonFuncExpr *jfe = (JsonFuncExpr *) node; + + if (walker(jfe->common, context)) + return true; + if (jfe->output && walker(jfe->output, context)) + return true; + if (walker(jfe->on_empty, context)) + return true; + if (walker(jfe->on_error, 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 b884bb8..7965210 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1779,6 +1779,98 @@ _outOnConflictExpr(StringInfo str, const OnConflictExpr *node) WRITE_NODE_FIELD(exclRelTlist); } +static void +_outJsonValueExpr(StringInfo str, const JsonValueExpr *node) +{ + WRITE_NODE_TYPE("JSONVALUEEXPR"); + + WRITE_NODE_FIELD(expr); + WRITE_ENUM_FIELD(format.type, JsonFormatType); + WRITE_ENUM_FIELD(format.encoding, JsonEncoding); + WRITE_LOCATION_FIELD(format.location); +} + +static void +_outJsonCtorOpts(StringInfo str, const JsonCtorOpts *node) +{ + WRITE_NODE_TYPE("JSONCTOROPTS"); + + WRITE_ENUM_FIELD(returning.format.type, JsonFormatType); + WRITE_ENUM_FIELD(returning.format.encoding, JsonEncoding); + WRITE_LOCATION_FIELD(returning.format.location); + WRITE_OID_FIELD(returning.typid); + WRITE_INT_FIELD(returning.typmod); + WRITE_BOOL_FIELD(unique); + WRITE_BOOL_FIELD(absent_on_null); +} + +static void +_outJsonExpr(StringInfo str, const JsonExpr *node) +{ + WRITE_NODE_TYPE("JSONEXPR"); + + WRITE_ENUM_FIELD(op, JsonExprOp); + WRITE_NODE_FIELD(raw_expr); + WRITE_NODE_FIELD(formatted_expr); + WRITE_NODE_FIELD(result_coercion); + WRITE_ENUM_FIELD(format.type, JsonFormatType); + WRITE_ENUM_FIELD(format.encoding, JsonEncoding); + WRITE_LOCATION_FIELD(format.location); + WRITE_NODE_FIELD(path_spec); + WRITE_NODE_FIELD(passing.values); + WRITE_NODE_FIELD(passing.names); + WRITE_ENUM_FIELD(returning.format.type, JsonFormatType); + WRITE_ENUM_FIELD(returning.format.encoding, JsonEncoding); + WRITE_LOCATION_FIELD(returning.format.location); + WRITE_OID_FIELD(returning.typid); + WRITE_INT_FIELD(returning.typmod); + WRITE_ENUM_FIELD(on_error.btype, JsonBehaviorType); + WRITE_NODE_FIELD(on_error.default_expr); + WRITE_ENUM_FIELD(on_empty.btype, JsonBehaviorType); + WRITE_NODE_FIELD(on_empty.default_expr); + WRITE_NODE_FIELD(coercions); + WRITE_ENUM_FIELD(wrapper, JsonWrapper); + WRITE_BOOL_FIELD(omit_quotes); + WRITE_LOCATION_FIELD(location); +} + +static void +_outJsonCoercion(StringInfo str, const JsonCoercion *node) +{ + WRITE_NODE_TYPE("JSONCOERCION"); + + WRITE_NODE_FIELD(expr); + WRITE_BOOL_FIELD(via_populate); + WRITE_BOOL_FIELD(via_io); + WRITE_OID_FIELD(collation); +} + +static void +_outJsonItemCoercions(StringInfo str, const JsonItemCoercions *node) +{ + WRITE_NODE_TYPE("JSONITEMCOERCIONS"); + + WRITE_NODE_FIELD(null); + WRITE_NODE_FIELD(string); + WRITE_NODE_FIELD(numeric); + WRITE_NODE_FIELD(boolean); + WRITE_NODE_FIELD(date); + WRITE_NODE_FIELD(time); + WRITE_NODE_FIELD(timetz); + WRITE_NODE_FIELD(timestamp); + WRITE_NODE_FIELD(timestamptz); + WRITE_NODE_FIELD(composite); +} + +static void +_outJsonIsPredicateOpts(StringInfo str, const JsonIsPredicateOpts *node) +{ + WRITE_NODE_TYPE("JSONISOPTS"); + + WRITE_ENUM_FIELD(value_type, JsonValueType); + WRITE_BOOL_FIELD(unique_keys); +} + /***************************************************************************** * * Stuff from relation.h. @@ -4354,6 +4446,24 @@ outNode(StringInfo str, const void *obj) case T_PartitionRangeDatum: _outPartitionRangeDatum(str, obj); break; + case T_JsonValueExpr: + _outJsonValueExpr(str, obj); + break; + case T_JsonCtorOpts: + _outJsonCtorOpts(str, obj); + break; + case T_JsonIsPredicateOpts: + _outJsonIsPredicateOpts(str, obj); + break; + case T_JsonExpr: + _outJsonExpr(str, obj); + break; + case T_JsonCoercion: + _outJsonCoercion(str, obj); + break; + case T_JsonItemCoercions: + _outJsonItemCoercions(str, obj); + break; default: diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index cc55517..80c95e8 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -1346,6 +1346,127 @@ _readOnConflictExpr(void) } /* + * _readJsonValueExpr + */ +static JsonValueExpr * +_readJsonValueExpr(void) +{ + READ_LOCALS(JsonValueExpr); + + READ_NODE_FIELD(expr); + READ_ENUM_FIELD(format.type, JsonFormatType); + READ_ENUM_FIELD(format.encoding, JsonEncoding); + READ_LOCATION_FIELD(format.location); + + READ_DONE(); +} + +/* + * _readJsonCtorOpts + */ +static JsonCtorOpts * +_readJsonCtorOpts(void) +{ + READ_LOCALS(JsonCtorOpts); + READ_ENUM_FIELD(returning.format.type, JsonFormatType); + READ_ENUM_FIELD(returning.format.encoding, JsonEncoding); + READ_LOCATION_FIELD(returning.format.location); + READ_OID_FIELD(returning.typid); + READ_INT_FIELD(returning.typmod); + READ_BOOL_FIELD(unique); + READ_BOOL_FIELD(absent_on_null); + + READ_DONE(); +} + +/* + * _readJsonExpr + */ +static JsonExpr * +_readJsonExpr(void) +{ + READ_LOCALS(JsonExpr); + + READ_ENUM_FIELD(op, JsonExprOp); + READ_NODE_FIELD(raw_expr); + READ_NODE_FIELD(formatted_expr); + READ_NODE_FIELD(result_coercion); + READ_ENUM_FIELD(format.type, JsonFormatType); + READ_ENUM_FIELD(format.encoding, JsonEncoding); + READ_LOCATION_FIELD(format.location); + READ_NODE_FIELD(path_spec); + READ_NODE_FIELD(passing.values); + READ_NODE_FIELD(passing.names); + READ_ENUM_FIELD(returning.format.type, JsonFormatType); + READ_ENUM_FIELD(returning.format.encoding, JsonEncoding); + READ_LOCATION_FIELD(returning.format.location); + READ_OID_FIELD(returning.typid); + READ_INT_FIELD(returning.typmod); + READ_ENUM_FIELD(on_error.btype, JsonBehaviorType); + READ_NODE_FIELD(on_error.default_expr); + READ_ENUM_FIELD(on_empty.btype, JsonBehaviorType); + READ_NODE_FIELD(on_empty.default_expr); + READ_NODE_FIELD(coercions); + READ_ENUM_FIELD(wrapper, JsonWrapper); + READ_BOOL_FIELD(omit_quotes); + READ_LOCATION_FIELD(location); + + READ_DONE(); +} + +/* + * _readJsonCoercion + */ +static JsonCoercion * +_readJsonCoercion(void) +{ + READ_LOCALS(JsonCoercion); + + READ_NODE_FIELD(expr); + READ_BOOL_FIELD(via_populate); + READ_BOOL_FIELD(via_io); + READ_OID_FIELD(collation); + + READ_DONE(); +} + +/* + * _readJsonItemCoercions + */ +static JsonItemCoercions * +_readJsonItemCoercions(void) +{ + READ_LOCALS(JsonItemCoercions); + + READ_NODE_FIELD(null); + READ_NODE_FIELD(string); + READ_NODE_FIELD(numeric); + READ_NODE_FIELD(boolean); + READ_NODE_FIELD(date); + READ_NODE_FIELD(time); + READ_NODE_FIELD(timetz); + READ_NODE_FIELD(timestamp); + READ_NODE_FIELD(timestamptz); + READ_NODE_FIELD(composite); + + READ_DONE(); +} + +/* + * _readJsonIsPredicateOpts + */ +static JsonIsPredicateOpts * +_readJsonIsPredicateOpts() +{ + READ_LOCALS(JsonIsPredicateOpts); + + READ_ENUM_FIELD(value_type, JsonValueType); + READ_BOOL_FIELD(unique_keys); + + READ_DONE(); +} + +/* * Stuff from parsenodes.h. */ @@ -2796,6 +2917,18 @@ parseNodeString(void) return_value = _readPartitionBoundSpec(); else if (MATCH("PARTITIONRANGEDATUM", 19)) return_value = _readPartitionRangeDatum(); + else if (MATCH("JSONVALUEEXPR", 13)) + return_value = _readJsonValueExpr(); + else if (MATCH("JSONCTOROPTS", 12)) + return_value = _readJsonCtorOpts(); + else if (MATCH("JSONISOPTS", 10)) + return_value = _readJsonIsPredicateOpts(); + else if (MATCH("JSONEXPR", 8)) + return_value = _readJsonExpr(); + else if (MATCH("JSONCOERCION", 12)) + return_value = _readJsonCoercion(); + else if (MATCH("JSONITEMCOERCIONS", 17)) + return_value = _readJsonItemCoercions(); else { elog(ERROR, "badly formatted node string \"%.32s\"...", token); diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index 7bf67a0..7ba7b54 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -3909,7 +3909,8 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context) IsA(node, SQLValueFunction) || IsA(node, XmlExpr) || IsA(node, CoerceToDomain) || - IsA(node, NextValueExpr)) + IsA(node, NextValueExpr) || + IsA(node, JsonExpr)) { /* Treat all these as having cost 1 */ context->total.per_tuple += cpu_operator_cost; diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 4413d03..4000b09 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -28,6 +28,7 @@ #include "catalog/pg_type.h" #include "executor/executor.h" #include "executor/functions.h" +#include "executor/execExpr.h" #include "funcapi.h" #include "miscadmin.h" #include "nodes/makefuncs.h" @@ -1302,6 +1303,16 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context) context, 0); } + /* JsonExpr is parallel-unsafe if subtransactions can be used. */ + else if (IsA(node, JsonExpr)) + { + JsonExpr *jsexpr = (JsonExpr *) node; + + if (ExecEvalJsonNeedsSubTransaction(jsexpr)) + context->max_hazard = PROPARALLEL_UNSAFE; + return true; + } + /* Recurse to check arguments */ return expression_tree_walker(node, max_parallel_hazard_walker, diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 2c2208f..0001199 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -211,6 +211,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); JoinType jtype; DropBehavior dbehavior; OnCommitAction oncommit; + JsonFormat jsformat; List *list; Node *node; Value *value; @@ -241,6 +242,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); PartitionSpec *partspec; PartitionBoundSpec *partboundspec; RoleSpec *rolespec; + JsonBehavior *jsbehavior; + struct { + JsonBehavior *on_empty; + JsonBehavior *on_error; + } on_behavior; + JsonQuotes js_quotes; } %type stmt schema_stmt @@ -585,6 +592,72 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type hash_partbound partbound_datum_list range_datum_list %type hash_partbound_elem +%type json_value_expr + json_func_expr + json_value_func_expr + json_query_expr + json_exists_predicate + json_api_common_syntax + json_context_item + json_argument + json_output_clause_opt + json_value_constructor + json_object_constructor + json_object_constructor_args_opt + json_object_args + json_object_ctor_args_opt + json_object_func_args + json_array_constructor + json_name_and_value + json_aggregate_func + json_object_aggregate_constructor + json_array_aggregate_constructor + json_path_specification + +%type json_arguments + json_passing_clause_opt + json_name_and_value_list + json_value_expr_list + json_array_aggregate_order_by_clause_opt + +%type json_returning_clause_opt + +%type json_table_path_name + json_as_path_name_clause_opt + +%type json_encoding + json_encoding_clause_opt + json_wrapper_clause_opt + json_wrapper_behavior + json_conditional_or_unconditional_opt + json_predicate_type_constraint_opt + +%type json_format_clause_opt + json_representation + +%type json_behavior_error + json_behavior_null + json_behavior_true + json_behavior_false + json_behavior_unknown + json_behavior_empty_array + json_behavior_empty_object + json_behavior_default + json_value_behavior + json_query_behavior + json_exists_error_behavior + json_exists_error_clause_opt + +%type json_value_on_behavior_clause_opt + json_query_on_behavior_clause_opt + +%type json_quotes_behavior + json_quotes_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 @@ -607,7 +680,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 ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION @@ -617,8 +690,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); CACHE CALL CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE CLUSTER COALESCE COLLATE COLLATION COLUMN COLUMNS COMMENT COMMENTS COMMIT - COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT - CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE + COMMITTED CONCURRENTLY CONDITIONAL CONFIGURATION CONFLICT CONNECTION + CONSTRAINT CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE CROSS CSV CUBE CURRENT_P CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE @@ -628,12 +701,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP - EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT - EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN + EACH ELSE EMPTY_P ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ERROR_P ESCAPE + EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXTENSION EXTERNAL EXTRACT FALSE_P FAMILY FETCH FILTER FIRST_P FLOAT_P FOLLOWING FOR - FORCE FOREIGN FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS + FORCE FOREIGN FORMAT FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS GENERATED GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING GROUPS @@ -644,9 +717,10 @@ 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 + JOIN JSON JSON_ARRAY JSON_ARRAYAGG JSON_EXISTS JSON_OBJECT JSON_OBJECTAGG + JSON_QUERY JSON_VALUE - KEY + KEY KEYS KEEP LABEL LANGUAGE LARGE_P LAST_P LATERAL_P LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL @@ -658,7 +732,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF NULLS_P NUMERIC - OBJECT_P OF OFF OFFSET OIDS OLD ON ONLY OPERATOR OPTION OPTIONS OR + OBJECT_P OF OFF OFFSET OIDS OLD OMIT ON ONLY OPERATOR OPTION OPTIONS OR ORDER ORDINALITY OTHERS OUT_P OUTER_P OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER @@ -666,17 +740,17 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION - QUOTE + QUOTE QUOTES RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REFERENCING REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP ROUTINE ROUTINES ROW ROWS RULE - SAVEPOINT SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES - SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW - SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P - START STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P + SAVEPOINT SCALAR SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT + SEQUENCE SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF + SHARE SHOW SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P + START STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRING STRIP_P SUBSCRIPTION SUBSTRING SYMMETRIC SYSID SYSTEM_P TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN @@ -684,8 +758,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); TREAT TRIGGER TRIM TRUE_P TRUNCATE TRUSTED TYPE_P TYPES_P - UNBOUNDED UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN UNLISTEN UNLOGGED - UNTIL UPDATE USER USING + UNBOUNDED UNCOMMITTED UNCONDITIONAL UNENCRYPTED UNION UNIQUE UNKNOWN + UNLISTEN UNLOGGED UNTIL UPDATE USER USING VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING VERBOSE VERSION_P VIEW VIEWS VOLATILE @@ -709,11 +783,11 @@ 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 /* Precedence: lowest to highest */ %nonassoc SET /* see relation_expr_opt_alias */ +%right FORMAT %left UNION EXCEPT %left INTERSECT %left OR @@ -752,6 +826,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); * blame any funny behavior of UNBOUNDED on the SQL standard, though. */ %nonassoc UNBOUNDED /* ideally should have same precedence as IDENT */ +%nonassoc ERROR_P EMPTY_P DEFAULT ABSENT /* JSON error/empty behavior */ +%nonassoc FALSE_P KEEP OMIT PASSING TRUE_P UNKNOWN %nonassoc IDENT GENERATED NULL_P PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP %left Op OPERATOR /* multi-character ops and user-defined operators */ %left '+' '-' @@ -776,6 +852,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); /* kluge to keep xml_whitespace_option from causing shift/reduce conflicts */ %right PRESERVE STRIP_P +%nonassoc empty_json_unique +%left WITHOUT WITH_LA_UNIQUE + %% /* @@ -12796,7 +12875,7 @@ ConstInterval: opt_timezone: WITH_LA TIME ZONE { $$ = true; } - | WITHOUT TIME ZONE { $$ = false; } + | WITHOUT_LA TIME ZONE { $$ = false; } | /*EMPTY*/ { $$ = false; } ; @@ -13297,6 +13376,48 @@ a_expr: c_expr { $$ = $1; } list_make1($1), @2), @2); } + | a_expr + IS JSON + json_predicate_type_constraint_opt + json_key_uniqueness_constraint_opt %prec IS + { + JsonFormat format = { JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1 }; + $$ = makeJsonIsPredicate($1, format, $4, $5); + } + /* + * Required by standard, but it would conflict with expressions + * like: 'str' || format(...) + | a_expr + FORMAT json_representation + IS JSON + json_predicate_type_constraint_opt + json_key_uniqueness_constraint_opt %prec FORMAT + { + $3.location = @2; + $$ = makeJsonIsPredicate($1, $3, $6, $7); + } + */ + | a_expr + IS NOT JSON + json_predicate_type_constraint_opt + json_key_uniqueness_constraint_opt %prec IS + { + JsonFormat format = { JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1 }; + $$ = makeNotExpr(makeJsonIsPredicate($1, format, $5, $6), @1); + } + /* + * Required by standard, but it would conflict with expressions + * like: 'str' || format(...) + | a_expr + FORMAT json_representation + IS NOT JSON + json_predicate_type_constraint_opt + json_key_uniqueness_constraint_opt %prec FORMAT + { + $3.location = @2; + $$ = makeNotExpr(makeJsonIsPredicate($1, $3, $7, $8), @1); + } + */ | DEFAULT { /* @@ -13389,6 +13510,25 @@ b_expr: c_expr } ; +json_predicate_type_constraint_opt: + VALUE_P { $$ = JS_TYPE_ANY; } + | ARRAY { $$ = JS_TYPE_ARRAY; } + | OBJECT_P { $$ = JS_TYPE_OBJECT; } + | SCALAR { $$ = JS_TYPE_SCALAR; } + | /* EMPTY */ { $$ = JS_TYPE_ANY; } + ; + +json_key_uniqueness_constraint_opt: + WITH_LA_UNIQUE UNIQUE opt_keys { $$ = true; } + | WITHOUT UNIQUE opt_keys { $$ = false; } + | /* EMPTY */ %prec empty_json_unique { $$ = false; } + ; + +opt_keys: + KEYS { } + | /* EMPTY */ { } + ; + /* * Productions that can be used in both a_expr and b_expr. * @@ -13649,6 +13789,13 @@ func_expr: func_application within_group_clause filter_clause over_clause n->over = $4; $$ = (Node *) n; } + | json_aggregate_func filter_clause over_clause + { + JsonAggCtor *n = (JsonAggCtor *) $1; + n->agg_filter = $2; + n->over = $3; + $$ = (Node *) $1; + } | func_expr_common_subexpr { $$ = $1; } ; @@ -13662,6 +13809,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; } ; /* @@ -13883,6 +14031,8 @@ func_expr_common_subexpr: n->location = @1; $$ = (Node *)n; } + | json_func_expr + { $$ = $1; } ; /* @@ -14571,6 +14721,495 @@ opt_asymmetric: ASYMMETRIC | /*EMPTY*/ ; +/* SQL/JSON support */ +json_func_expr: + json_value_func_expr + | json_query_expr + | json_exists_predicate + | json_value_constructor + ; + + +json_value_func_expr: + JSON_VALUE '(' + json_api_common_syntax + json_returning_clause_opt + json_value_on_behavior_clause_opt + ')' + { + JsonFuncExpr *n = makeNode(JsonFuncExpr); + n->op = IS_JSON_VALUE; + n->common = (JsonCommon *) $3; + if ($4) + { + n->output = (JsonOutput *) makeNode(JsonOutput); + n->output->typeName = $4; + n->output->returning.format.location = @4; + n->output->returning.format.type = JS_FORMAT_DEFAULT; + n->output->returning.format.encoding = JS_ENC_DEFAULT; + } + else + n->output = NULL; + n->on_empty = $5.on_empty; + n->on_error = $5.on_error; + n->location = @1; + $$ = (Node *) n; + } + ; + +json_api_common_syntax: + json_context_item ',' json_path_specification + json_as_path_name_clause_opt + json_passing_clause_opt + { + JsonCommon *n = makeNode(JsonCommon); + n->expr = (JsonValueExpr *) $1; + n->pathspec = $3; + n->pathname = $4; + n->passing = $5; + n->location = @1; + $$ = (Node *) n; + } + ; + +json_context_item: + json_value_expr { $$ = $1; } + ; + +json_path_specification: + a_expr { $$ = $1; } + ; + +json_as_path_name_clause_opt: + AS json_table_path_name { $$ = $2; } + | /* EMPTY */ { $$ = NULL; } + ; + +json_table_path_name: + name { $$ = $1; } + ; + +json_passing_clause_opt: + PASSING json_arguments { $$ = $2; } + | /* EMPTY */ { $$ = NIL; } + ; + +json_arguments: + json_argument { $$ = list_make1($1); } + | json_arguments ',' json_argument { $$ = lappend($1, $3); } + ; + +json_argument: + json_value_expr AS ColLabel + { + JsonArgument *n = makeNode(JsonArgument); + n->val = (JsonValueExpr *) $1; + n->name = $3; + $$ = (Node *) n; + } + ; + +json_value_expr: + a_expr json_format_clause_opt + { + $$ = (Node *) makeJsonValueExpr((Expr *) $1, $2); + } + ; + +json_format_clause_opt: + FORMAT json_representation + { + $$ = $2; + $$.location = @1; + } + | /* EMPTY */ + { + $$.type = JS_FORMAT_DEFAULT; + $$.encoding = JS_ENC_DEFAULT; + $$.location = -1; + } + ; + +json_representation: + JSON json_encoding_clause_opt + { + $$.type = JS_FORMAT_JSON; + $$.encoding = $2; + $$.location = @1; + } + /* | implementation_defined_JSON_representation_option (BSON, AVRO etc) */ + ; + +json_encoding_clause_opt: + ENCODING json_encoding { $$ = $2; } + | /* EMPTY */ { $$ = JS_ENC_DEFAULT; } + ; + +json_encoding: + name { $$ = makeJsonEncoding($1); } + /* + | UTF8 { $$ = JS_ENC_UTF8; } + | UTF16 { $$ = JS_ENC_UTF16; } + | UTF32 { $$ = JS_ENC_UTF32; } + */ + ; + +json_returning_clause_opt: + RETURNING Typename { $$ = $2; } + | /* EMPTY */ { $$ = NULL; } + ; + +json_behavior_error: + ERROR_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_ERROR, NULL); } + ; + +json_behavior_null: + NULL_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_NULL, NULL); } + ; + +json_behavior_true: + TRUE_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_TRUE, NULL); } + ; + +json_behavior_false: + FALSE_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_FALSE, NULL); } + ; + +json_behavior_unknown: + UNKNOWN { $$ = makeJsonBehavior(JSON_BEHAVIOR_UNKNOWN, NULL); } + ; + +json_behavior_empty_array: + EMPTY_P ARRAY { $$ = makeJsonBehavior(JSON_BEHAVIOR_EMPTY_ARRAY, NULL); } + ; + +json_behavior_empty_object: + EMPTY_P OBJECT_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_EMPTY_OBJECT, NULL); } + ; + +json_behavior_default: + DEFAULT a_expr { $$ = makeJsonBehavior(JSON_BEHAVIOR_DEFAULT, $2); } + ; + + +json_value_behavior: + json_behavior_null + | json_behavior_error + | json_behavior_default + ; + +json_value_on_behavior_clause_opt: + json_value_behavior ON EMPTY_P + { $$.on_empty = $1; $$.on_error = NULL; } + | json_value_behavior ON EMPTY_P json_value_behavior ON ERROR_P + { $$.on_empty = $1; $$.on_error = $4; } + | json_value_behavior ON ERROR_P + { $$.on_empty = NULL; $$.on_error = $1; } + | /* EMPTY */ + { $$.on_empty = NULL; $$.on_error = NULL; } + ; + +json_query_expr: + JSON_QUERY '(' + json_api_common_syntax + json_output_clause_opt + json_wrapper_clause_opt + json_quotes_clause_opt + json_query_on_behavior_clause_opt + ')' + { + JsonFuncExpr *n = makeNode(JsonFuncExpr); + n->op = IS_JSON_QUERY; + n->common = (JsonCommon *) $3; + n->output = (JsonOutput *) $4; + n->wrapper = $5; + if (n->wrapper != JSW_NONE && $6 != JS_QUOTES_UNSPEC) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("SQL/JSON QUOTES behavior shall not be specified when WITH WRAPPER is used"), + parser_errposition(@6))); + n->omit_quotes = $6 == JS_QUOTES_OMIT; + n->on_empty = $7.on_empty; + n->on_error = $7.on_error; + n->location = @1; + $$ = (Node *) n; + } + ; + +json_wrapper_clause_opt: + json_wrapper_behavior WRAPPER { $$ = $1; } + | /* EMPTY */ { $$ = 0; } + ; + +json_wrapper_behavior: + WITHOUT array_opt { $$ = JSW_NONE; } + | WITH json_conditional_or_unconditional_opt array_opt { $$ = $2; } + ; + +array_opt: + ARRAY { } + | /* EMPTY */ { } + ; + +json_conditional_or_unconditional_opt: + CONDITIONAL { $$ = JSW_CONDITIONAL; } + | UNCONDITIONAL { $$ = JSW_UNCONDITIONAL; } + | /* EMPTY */ { $$ = JSW_UNCONDITIONAL; } + ; + +json_quotes_clause_opt: + json_quotes_behavior QUOTES json_on_scalar_string_opt { $$ = $1; } + | /* EMPTY */ { $$ = JS_QUOTES_UNSPEC; } + ; + +json_quotes_behavior: + KEEP { $$ = JS_QUOTES_KEEP; } + | OMIT { $$ = JS_QUOTES_OMIT; } + ; + +json_on_scalar_string_opt: + ON SCALAR STRING { } + | /* EMPTY */ { } + ; + +json_query_behavior: + json_behavior_error + | json_behavior_null + | json_behavior_empty_array + | json_behavior_empty_object + ; + +json_query_on_behavior_clause_opt: + json_query_behavior ON EMPTY_P + { $$.on_empty = $1; $$.on_error = NULL; } + | json_query_behavior ON EMPTY_P json_query_behavior ON ERROR_P + { $$.on_empty = $1; $$.on_error = $4; } + | json_query_behavior ON ERROR_P + { $$.on_empty = NULL; $$.on_error = $1; } + | /* EMPTY */ + { $$.on_empty = NULL; $$.on_error = NULL; } + ; + + +json_output_clause_opt: + RETURNING Typename json_format_clause_opt + { + JsonOutput *n = makeNode(JsonOutput); + n->typeName = $2; + n->returning.format = $3; + $$ = (Node *) n; + } + | /* EMPTY */ { $$ = NULL; } + ; + +json_exists_predicate: + JSON_EXISTS '(' + json_api_common_syntax + json_exists_error_clause_opt + ')' + { + JsonFuncExpr *p = makeNode(JsonFuncExpr); + p->op = IS_JSON_EXISTS; + p->common = (JsonCommon *) $3; + p->on_error = $4; + p->location = @1; + $$ = (Node *) p; + } + ; + +json_exists_error_clause_opt: + json_exists_error_behavior ON ERROR_P { $$ = $1; } + | /* EMPTY */ { $$ = NULL; } + ; + +json_exists_error_behavior: + json_behavior_error + | json_behavior_true + | json_behavior_false + | json_behavior_unknown + ; + +json_value_constructor: + json_object_constructor + | json_array_constructor + ; + +json_object_constructor: + JSON_OBJECT '(' json_object_args ')' + { + $$ = $3; + } + ; + +json_object_args: + json_object_ctor_args_opt + | json_object_func_args + ; + +json_object_func_args: + func_arg_list + { + List *func = list_make1(makeString("json_object")); + $$ = (Node *) makeFuncCall(func, $1, @1); + } + ; + +json_object_ctor_args_opt: + json_object_constructor_args_opt json_output_clause_opt + { + JsonObjectCtor *n = (JsonObjectCtor *) $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 + { + JsonObjectCtor *n = makeNode(JsonObjectCtor); + n->exprs = $1; + n->absent_on_null = $2; + n->unique = $3; + $$ = (Node *) n; + } + | /* EMPTY */ + { + JsonObjectCtor *n = makeNode(JsonObjectCtor); + 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 + 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 + ')' + { + JsonArrayCtor *n = makeNode(JsonArrayCtor); + 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 + ')' + { + JsonArrayQueryCtor *n = makeNode(JsonArrayQueryCtor); + n->query = $3; + /* n->format = $4; */ + n->absent_on_null = true /* $5 */; + n->output = (JsonOutput *) $4; + n->location = @1; + $$ = (Node *) n; + } + | JSON_ARRAY '(' + json_output_clause_opt + ')' + { + JsonArrayCtor *n = makeNode(JsonArrayCtor); + 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->ctor.output = (JsonOutput *) $6; + n->ctor.agg_order = NULL; + n->ctor.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->ctor.agg_order = $4; + n->absent_on_null = $5; + n->ctor.output = (JsonOutput *) $6; + n->ctor.location = @1; + $$ = (Node *) n; + } + ; + +json_array_aggregate_order_by_clause_opt: + ORDER BY sortby_list { $$ = $3; } + | /* EMPTY */ { $$ = NIL; } + ; /***************************************************************************** * @@ -14967,6 +15606,7 @@ ColLabel: IDENT { $$ = $1; } */ unreserved_keyword: ABORT_P + | ABSENT | ABSOLUTE_P | ACCESS | ACTION @@ -15003,6 +15643,7 @@ unreserved_keyword: | COMMENTS | COMMIT | COMMITTED + | CONDITIONAL | CONFIGURATION | CONFLICT | CONNECTION @@ -15038,10 +15679,12 @@ unreserved_keyword: | DOUBLE_P | DROP | EACH + | EMPTY_P | ENABLE_P | ENCODING | ENCRYPTED | ENUM_P + | ERROR_P | ESCAPE | EVENT | EXCLUDE @@ -15087,7 +15730,10 @@ unreserved_keyword: | INSTEAD | INVOKER | ISOLATION + | JSON + | KEEP | KEY + | KEYS | LABEL | LANGUAGE | LARGE_P @@ -15125,6 +15771,7 @@ unreserved_keyword: | OFF | OIDS | OLD + | OMIT | OPERATOR | OPTION | OPTIONS @@ -15154,6 +15801,7 @@ unreserved_keyword: | PROGRAM | PUBLICATION | QUOTE + | QUOTES | RANGE | READ | REASSIGN @@ -15182,6 +15830,7 @@ unreserved_keyword: | ROWS | RULE | SAVEPOINT + | SCALAR | SCHEMA | SCHEMAS | SCROLL @@ -15230,6 +15879,7 @@ unreserved_keyword: | TYPES_P | UNBOUNDED | UNCOMMITTED + | UNCONDITIONAL | UNENCRYPTED | UNKNOWN | UNLISTEN @@ -15287,6 +15937,13 @@ col_name_keyword: | INT_P | INTEGER | INTERVAL + | JSON_ARRAY + | JSON_ARRAYAGG + | JSON_EXISTS + | JSON_OBJECT + | JSON_OBJECTAGG + | JSON_QUERY + | JSON_VALUE | LEAST | NATIONAL | NCHAR @@ -15338,6 +15995,7 @@ type_func_name_keyword: | CONCURRENTLY | CROSS | CURRENT_SCHEMA + | FORMAT | FREEZE | FULL | ILIKE @@ -15353,6 +16011,7 @@ type_func_name_keyword: | OVERLAPS | RIGHT | SIMILAR + | STRING | TABLESAMPLE | VERBOSE ; diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c index 6d34245..e486e7c 100644 --- a/src/backend/parser/parse_collate.c +++ b/src/backend/parser/parse_collate.c @@ -667,6 +667,10 @@ assign_collations_walker(Node *node, assign_collations_context *context) &loccontext); } break; + case T_JsonExpr: + /* Context item and PASSING arguments are already + * marked with collations in parse_expr.c. */ + break; default: /* diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 385e54a9..bcb3e22 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" @@ -35,6 +37,7 @@ #include "parser/parse_agg.h" #include "utils/builtins.h" #include "utils/date.h" +#include "utils/fmgroids.h" #include "utils/lsyscache.h" #include "utils/timestamp.h" #include "utils/xml.h" @@ -121,6 +124,15 @@ static Node *transformWholeRowRef(ParseState *pstate, RangeTblEntry *rte, static Node *transformIndirection(ParseState *pstate, A_Indirection *ind); static Node *transformTypeCast(ParseState *pstate, TypeCast *tc); static Node *transformCollateClause(ParseState *pstate, CollateClause *c); +static Node *transformJsonObjectCtor(ParseState *pstate, JsonObjectCtor *ctor); +static Node *transformJsonArrayCtor(ParseState *pstate, JsonArrayCtor *ctor); +static Node *transformJsonArrayQueryCtor(ParseState *pstate, + JsonArrayQueryCtor *ctor); +static Node *transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg); +static Node *transformJsonArrayAgg(ParseState *pstate, JsonArrayAgg *agg); +static Node *transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *p); +static Node *transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *p); +static Node *transformJsonValueExpr(ParseState *pstate, JsonValueExpr *jve); 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, @@ -369,6 +381,38 @@ transformExprRecurse(ParseState *pstate, Node *expr) break; } + case T_JsonObjectCtor: + result = transformJsonObjectCtor(pstate, (JsonObjectCtor *) expr); + break; + + case T_JsonArrayCtor: + result = transformJsonArrayCtor(pstate, (JsonArrayCtor *) expr); + break; + + case T_JsonArrayQueryCtor: + result = transformJsonArrayQueryCtor(pstate, (JsonArrayQueryCtor *) expr); + break; + + case T_JsonObjectAgg: + result = transformJsonObjectAgg(pstate, (JsonObjectAgg *) expr); + break; + + case T_JsonArrayAgg: + result = transformJsonArrayAgg(pstate, (JsonArrayAgg *) expr); + break; + + case T_JsonIsPredicate: + result = transformJsonIsPredicate(pstate, (JsonIsPredicate *) expr); + break; + + case T_JsonFuncExpr: + result = transformJsonFuncExpr(pstate, (JsonFuncExpr *) expr); + break; + + case T_JsonValueExpr: + result = transformJsonValueExpr(pstate, (JsonValueExpr *) expr); + break; + default: /* should not reach here */ elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); @@ -3485,3 +3529,1215 @@ ParseExprKindName(ParseExprKind exprKind) } return "unrecognized expression kind"; } + +/* + * Make string Const node from JSON encoding name. + * + * UTF8 is default encoding. + */ +static Const * +getJsonEncodingConst(JsonFormat *format) +{ + JsonEncoding encoding; + const char *enc; + Name encname = palloc(sizeof(NameData)); + + if (!format || + format->type == JS_FORMAT_DEFAULT || + format->encoding == JS_ENC_DEFAULT) + encoding = JS_ENC_UTF8; + else + encoding = format->encoding; + + switch (encoding) + { + case JS_ENC_UTF16: + enc = "UTF16"; + break; + case JS_ENC_UTF32: + enc = "UTF32"; + break; + case JS_ENC_UTF8: + default: + enc = "UTF8"; + break; + } + + namestrcpy(encname, enc); + + return makeConst(NAMEOID, -1, InvalidOid, NAMEDATALEN, + NameGetDatum(encname), false, false); +} + +/* + * Make bytea => text conversion using specified JSON format encoding. + */ +static Node * +makeJsonByteaToTextConversion(Node *expr, JsonFormat *format, int location) +{ + Const *encoding = getJsonEncodingConst(format); + FuncExpr *fexpr = makeFuncExpr(F_PG_CONVERT_FROM, TEXTOID, + list_make2(expr, encoding), + InvalidOid, InvalidOid, + COERCE_INTERNAL_CAST); + + fexpr->location = location; + + return (Node *) fexpr; +} + +static Node * +makeCaseTestExpr(Node *expr) +{ + CaseTestExpr *placeholder = makeNode(CaseTestExpr); + + placeholder->typeId = exprType(expr); + placeholder->typeMod = exprTypmod(expr); + placeholder->collation = exprCollation(expr); + + return (Node *) placeholder; +} + +/* + * Transform JSON value expression using specified input JSON format or + * default format otherwise. + */ +static Node * +transformJsonValueExprExt(ParseState *pstate, JsonValueExpr *ve, + JsonFormatType default_format, bool isarg, + Node **rawexpr) +{ + Node *expr = transformExprRecurse(pstate, (Node *) ve->expr); + JsonFormatType format; + Oid exprtype; + int location; + char typcategory; + bool typispreferred; + + if (exprType(expr) == UNKNOWNOID) + expr = coerce_to_specific_type(pstate, expr, TEXTOID, "JSON_VALUE_EXPR"); + + exprtype = exprType(expr); + location = exprLocation(expr); + + get_type_category_preferred(exprtype, &typcategory, &typispreferred); + + if (rawexpr) + { + /* + * Save a raw context item expression if it is needed for the isolation + * of error handling in the formatting stage. + */ + *rawexpr = expr; + assign_expr_collations(pstate, expr); + expr = makeCaseTestExpr(expr); + } + + if (ve->format.type != JS_FORMAT_DEFAULT) + { + if (ve->format.encoding != JS_ENC_DEFAULT && exprtype != BYTEAOID) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("JSON ENCODING clause is only allowed for bytea input type"), + parser_errposition(pstate, ve->format.location))); + + if (exprtype == JSONOID || exprtype == JSONBOID) + { + format = JS_FORMAT_DEFAULT; /* do not format json[b] types */ + ereport(WARNING, + (errmsg("FORMAT JSON has no effect for json and jsonb types"))); + } + else + format = ve->format.type; + } + else if (isarg) + { + /* Pass SQL/JSON item types directly without conversion to json[b]. */ + switch (exprtype) + { + case TEXTOID: + case NUMERICOID: + case BOOLOID: + case INT2OID: + case INT4OID: + case INT8OID: + case FLOAT4OID: + case FLOAT8OID: + case DATEOID: + case TIMEOID: + case TIMETZOID: + case TIMESTAMPOID: + case TIMESTAMPTZOID: + return expr; + + default: + if (typcategory == TYPCATEGORY_STRING) + return coerce_to_specific_type(pstate, expr, TEXTOID, + "JSON_VALUE_EXPR"); + /* else convert argument to json[b] type */ + break; + } + + format = default_format; + } + else if (exprtype == JSONOID || exprtype == JSONBOID) + format = JS_FORMAT_DEFAULT; /* do not format json[b] types */ + else + format = default_format; + + if (format != JS_FORMAT_DEFAULT) + { + Oid targettype = format == JS_FORMAT_JSONB ? JSONBOID : JSONOID; + Node *coerced; + FuncExpr *fexpr; + + if (!isarg && exprtype != BYTEAOID && typcategory != TYPCATEGORY_STRING) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg(ve->format.type == JS_FORMAT_DEFAULT ? + "cannot use non-string types with implicit FORMAT JSON clause" : + "cannot use non-string types with explicit FORMAT JSON clause"), + parser_errposition(pstate, ve->format.location >= 0 ? + ve->format.location : location))); + + /* Convert encoded JSON text from bytea. */ + if (format == JS_FORMAT_JSON && exprtype == BYTEAOID) + { + expr = makeJsonByteaToTextConversion(expr, &ve->format, location); + exprtype = TEXTOID; + } + + /* Try to coerce to the target type. */ + coerced = coerce_to_target_type(pstate, expr, exprtype, + targettype, -1, + COERCION_EXPLICIT, + COERCE_INTERNAL_CAST, + location); + + if (coerced) + expr = coerced; + else + { + + /* If coercion failed, use to_json()/to_jsonb() functions. */ + fexpr = makeFuncExpr(targettype == JSONOID ? F_TO_JSON : F_TO_JSONB, + targettype, list_make1(expr), + InvalidOid, InvalidOid, + COERCE_INTERNAL_CAST); + fexpr->location = location; + + expr = (Node *) fexpr; + } + + ve = copyObject(ve); + ve->expr = (Expr *) expr; + + expr = (Node *) ve; + } + + return expr; +} + +/* + * Transform JSON value expression using FORMAT JSON by default. + */ +static Node * +transformJsonValueExpr(ParseState *pstate, JsonValueExpr *jve) +{ + return transformJsonValueExprExt(pstate, jve, JS_FORMAT_JSON, false, NULL); +} + +/* + * Transform JSON value expression using unspecified format by default. + */ +static Node * +transformJsonValueExprDefault(ParseState *pstate, JsonValueExpr *jve) +{ + return transformJsonValueExprExt(pstate, jve, JS_FORMAT_DEFAULT, false, NULL); +} + +/* + * 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->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->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 void +transformJsonOutput(ParseState *pstate, const JsonOutput *output, + bool allow_format, JsonReturning *ret) +{ + /* if output clause is not specified, make default clause value */ + if (!output) + { + ret->format.type = JS_FORMAT_DEFAULT; + ret->format.encoding = JS_ENC_DEFAULT; + ret->format.location = -1; + ret->typid = InvalidOid; + ret->typmod = -1; + + return; + } + + *ret = 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.type == JS_FORMAT_DEFAULT) + /* assign JSONB format when returning jsonb, or JSON format otherwise */ + ret->format.type = + ret->typid == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON; + else + checkJsonOutputFormat(pstate, &ret->format, ret->typid, allow_format); +} + +/* + * Coerce json[b]-valued function expression to the output type. + */ +static Node * +coerceJsonFuncExpr(ParseState *pstate, Node *expr, 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.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_PG_CONVERT_TO, BYTEAOID, + list_make2(texpr, enc), + InvalidOid, InvalidOid, + COERCE_INTERNAL_CAST); + 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_INTERNAL_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 JsonCtorOpts * +makeJsonCtorOpts(const JsonReturning *returning, bool unique, + bool absent_on_null) +{ + JsonCtorOpts *opts = makeNode(JsonCtorOpts); + + opts->returning = *returning; + opts->unique = unique; + opts->absent_on_null = absent_on_null; + + return opts; +} + +/* + * 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 * +transformJsonObjectCtor(ParseState *pstate, JsonObjectCtor *ctor) +{ + JsonReturning returning; + FuncExpr *fexpr; + List *args = NIL; + Oid funcid; + Oid funcrettype; + + /* transform key-value pairs, if any */ + if (ctor->exprs) + { + ListCell *lc; + + /* append the first two arguments */ + args = lappend(args, makeBoolConst(ctor->absent_on_null, false)); + args = lappend(args, makeBoolConst(ctor->unique, false)); + + /* 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 = transformJsonValueExprDefault(pstate, kv->value); + + args = lappend(args, key); + args = lappend(args, val); + } + } + + transformJsonOutput(pstate, ctor->output, true, &returning); + + if (returning.format.type == JS_FORMAT_JSONB) + { + funcid = args ? F_JSONB_BUILD_OBJECT_EXT : F_JSONB_BUILD_OBJECT_NOARGS; + funcrettype = JSONBOID; + } + else + { + funcid = args ? F_JSON_BUILD_OBJECT_EXT : F_JSON_BUILD_OBJECT_NOARGS; + funcrettype = JSONOID; + } + + fexpr = makeFuncExpr(funcid, funcrettype, args, + InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); + fexpr->location = ctor->location; + fexpr->funcformat2 = FUNCFMT_JSON_OBJECT; + fexpr->funcformatopts = (Node *) makeJsonCtorOpts(&returning, + ctor->unique, + ctor->absent_on_null); + + return coerceJsonFuncExpr(pstate, (Node *) fexpr, &returning, true); +} + +/* + * Transform JSON_ARRAY(query [FORMAT] [RETURNING] [ON NULL]) into + * (SELECT JSON_ARRAYAGG(a [FORMAT] [RETURNING] [ON NULL]) FROM (query) q(a)) + */ +static Node * +transformJsonArrayQueryCtor(ParseState *pstate, JsonArrayQueryCtor *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->ctor.agg_order = NIL; + agg->ctor.output = ctor->output; + agg->absent_on_null = ctor->absent_on_null; + agg->ctor.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 * +transformJsonAggCtor(ParseState *pstate, JsonAggCtor *agg_ctor, + JsonReturning *returning, List *args, const char *aggfn, + Oid aggtype, FuncFormat format, JsonCtorOpts *formatopts) +{ + 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->winformat = format; + wfunc->winformatopts = (Node *) formatopts; + 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->aggformat = format; + aggref->aggformatopts = (Node *) formatopts; + aggref->location = agg_ctor->location; + + transformAggregateCall(pstate, aggref, args, agg_ctor->agg_order, false); + + node = (Node *) aggref; + } + + return coerceJsonFuncExpr(pstate, node, returning, true); +} + +/* + * 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; + + transformJsonOutput(pstate, agg->ctor.output, true, &returning); + + key = transformExprRecurse(pstate, (Node *) agg->arg->key); + val = transformJsonValueExprDefault(pstate, agg->arg->value); + + args = list_make4(key, + val, + makeBoolConst(agg->absent_on_null, false), + makeBoolConst(agg->unique, false)); + + if (returning.format.type == JS_FORMAT_JSONB) + { + aggfnname = "pg_catalog.jsonb_objectagg"; /* F_JSONB_OBJECTAGG */ + aggtype = JSONBOID; + } + else + { + aggfnname = "pg_catalog.json_objectagg"; /* F_JSON_OBJECTAGG; */ + aggtype = JSONOID; + } + + return transformJsonAggCtor(pstate, &agg->ctor, &returning, args, aggfnname, + aggtype, FUNCFMT_JSON_OBJECTAGG, + makeJsonCtorOpts(&returning, + 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; + + transformJsonOutput(pstate, agg->ctor.output, true, &returning); + + arg = transformJsonValueExprDefault(pstate, agg->arg); + + if (returning.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 transformJsonAggCtor(pstate, &agg->ctor, &returning, list_make1(arg), + aggfnname, aggtype, FUNCFMT_JSON_ARRAYAGG, + makeJsonCtorOpts(&returning, + 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 * +transformJsonArrayCtor(ParseState *pstate, JsonArrayCtor *ctor) +{ + JsonReturning returning; + FuncExpr *fexpr; + List *args = NIL; + Oid funcid; + Oid funcrettype; + + /* transform element expressions, if any */ + if (ctor->exprs) + { + ListCell *lc; + + /* append the first absent_on_null argument */ + args = lappend(args, makeBoolConst(ctor->absent_on_null, false)); + + /* transform and append element arguments */ + foreach(lc, ctor->exprs) + { + JsonValueExpr *jsval = castNode(JsonValueExpr, lfirst(lc)); + Node *val = transformJsonValueExprDefault(pstate, jsval); + + args = lappend(args, val); + } + } + + transformJsonOutput(pstate, ctor->output, true, &returning); + + if (returning.format.type == JS_FORMAT_JSONB) + { + funcid = args ? F_JSONB_BUILD_ARRAY_EXT : F_JSONB_BUILD_ARRAY_NOARGS; + funcrettype = JSONBOID; + } + else + { + funcid = args ? F_JSON_BUILD_ARRAY_EXT : F_JSON_BUILD_ARRAY_NOARGS; + funcrettype = JSONOID; + } + + fexpr = makeFuncExpr(funcid, funcrettype, args, + InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); + fexpr->location = ctor->location; + fexpr->funcformat2 = FUNCFMT_JSON_ARRAY; + fexpr->funcformatopts = (Node *) makeJsonCtorOpts(&returning, false, + ctor->absent_on_null); + + return coerceJsonFuncExpr(pstate, (Node *) fexpr, &returning, true); +} + +static const char * +JsonValueTypeStrings[] = +{ + "any", + "object", + "array", + "scalar", +}; + +static Const * +makeJsonValueTypeConst(JsonValueType type) +{ + return makeConst(TEXTOID, -1, InvalidOid, -1, + PointerGetDatum(cstring_to_text( + JsonValueTypeStrings[(int) type])), + false, false); +} + +/* + * Transform IS JSON predicate into + * json[b]_is_valid(json, value_type [, check_key_uniqueness]) call. + */ +static Node * +transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *pred) +{ + Node *expr = transformExprRecurse(pstate, pred->expr); + Oid exprtype = exprType(expr); + FuncExpr *fexpr; + JsonIsPredicateOpts *opts; + + /* prepare input document */ + if (exprtype == BYTEAOID) + { + expr = makeJsonByteaToTextConversion(expr, &pred->format, + exprLocation(expr)); + exprtype = TEXTOID; + } + else + { + char typcategory; + bool typispreferred; + + get_type_category_preferred(exprtype, &typcategory, &typispreferred); + + if (exprtype == UNKNOWNOID || typcategory == TYPCATEGORY_STRING) + { + expr = coerce_to_target_type(pstate, (Node *) expr, exprtype, + TEXTOID, -1, + COERCION_IMPLICIT, + COERCE_IMPLICIT_CAST, -1); + exprtype = TEXTOID; + } + + if (pred->format.encoding != JS_ENC_DEFAULT) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + parser_errposition(pstate, pred->format.location), + errmsg("cannot use JSON FORMAT ENCODING clause for non-bytea input types"))); + } + + expr = (Node *) makeJsonValueExpr((Expr *) expr, pred->format); + + /* make resulting expression */ + if (exprtype == TEXTOID || exprtype == JSONOID) + { + fexpr = makeFuncExpr(F_JSON_IS_VALID, BOOLOID, + list_make3(expr, + makeJsonValueTypeConst(pred->vtype), + makeBoolConst(pred->unique_keys, false)), + InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); + + fexpr->location = pred->location; + } + else if (exprtype == JSONBOID) + { + /* XXX the following expressions also can be used here: + * jsonb_type(jsonb) = 'type' (for object and array checks) + * CASE jsonb_type(jsonb) WHEN ... END (for scalars checks) + */ + fexpr = makeFuncExpr(F_JSONB_IS_VALID, BOOLOID, + list_make2(expr, + makeJsonValueTypeConst(pred->vtype)), + InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); + + fexpr->location = pred->location; + } + else + { + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot use type %s in IS JSON predicate", + format_type_be(exprtype)))); + return NULL; + } + + opts = makeNode(JsonIsPredicateOpts); + opts->unique_keys = pred->unique_keys; + opts->value_type = pred->vtype; + + fexpr->funcformat2 = FUNCFMT_IS_JSON; + fexpr->funcformatopts = (Node *) opts; + + return (Node *) fexpr; +} + +/* + * Transform a JSON PASSING clause. + */ +static void +transformJsonPassingArgs(ParseState *pstate, JsonFormatType format, List *args, + JsonPassing *passing) +{ + ListCell *lc; + + passing->values = NIL; + passing->names = NIL; + + foreach(lc, args) + { + JsonArgument *arg = castNode(JsonArgument, lfirst(lc)); + Node *expr = transformJsonValueExprExt(pstate, arg->val, + format, true, NULL); + + assign_expr_collations(pstate, expr); + + passing->values = lappend(passing->values, expr); + passing->names = lappend(passing->names, makeString(arg->name)); + } +} + +/* + * Transform a JSON BEHAVIOR clause. + */ +static JsonBehavior +transformJsonBehavior(ParseState *pstate, JsonBehavior *behavior, + JsonBehaviorType default_behavior) +{ + JsonBehavior b; + + b.btype = behavior ? behavior->btype : default_behavior; + b.default_expr = b.btype != JSON_BEHAVIOR_DEFAULT ? NULL : + transformExprRecurse(pstate, behavior->default_expr); + + return b; +} + +/* + * Common code for JSON_VALUE, JSON_QUERY, JSON_EXISTS transformation + * into a JsonExpr node. + */ +static JsonExpr * +transformJsonExprCommon(ParseState *pstate, JsonFuncExpr *func) +{ + JsonExpr *jsexpr = makeNode(JsonExpr); + Node *pathspec; + JsonFormatType format; + + if (func->common->pathname) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("JSON_TABLE path name is not allowed here"), + parser_errposition(pstate, func->location))); + + jsexpr->location = func->location; + jsexpr->op = func->op; + jsexpr->formatted_expr = transformJsonValueExprExt(pstate, + func->common->expr, + JS_FORMAT_JSON, + false, + &jsexpr->raw_expr); + + assign_expr_collations(pstate, jsexpr->formatted_expr); + + /* format is determined by context item type */ + format = exprType(jsexpr->formatted_expr) == JSONBOID ? + JS_FORMAT_JSONB : JS_FORMAT_JSON; + + if (jsexpr->formatted_expr == jsexpr->raw_expr) + jsexpr->formatted_expr = NULL; + + jsexpr->result_coercion = NULL; + jsexpr->omit_quotes = false; + + jsexpr->format = func->common->expr->format; + + pathspec = transformExprRecurse(pstate, func->common->pathspec); + + jsexpr->path_spec = + coerce_to_target_type(pstate, pathspec, exprType(pathspec), + JSONPATHOID, -1, + COERCION_EXPLICIT, COERCE_IMPLICIT_CAST, + exprLocation(pathspec)); + if (!jsexpr->path_spec) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("JSON path expression must be type %s, not type %s", + "jsonpath", format_type_be(exprType(pathspec))), + parser_errposition(pstate, exprLocation(pathspec)))); + + /* transform and coerce to json[b] passing arguments */ + transformJsonPassingArgs(pstate, format, func->common->passing, + &jsexpr->passing); + + if (func->op != IS_JSON_EXISTS) + jsexpr->on_empty = transformJsonBehavior(pstate, func->on_empty, + JSON_BEHAVIOR_NULL); + + jsexpr->on_error = transformJsonBehavior(pstate, func->on_error, + func->op == IS_JSON_EXISTS ? + JSON_BEHAVIOR_FALSE : + JSON_BEHAVIOR_NULL); + + return jsexpr; +} + +/* + * Assign default JSON returning type from the specified format or from + * the context item type. + */ +static void +assignDefaultJsonReturningType(Node *context_item, JsonFormat *context_format, + JsonReturning *ret) +{ + bool is_jsonb; + + ret->format = *context_format; + + if (ret->format.type == JS_FORMAT_DEFAULT) + is_jsonb = exprType(context_item) == JSONBOID; + else + is_jsonb = ret->format.type == JS_FORMAT_JSONB; + + ret->typid = is_jsonb ? JSONBOID : JSONOID; + ret->typmod = -1; +} + +/* + * Try to coerce expression to the output type or + * use json_populate_type() for composite, array and domain types or + * use coercion via I/O. + */ +static JsonCoercion * +coerceJsonExpr(ParseState *pstate, Node *expr, JsonReturning *returning) +{ + char typtype; + JsonCoercion *coercion = makeNode(JsonCoercion); + + coercion->expr = coerceJsonFuncExpr(pstate, expr, returning, false); + + if (coercion->expr) + { + if (coercion->expr == expr) + coercion->expr = NULL; + + return coercion; + } + + typtype = get_typtype(returning->typid); + + if (returning->typid == RECORDOID || + typtype == TYPTYPE_COMPOSITE || + typtype == TYPTYPE_DOMAIN || + type_is_array(returning->typid)) + coercion->via_populate = true; + else + coercion->via_io = true; + + return coercion; +} + +/* + * Transform a JSON output clause of JSON_VALUE, JSON_QUERY, JSON_EXISTS. + */ +static void +transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func, + JsonExpr *jsexpr) +{ + Node *expr = jsexpr->formatted_expr ? + jsexpr->formatted_expr : jsexpr->raw_expr; + + transformJsonOutput(pstate, func->output, false, &jsexpr->returning); + + /* JSON_VALUE returns text by default */ + if (func->op == IS_JSON_VALUE && !OidIsValid(jsexpr->returning.typid)) + { + jsexpr->returning.typid = TEXTOID; + jsexpr->returning.typmod = -1; + } + + if (OidIsValid(jsexpr->returning.typid)) + { + JsonReturning ret; + + if (func->op == IS_JSON_VALUE && + jsexpr->returning.typid != JSONOID && + jsexpr->returning.typid != JSONBOID) + { + /* Forced coercion via I/O for JSON_VALUE for non-JSON types */ + jsexpr->result_coercion = makeNode(JsonCoercion); + jsexpr->result_coercion->expr = NULL; + jsexpr->result_coercion->via_io = true; + return; + } + + assignDefaultJsonReturningType(jsexpr->raw_expr, &jsexpr->format, &ret); + + if (ret.typid != jsexpr->returning.typid || + ret.typmod != jsexpr->returning.typmod) + { + Node *placeholder = makeCaseTestExpr(expr); + + Assert(((CaseTestExpr *) placeholder)->typeId == ret.typid); + Assert(((CaseTestExpr *) placeholder)->typeMod == ret.typmod); + + jsexpr->result_coercion = coerceJsonExpr(pstate, placeholder, + &jsexpr->returning); + } + } + else + assignDefaultJsonReturningType(jsexpr->raw_expr, &jsexpr->format, + &jsexpr->returning); +} + +/* + * Coerce a expression in JSON DEFAULT behavior to the target output type. + */ +static Node * +coerceDefaultJsonExpr(ParseState *pstate, JsonExpr *jsexpr, Node *defexpr) +{ + int location; + Oid exprtype; + + if (!defexpr) + return NULL; + + exprtype = exprType(defexpr); + location = exprLocation(defexpr); + + if (location < 0) + location = jsexpr->location; + + defexpr = coerce_to_target_type(pstate, + defexpr, + exprtype, + jsexpr->returning.typid, + jsexpr->returning.typmod, + COERCION_EXPLICIT, + COERCE_INTERNAL_CAST, + location); + + if (!defexpr) + ereport(ERROR, + (errcode(ERRCODE_CANNOT_COERCE), + errmsg("cannot cast DEFAULT expression type %s to %s", + format_type_be(exprtype), + format_type_be(jsexpr->returning.typid)), + parser_errposition(pstate, location))); + + return defexpr; +} + +/* + * Initialize SQL/JSON item coercion from the SQL type "typid" to the target + * "returning" type. + */ +static JsonCoercion * +initJsonItemCoercion(ParseState *pstate, Oid typid, JsonReturning *returning) +{ + Node *expr; + + if (typid == UNKNOWNOID) + { + expr = (Node *) makeNullConst(UNKNOWNOID, -1, InvalidOid); + } + else + { + CaseTestExpr *placeholder = makeNode(CaseTestExpr); + + placeholder->typeId = typid; + placeholder->typeMod = -1; + placeholder->collation = InvalidOid; + + expr = (Node *) placeholder; + } + + return coerceJsonExpr(pstate, expr, returning); +} + +static void +initJsonItemCoercions(ParseState *pstate, JsonItemCoercions *coercions, + JsonReturning *returning, Oid contextItemTypeId) +{ + struct + { + JsonCoercion **coercion; + Oid typid; + } *p, + coercionTypids[] = + { + { &coercions->null, UNKNOWNOID }, + { &coercions->string, TEXTOID }, + { &coercions->numeric, NUMERICOID }, + { &coercions->boolean, BOOLOID }, + { &coercions->date, DATEOID }, + { &coercions->time, TIMEOID }, + { &coercions->timetz, TIMETZOID }, + { &coercions->timestamp, TIMESTAMPOID }, + { &coercions->timestamptz, TIMESTAMPTZOID }, + { &coercions->composite, contextItemTypeId }, + { NULL, InvalidOid } + }; + + for (p = coercionTypids; p->coercion; p++) + *p->coercion = initJsonItemCoercion(pstate, p->typid, returning); +} + +/* + * Transform JSON_VALUE, JSON_QUERY, JSON_EXISTS functions into a JsonExpr node. + */ +static Node * +transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func) +{ + JsonExpr *jsexpr = transformJsonExprCommon(pstate, func); + Node *contextItemExpr = + jsexpr->formatted_expr ? jsexpr->formatted_expr : jsexpr->raw_expr; + const char *func_name = NULL; + + switch (func->op) + { + case IS_JSON_VALUE: + func_name = "JSON_VALUE"; + + transformJsonFuncExprOutput(pstate, func, jsexpr); + + jsexpr->returning.format.type = JS_FORMAT_DEFAULT; + jsexpr->returning.format.encoding = JS_ENC_DEFAULT; + + jsexpr->on_empty.default_expr = + coerceDefaultJsonExpr(pstate, jsexpr, + jsexpr->on_empty.default_expr); + + jsexpr->on_error.default_expr = + coerceDefaultJsonExpr(pstate, jsexpr, + jsexpr->on_error.default_expr); + + jsexpr->coercions = makeNode(JsonItemCoercions); + initJsonItemCoercions(pstate, jsexpr->coercions, &jsexpr->returning, + exprType(contextItemExpr)); + + break; + + case IS_JSON_QUERY: + func_name = "JSON_QUERY"; + + transformJsonFuncExprOutput(pstate, func, jsexpr); + + jsexpr->wrapper = func->wrapper; + jsexpr->omit_quotes = func->omit_quotes; + + break; + + case IS_JSON_EXISTS: + func_name = "JSON_EXISTS"; + + jsexpr->returning.format.type = JS_FORMAT_DEFAULT; + jsexpr->returning.format.encoding = JS_ENC_DEFAULT; + jsexpr->returning.format.location = -1; + jsexpr->returning.typid = BOOLOID; + jsexpr->returning.typmod = -1; + + break; + } + + if (exprType(contextItemExpr) != JSONBOID) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("%s() is not yet implemented for json type", func_name), + parser_errposition(pstate, func->location))); + + return (Node *) jsexpr; +} diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index b8702d9..1a7e845 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -1924,6 +1924,34 @@ FigureColnameInternal(Node *node, char **name) case T_XmlSerialize: *name = "xmlserialize"; return 2; + case T_JsonObjectCtor: + *name = "json_object"; + return 2; + case T_JsonArrayCtor: + case T_JsonArrayQueryCtor: + *name = "json_array"; + return 2; + case T_JsonObjectAgg: + *name = "json_objectagg"; + return 2; + case T_JsonArrayAgg: + *name = "json_arrayagg"; + return 2; + case T_JsonFuncExpr: + /* make SQL/JSON functions act like a regular function */ + switch (((JsonFuncExpr *) node)->op) + { + case IS_JSON_QUERY: + *name = "json_query"; + return 2; + case IS_JSON_VALUE: + *name = "json_value"; + return 2; + case IS_JSON_EXISTS: + *name = "json_exists"; + return 2; + } + break; default: break; } diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c index db30483..3be9d6c 100644 --- a/src/backend/parser/parser.c +++ b/src/backend/parser/parser.c @@ -24,7 +24,6 @@ #include "parser/gramparse.h" #include "parser/parser.h" - /* * raw_parser * Given a query in string form, do lexical and grammatical analysis. @@ -117,6 +116,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner) case WITH: cur_token_length = 4; break; + case WITHOUT: + cur_token_length = 7; + break; default: return cur_token; } @@ -188,8 +190,22 @@ 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; + } return cur_token; diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index 4850e09..74f36af 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -13,6 +13,7 @@ */ #include "postgres.h" +#include "access/hash.h" #include "access/htup_details.h" #include "access/transam.h" #include "catalog/pg_type.h" @@ -66,6 +67,23 @@ typedef enum /* type categories for datum_to_json */ JSONTYPE_OTHER /* all else */ } JsonTypeCategory; +/* Context for key uniqueness check */ +typedef struct JsonUniqueCheckContext +{ + struct JsonKeyInfo + { + int offset; /* key offset: + * in result if positive, + * in skipped_keys if negative */ + int length; /* key length */ + } *keys; /* key info array */ + int nkeys; /* number of processed keys */ + int nallocated; /* number of allocated keys in array */ + StringInfo result; /* resulting json */ + StringInfoData skipped_keys; /* skipped keys with NULL values */ + MemoryContext mcxt; /* context for saving skipped keys */ +} JsonUniqueCheckContext; + typedef struct JsonAggState { StringInfo str; @@ -73,8 +91,23 @@ typedef struct JsonAggState Oid key_output_func; JsonTypeCategory val_category; Oid val_output_func; + JsonUniqueCheckContext unique_check; } JsonAggState; +/* Element of object stack for key uniqueness check */ +typedef struct JsonObjectFields +{ + struct JsonObjectFields *parent; + HTAB *fields; +} JsonObjectFields; + +/* State for key uniqueness check */ +typedef struct JsonUniqueState +{ + JsonLexContext *lex; + JsonObjectFields *stack; +} JsonUniqueState; + static inline void json_lex(JsonLexContext *lex); static inline void json_lex_string(JsonLexContext *lex); static inline void json_lex_number(JsonLexContext *lex, char *s, @@ -1968,8 +2001,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; @@ -2009,9 +2042,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)) { @@ -2023,7 +2061,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)) { @@ -2041,6 +2079,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 */ @@ -2064,18 +2121,115 @@ json_agg_finalfn(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(catenate_stringinfo_string(state->str, "]")); } +static inline void +json_unique_check_init(JsonUniqueCheckContext *cxt, + StringInfo result, int nkeys) +{ + cxt->mcxt = CurrentMemoryContext; + cxt->nkeys = 0; + cxt->nallocated = nkeys ? nkeys : 16; + cxt->keys = palloc(sizeof(*cxt->keys) * cxt->nallocated); + cxt->result = result; + cxt->skipped_keys.data = NULL; +} + +static inline void +json_unique_check_free(JsonUniqueCheckContext *cxt) +{ + if (cxt->keys) + pfree(cxt->keys); + + if (cxt->skipped_keys.data) + pfree(cxt->skipped_keys.data); +} + +/* On-demand initialization of skipped_keys StringInfo structure */ +static inline StringInfo +json_unique_check_get_skipped_keys(JsonUniqueCheckContext *cxt) +{ + StringInfo out = &cxt->skipped_keys; + + if (!out->data) + { + MemoryContext oldcxt = MemoryContextSwitchTo(cxt->mcxt); + initStringInfo(out); + MemoryContextSwitchTo(oldcxt); + } + + return out; +} + +/* + * Save current key offset (key is not yet appended) to the key list, key + * length is saved later in json_unique_check_key() when the key is appended. + */ +static inline void +json_unique_check_save_key_offset(JsonUniqueCheckContext *cxt, StringInfo out) +{ + if (cxt->nkeys >= cxt->nallocated) + { + cxt->nallocated *= 2; + cxt->keys = repalloc(cxt->keys, sizeof(*cxt->keys) * cxt->nallocated); + } + + cxt->keys[cxt->nkeys++].offset = out->len; +} + +/* + * Check uniqueness of key already appended to 'out' StringInfo. + */ +static inline void +json_unique_check_key(JsonUniqueCheckContext *cxt, StringInfo out) +{ + struct JsonKeyInfo *keys = cxt->keys; + int curr = cxt->nkeys - 1; + int offset = keys[curr].offset; + int length = out->len - offset; + char *curr_key = &out->data[offset]; + int i; + + keys[curr].length = length; /* save current key length */ + + if (out == &cxt->skipped_keys) + /* invert offset for skipped keys for their recognition */ + keys[curr].offset = -keys[curr].offset; + + /* check collisions with previous keys */ + for (i = 0; i < curr; i++) + { + char *prev_key; + + if (cxt->keys[i].length != length) + continue; + + offset = cxt->keys[i].offset; + + prev_key = offset > 0 + ? &cxt->result->data[offset] + : &cxt->skipped_keys.data[-offset]; + + if (!memcmp(curr_key, prev_key, length)) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE), + errmsg("duplicate JSON key %s", curr_key))); + } +} + /* * 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; if (!AggCheckCallContext(fcinfo, &aggcontext)) { @@ -2096,6 +2250,10 @@ json_object_agg_transfn(PG_FUNCTION_ARGS) oldcontext = MemoryContextSwitchTo(aggcontext); state = (JsonAggState *) palloc(sizeof(JsonAggState)); state->str = makeStringInfo(); + if (unique_keys) + json_unique_check_init(&state->unique_check, state->str, 0); + else + memset(&state->unique_check, 0, sizeof(state->unique_check)); MemoryContextSwitchTo(oldcontext); arg_type = get_fn_expr_argtype(fcinfo->flinfo, 1); @@ -2123,7 +2281,6 @@ json_object_agg_transfn(PG_FUNCTION_ARGS) else { state = (JsonAggState *) PG_GETARG_POINTER(0); - appendStringInfoString(state->str, ", "); } /* @@ -2139,11 +2296,41 @@ 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_check_get_skipped_keys(&state->unique_check); + } + else + { + out = state->str; + + if (out->len > 2) + appendStringInfoString(out, ", "); + } + arg = PG_GETARG_DATUM(1); - datum_to_json(arg, false, state->str, state->key_category, + if (unique_keys) + json_unique_check_save_key_offset(&state->unique_check, out); + + datum_to_json(arg, false, out, state->key_category, state->key_output_func, true); + if (unique_keys) + { + json_unique_check_key(&state->unique_check, out); + + if (skip) + PG_RETURN_POINTER(state); + } + appendStringInfoString(state->str, " : "); if (PG_ARGISNULL(2)) @@ -2158,6 +2345,26 @@ json_object_agg_transfn(PG_FUNCTION_ARGS) } /* + * json_object_agg aggregate function + */ +Datum +json_object_agg_transfn(PG_FUNCTION_ARGS) +{ + return json_object_agg_transfn_worker(fcinfo, false, false); +} + +/* + * json_objectagg aggregate function + */ +Datum +json_objectagg_transfn(PG_FUNCTION_ARGS) +{ + return json_object_agg_transfn_worker(fcinfo, + PG_GETARG_BOOL(3), + PG_GETARG_BOOL(4)); +} + +/* * json_object_agg final function. */ Datum @@ -2174,6 +2381,8 @@ json_object_agg_finalfn(PG_FUNCTION_ARGS) if (state == NULL) PG_RETURN_NULL(); + json_unique_check_free(&state->unique_check); + /* Else return state with appropriate object terminator added */ PG_RETURN_TEXT_P(catenate_stringinfo_string(state->str, " }")); } @@ -2198,11 +2407,9 @@ catenate_stringinfo_string(StringInfo buffer, const char *addon) return result; } -/* - * SQL function json_build_object(variadic "any") - */ -Datum -json_build_object(PG_FUNCTION_ARGS) +static Datum +json_build_object_worker(FunctionCallInfo fcinfo, int first_vararg, + bool absent_on_null, bool unique_keys) { int nargs = PG_NARGS(); int i; @@ -2211,9 +2418,11 @@ json_build_object(PG_FUNCTION_ARGS) Datum *args; bool *nulls; Oid *types; + JsonUniqueCheckContext unique_check; /* fetch argument values to build the object */ - nargs = extract_variadic_args(fcinfo, 0, false, &args, &types, &nulls); + nargs = extract_variadic_args(fcinfo, first_vararg, false, + &args, &types, &nulls); if (nargs < 0) PG_RETURN_NULL(); @@ -2228,19 +2437,53 @@ json_build_object(PG_FUNCTION_ARGS) appendStringInfoChar(result, '{'); + if (unique_keys) + json_unique_check_init(&unique_check, result, nargs / 2); + for (i = 0; i < nargs; i += 2) { - appendStringInfoString(result, sep); - sep = ", "; + StringInfo out; + bool skip; + + /* 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_check_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", first_vararg + i + 1), errhint("Object keys should be text."))); - add_json(args[i], false, result, types[i], true); + if (unique_keys) + /* save key offset before key appending */ + json_unique_check_save_key_offset(&unique_check, out); + + add_json(args[i], false, out, types[i], true); + + if (unique_keys) + { + /* check key uniqueness after key appending */ + json_unique_check_key(&unique_check, out); + + if (skip) + continue; + } appendStringInfoString(result, " : "); @@ -2250,23 +2493,43 @@ json_build_object(PG_FUNCTION_ARGS) appendStringInfoChar(result, '}'); + if (unique_keys) + json_unique_check_free(&unique_check); + PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len)); } /* - * degenerate case of json_build_object where it gets 0 arguments. + * SQL function json_build_object(variadic "any") */ Datum -json_build_object_noargs(PG_FUNCTION_ARGS) +json_build_object(PG_FUNCTION_ARGS) { - PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2)); + return json_build_object_worker(fcinfo, 0, false, false); } /* - * SQL function json_build_array(variadic "any") + * SQL function json_build_object_ext(absent_on_null bool, unique bool, variadic "any") */ Datum -json_build_array(PG_FUNCTION_ARGS) +json_build_object_ext(PG_FUNCTION_ARGS) +{ + return json_build_object_worker(fcinfo, 2, + PG_GETARG_BOOL(0), PG_GETARG_BOOL(1)); +} + +/* + * degenerate case of json_build_object where it gets 0 arguments. + */ +Datum +json_build_object_noargs(PG_FUNCTION_ARGS) +{ + PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2)); +} + +static Datum +json_build_array_worker(FunctionCallInfo fcinfo, int first_vararg, + bool absent_on_null) { int nargs; int i; @@ -2277,7 +2540,8 @@ json_build_array(PG_FUNCTION_ARGS) Oid *types; /* fetch argument values to build the array */ - nargs = extract_variadic_args(fcinfo, 0, false, &args, &types, &nulls); + nargs = extract_variadic_args(fcinfo, first_vararg, false, + &args, &types, &nulls); if (nargs < 0) PG_RETURN_NULL(); @@ -2288,6 +2552,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); @@ -2299,6 +2566,24 @@ json_build_array(PG_FUNCTION_ARGS) } /* + * SQL function json_build_array(variadic "any") + */ +Datum +json_build_array(PG_FUNCTION_ARGS) +{ + return json_build_array_worker(fcinfo, 0, false); +} + +/* + * SQL function json_build_array_ext(absent_on_null bool, variadic "any") + */ +Datum +json_build_array_ext(PG_FUNCTION_ARGS) +{ + return json_build_array_worker(fcinfo, 1, PG_GETARG_BOOL(0)); +} + +/* * degenerate case of json_build_array where it gets 0 arguments. */ Datum @@ -2529,6 +2814,178 @@ escape_json(StringInfo buf, const char *str) appendStringInfoCharMacro(buf, '"'); } +/* Functions implementing hash table for key uniqueness check */ +static int +json_unique_hash_match(const void *key1, const void *key2, Size keysize) +{ + return strcmp(*(const char **) key1, *(const char **) key2); +} + +static void * +json_unique_hash_keycopy(void *dest, const void *src, Size keysize) +{ + *(const char **) dest = pstrdup(*(const char **) src); + + return dest; +} + +static uint32 +json_unique_hash(const void *key, Size keysize) +{ + const char *s = *(const char **) key; + + return DatumGetUInt32(hash_any((const unsigned char *) s, (int) strlen(s))); +} + +/* Semantic actions for key uniqueness check */ +static void +json_unique_object_start(void *_state) +{ + JsonUniqueState *state = _state; + JsonObjectFields *obj = palloc(sizeof(*obj)); + HASHCTL ctl; + + memset(&ctl, 0, sizeof(ctl)); + ctl.keysize = sizeof(char *); + ctl.entrysize = sizeof(char *); + ctl.hcxt = CurrentMemoryContext; + ctl.hash = json_unique_hash; + ctl.keycopy = json_unique_hash_keycopy; + ctl.match = json_unique_hash_match; + obj->fields = hash_create("json object hashtable", + 32, + &ctl, + HASH_ELEM | HASH_CONTEXT | + HASH_FUNCTION | HASH_COMPARE | HASH_KEYCOPY); + obj->parent = state->stack; /* push object to stack */ + + state->stack = obj; +} + +static void +json_unique_object_end(void *_state) +{ + JsonUniqueState *state = _state; + + hash_destroy(state->stack->fields); + + state->stack = state->stack->parent; /* pop object from stack */ +} + +static void +json_unique_object_field_start(void *_state, char *field, bool isnull) +{ + JsonUniqueState *state = _state; + bool found; + + /* find key collision in the current object */ + (void) hash_search(state->stack->fields, &field, HASH_ENTER, &found); + + if (found) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("duplicate JSON key \"%s\"", field), + report_json_context(state->lex))); +} + +/* + * json_is_valid -- check json text validity, its value type and key uniqueness + */ +Datum +json_is_valid(PG_FUNCTION_ARGS) +{ + text *json = PG_GETARG_TEXT_P(0); + text *type = PG_GETARG_TEXT_P(1); + bool unique = PG_GETARG_BOOL(2); + MemoryContext mcxt = CurrentMemoryContext; + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + if (!PG_ARGISNULL(1) && + strncmp("any", VARDATA(type), VARSIZE_ANY_EXHDR(type))) + { + JsonLexContext *lex; + JsonTokenType tok; + + lex = makeJsonLexContext(json, false); + + /* Lex exactly one token from the input and check its type. */ + PG_TRY(); + { + json_lex(lex); + } + PG_CATCH(); + { + if (ERRCODE_TO_CATEGORY(geterrcode()) == ERRCODE_DATA_EXCEPTION) + { + FlushErrorState(); + MemoryContextSwitchTo(mcxt); + PG_RETURN_BOOL(false); /* invalid json */ + } + PG_RE_THROW(); + } + PG_END_TRY(); + + tok = lex_peek(lex); + + if (!strncmp("object", VARDATA(type), VARSIZE_ANY_EXHDR(type))) + { + if (tok != JSON_TOKEN_OBJECT_START) + PG_RETURN_BOOL(false); /* json is not a object */ + } + else if (!strncmp("array", VARDATA(type), VARSIZE_ANY_EXHDR(type))) + { + if (tok != JSON_TOKEN_ARRAY_START) + PG_RETURN_BOOL(false); /* json is not an array */ + } + else + { + if (tok == JSON_TOKEN_OBJECT_START || + tok == JSON_TOKEN_ARRAY_START) + PG_RETURN_BOOL(false); /* json is not a scalar */ + } + } + + /* do full parsing pass only for uniqueness check or JSON text validation */ + if (unique || + get_fn_expr_argtype(fcinfo->flinfo, 0) != JSONOID) + { + JsonLexContext *lex = makeJsonLexContext(json, unique); + JsonSemAction uniqueSemAction = {0}; + JsonUniqueState state; + + if (unique) + { + state.lex = lex; + state.stack = NULL; + + uniqueSemAction.semstate = &state; + uniqueSemAction.object_start = json_unique_object_start; + uniqueSemAction.object_field_start = json_unique_object_field_start; + uniqueSemAction.object_end = json_unique_object_end; + } + + PG_TRY(); + { + pg_parse_json(lex, unique ? &uniqueSemAction : &nullSemAction); + } + PG_CATCH(); + { + if (ERRCODE_TO_CATEGORY(geterrcode()) == ERRCODE_DATA_EXCEPTION) + { + FlushErrorState(); + MemoryContextSwitchTo(mcxt); + PG_RETURN_BOOL(false); /* invalid json or key collision found */ + } + PG_RE_THROW(); + } + PG_END_TRY(); + } + + PG_RETURN_BOOL(true); /* ok */ +} + /* * SQL function json_typeof(json) -> text * diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index 00a7f3a..50544de 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -52,6 +52,16 @@ typedef enum /* type categories for datum_to_jsonb */ JSONBTYPE_OTHER /* all else */ } JsonbTypeCategory; +/* Context for key uniqueness check */ +typedef struct JsonbUniqueCheckContext +{ + JsonbValue *obj; /* object containing skipped keys also */ + int *skipped_keys; /* array of skipped key-value pair indices */ + int skipped_keys_allocated; + int skipped_keys_count; + MemoryContext mcxt; /* context for saving skipped keys */ +} JsonbUniqueCheckContext; + typedef struct JsonbAggState { JsonbInState *res; @@ -59,6 +69,7 @@ typedef struct JsonbAggState Oid key_output_func; JsonbTypeCategory val_category; Oid val_output_func; + JsonbUniqueCheckContext unique_check; } JsonbAggState; static inline Datum jsonb_from_cstring(char *json, int len); @@ -1121,11 +1132,121 @@ to_jsonb(PG_FUNCTION_ARGS) PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); } +static inline void +jsonb_unique_check_init(JsonbUniqueCheckContext *cxt, JsonbValue *obj, + MemoryContext mcxt) +{ + cxt->mcxt = mcxt; + cxt->obj = obj; + cxt->skipped_keys = NULL; + cxt->skipped_keys_count = 0; + cxt->skipped_keys_allocated = 0; +} + /* - * SQL function jsonb_build_object(variadic "any") + * Save the index of the skipped key-value pair that has just been appended + * to the object. */ -Datum -jsonb_build_object(PG_FUNCTION_ARGS) +static inline void +jsonb_unique_check_add_skipped(JsonbUniqueCheckContext *cxt) +{ + /* + * Make a room for the skipped index plus one additional index + * (see jsonb_unique_check_remove_skipped_keys()). + */ + if (cxt->skipped_keys_count + 1 >= cxt->skipped_keys_allocated) + { + if (cxt->skipped_keys_allocated) + { + cxt->skipped_keys_allocated *= 2; + cxt->skipped_keys = repalloc(cxt->skipped_keys, + sizeof(*cxt->skipped_keys) * + cxt->skipped_keys_allocated); + } + else + { + cxt->skipped_keys_allocated = 16; + cxt->skipped_keys = MemoryContextAlloc(cxt->mcxt, + sizeof(*cxt->skipped_keys) * + cxt->skipped_keys_allocated); + } + } + + cxt->skipped_keys[cxt->skipped_keys_count++] = cxt->obj->val.object.nPairs; +} + +/* + * Check uniqueness of the key that has just been appended to the object. + */ +static inline void +jsonb_unique_check_key(JsonbUniqueCheckContext *cxt, bool skip) +{ + JsonbPair *pair = cxt->obj->val.object.pairs; + /* nPairs is incremented only after the value is appended */ + JsonbPair *last = &pair[cxt->obj->val.object.nPairs]; + + for (; pair < last; pair++) + if (pair->key.val.string.len == + last->key.val.string.len && + !memcmp(pair->key.val.string.val, + last->key.val.string.val, + last->key.val.string.len)) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE), + errmsg("duplicate JSON key \"%*s\"", + last->key.val.string.len, + last->key.val.string.val))); + + if (skip) + { + /* save skipped key index */ + jsonb_unique_check_add_skipped(cxt); + + /* add dummy null value for the skipped key */ + last->value.type = jbvNull; + cxt->obj->val.object.nPairs++; + } +} + +/* + * Remove skipped key-value pairs from the resulting object. + */ +static void +jsonb_unique_check_remove_skipped_keys(JsonbUniqueCheckContext *cxt) +{ + int *skipped_keys = cxt->skipped_keys; + int skipped_keys_count = cxt->skipped_keys_count; + + if (!skipped_keys_count) + return; + + if (cxt->obj->val.object.nPairs > skipped_keys_count) + { + /* remove skipped key-value pairs */ + JsonbPair *pairs = cxt->obj->val.object.pairs; + int i; + + /* save total pair count into the last element of skipped_keys */ + Assert(cxt->skipped_keys_count < cxt->skipped_keys_allocated); + cxt->skipped_keys[cxt->skipped_keys_count] = cxt->obj->val.object.nPairs; + + for (i = 0; i < skipped_keys_count; i++) + { + int skipped_key = skipped_keys[i]; + int nkeys = skipped_keys[i + 1] - skipped_key - 1; + + memmove(&pairs[skipped_key - i], + &pairs[skipped_key + 1], + sizeof(JsonbPair) * nkeys); + } + } + + cxt->obj->val.object.nPairs -= skipped_keys_count; +} + +static Datum +jsonb_build_object_worker(FunctionCallInfo fcinfo, int first_vararg, + bool absent_on_null, bool unique_keys) { int nargs; int i; @@ -1133,9 +1254,11 @@ jsonb_build_object(PG_FUNCTION_ARGS) Datum *args; bool *nulls; Oid *types; + JsonbUniqueCheckContext unique_check; /* build argument values to build the object */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = extract_variadic_args(fcinfo, first_vararg, true, + &args, &types, &nulls); if (nargs < 0) PG_RETURN_NULL(); @@ -1150,26 +1273,69 @@ jsonb_build_object(PG_FUNCTION_ARGS) result.res = pushJsonbValue(&result.parseState, WJB_BEGIN_OBJECT, NULL); + /* if (unique_keys) */ + jsonb_unique_check_init(&unique_check, result.res, CurrentMemoryContext); + 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))); + errmsg("argument %d: key must not be null", + first_vararg + 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); + if (unique_keys) + { + jsonb_unique_check_key(&unique_check, skip); + + if (skip) + continue; /* do not process the value if the key is skipped */ + } + /* process value */ add_jsonb(args[i + 1], nulls[i + 1], &result, types[i + 1], false); } + if (unique_keys && absent_on_null) + jsonb_unique_check_remove_skipped_keys(&unique_check); + result.res = pushJsonbValue(&result.parseState, WJB_END_OBJECT, NULL); PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); } /* + * SQL function jsonb_build_object(variadic "any") + */ +Datum +jsonb_build_object(PG_FUNCTION_ARGS) +{ + return jsonb_build_object_worker(fcinfo, 0, false, false); +} + +/* + * SQL function jsonb_build_object_ext(absent_on_null bool, unique bool, variadic "any") + */ +Datum +jsonb_build_object_ext(PG_FUNCTION_ARGS) +{ + return jsonb_build_object_worker(fcinfo, 2, + PG_GETARG_BOOL(0), PG_GETARG_BOOL(1)); +} + +/* * degenerate case of jsonb_build_object where it gets 0 arguments. */ Datum @@ -1185,11 +1351,9 @@ 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) +static Datum +jsonb_build_array_worker(FunctionCallInfo fcinfo, int first_vararg, + bool absent_on_null) { int nargs; int i; @@ -1199,7 +1363,8 @@ jsonb_build_array(PG_FUNCTION_ARGS) Oid *types; /* build argument values to build the array */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = extract_variadic_args(fcinfo, first_vararg, true, + &args, &types, &nulls); if (nargs < 0) PG_RETURN_NULL(); @@ -1209,7 +1374,12 @@ jsonb_build_array(PG_FUNCTION_ARGS) 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); @@ -1217,6 +1387,24 @@ jsonb_build_array(PG_FUNCTION_ARGS) } /* + * SQL function jsonb_build_array(variadic "any") + */ +Datum +jsonb_build_array(PG_FUNCTION_ARGS) +{ + return jsonb_build_array_worker(fcinfo, 0, false); +} + +/* + * SQL function jsonb_build_array_ext(absent_on_null bool, variadic "any") + */ +Datum +jsonb_build_array_ext(PG_FUNCTION_ARGS) +{ + return jsonb_build_array_worker(fcinfo, 1, PG_GETARG_BOOL(0)); +} + +/* * degenerate case of jsonb_build_array where it gets 0 arguments. */ Datum @@ -1467,12 +1655,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; @@ -1520,6 +1704,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); @@ -1589,6 +1776,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) { @@ -1621,11 +1826,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; @@ -1639,6 +1842,7 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS) *jbval; JsonbValue v; JsonbIteratorToken type; + bool skip; if (!AggCheckCallContext(fcinfo, &aggcontext)) { @@ -1658,6 +1862,11 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS) state->res = result; result->res = pushJsonbValue(&result->parseState, WJB_BEGIN_OBJECT, NULL); + if (unique_keys) + jsonb_unique_check_init(&state->unique_check, result->res, + aggcontext); + else + memset(&state->unique_check, 0, sizeof(state->unique_check)); MemoryContextSwitchTo(oldcontext); arg_type = get_fn_expr_argtype(fcinfo->flinfo, 1); @@ -1693,6 +1902,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)); @@ -1748,6 +1966,18 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS) } result->res = pushJsonbValue(&result->parseState, WJB_KEY, &v); + + if (unique_keys) + { + jsonb_unique_check_key(&state->unique_check, skip); + + if (skip) + { + MemoryContextSwitchTo(oldcontext); + PG_RETURN_POINTER(state); + } + } + break; case WJB_END_ARRAY: break; @@ -1820,6 +2050,26 @@ 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_objectagg aggregate function + */ +Datum +jsonb_objectagg_transfn(PG_FUNCTION_ARGS) +{ + return jsonb_object_agg_transfn_worker(fcinfo, + PG_GETARG_BOOL(3), + PG_GETARG_BOOL(4)); +} + Datum jsonb_object_agg_finalfn(PG_FUNCTION_ARGS) { @@ -1855,6 +2105,41 @@ jsonb_object_agg_finalfn(PG_FUNCTION_ARGS) /* + * jsonb_is_valid -- check jsonb value type + */ +Datum +jsonb_is_valid(PG_FUNCTION_ARGS) +{ + Jsonb *jb = PG_GETARG_JSONB_P(0); + text *type = PG_GETARG_TEXT_P(1); + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + if (!PG_ARGISNULL(1) && + strncmp("any", VARDATA(type), VARSIZE_ANY_EXHDR(type))) + { + if (!strncmp("object", VARDATA(type), VARSIZE_ANY_EXHDR(type))) + { + if (!JB_ROOT_IS_OBJECT(jb)) + PG_RETURN_BOOL(false); + } + else if (!strncmp("array", VARDATA(type), VARSIZE_ANY_EXHDR(type))) + { + if (!JB_ROOT_IS_ARRAY(jb) || JB_ROOT_IS_SCALAR(jb)) + PG_RETURN_BOOL(false); + } + else + { + if (!JB_ROOT_IS_ARRAY(jb) || !JB_ROOT_IS_SCALAR(jb)) + PG_RETURN_BOOL(false); + } + } + + PG_RETURN_BOOL(true); +} + +/* * Extract scalar value from raw-scalar pseudo-array jsonb. */ JsonbValue * @@ -2051,3 +2336,65 @@ jsonb_float8(PG_FUNCTION_ARGS) PG_RETURN_DATUM(retValue); } + +/* + * Construct an empty array jsonb. + */ +Jsonb * +JsonbMakeEmptyArray(void) +{ + JsonbValue jbv; + + jbv.type = jbvArray; + jbv.val.array.elems = NULL; + jbv.val.array.nElems = 0; + jbv.val.array.rawScalar = false; + + return JsonbValueToJsonb(&jbv); +} + +/* + * Construct an empty object jsonb. + */ +Jsonb * +JsonbMakeEmptyObject(void) +{ + JsonbValue jbv; + + jbv.type = jbvObject; + jbv.val.object.pairs = NULL; + jbv.val.object.nPairs = 0; + + return JsonbValueToJsonb(&jbv); +} + +/* + * Convert jsonb to a C-string stripping quotes from scalar strings. + */ +char * +JsonbUnquote(Jsonb *jb) +{ + if (JB_ROOT_IS_SCALAR(jb)) + { + JsonbValue v; + + JsonbExtractScalar(&jb->root, &v); + + if (v.type == jbvString) + return pnstrdup(v.val.string.val, v.val.string.len); + else if (v.type == jbvBool) + return pstrdup(v.val.boolean ? "true" : "false"); + else if (v.type == jbvNumeric) + return DatumGetCString(DirectFunctionCall1(numeric_out, + PointerGetDatum(v.val.numeric))); + else if (v.type == jbvNull) + return pstrdup("null"); + else + { + elog(ERROR, "unrecognized jsonb value type %d", v.type); + return NULL; + } + } + else + return JsonbToCString(NULL, &jb->root, VARSIZE(jb)); +} diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c index 1d63abc..ed68073 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -3055,6 +3055,50 @@ populate_record_field(ColumnIOData *col, } } +/* recursively populate specified type from a json/jsonb value */ +Datum +json_populate_type(Datum json_val, Oid json_type, Oid typid, int32 typmod, + void **cache, MemoryContext mcxt, bool *isnull) +{ + JsValue jsv = { 0 }; + JsonbValue jbv; + + jsv.is_json = json_type == JSONOID; + + if (*isnull) + { + if (jsv.is_json) + jsv.val.json.str = NULL; + else + jsv.val.jsonb = NULL; + } + else if (jsv.is_json) + { + text *json = DatumGetTextPP(json_val); + + jsv.val.json.str = VARDATA_ANY(json); + jsv.val.json.len = VARSIZE_ANY_EXHDR(json); + jsv.val.json.type = JSON_TOKEN_INVALID; /* not used in populate_composite() */ + } + else + { + Jsonb *jsonb = DatumGetJsonbP(json_val); + + jsv.val.jsonb = &jbv; + + /* fill binary jsonb value pointing to jb */ + jbv.type = jbvBinary; + jbv.val.binary.data = &jsonb->root; + jbv.val.binary.len = VARSIZE(jsonb) - VARHDRSZ; + } + + if (!*cache) + *cache = MemoryContextAllocZero(mcxt, sizeof(ColumnIOData)); + + return populate_record_field(*cache , typid, typmod, NULL, mcxt, + PointerGetDatum(NULL), &jsv, isnull); +} + static RecordIOData * allocate_record_info(MemoryContext mcxt, int ncolumns) { diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index cded305..7224eb9 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -3116,3 +3116,104 @@ wrapItemsInArray(const JsonValueList *items) return pushJsonbValue(&ps, WJB_END_ARRAY, NULL); } + +/********************Interface to pgsql's executor***************************/ +bool +JsonbPathExists(Datum jb, JsonPath *jp, List *vars) +{ + JsonPathExecResult res = executeJsonPath(jp, vars, DatumGetJsonbP(jb), + NULL); + + throwJsonPathError(res); + + return res == jperOk; +} + +Datum +JsonbPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper, + bool *empty, List *vars) +{ + JsonbValue *first; + bool wrap; + JsonValueList found = { 0 }; + JsonPathExecResult jper = executeJsonPath(jp, vars, DatumGetJsonbP(jb), + &found); + int count; + + throwJsonPathError(jper); + + count = JsonValueListLength(&found); + + first = count ? JsonValueListHead(&found) : NULL; + + if (!first) + wrap = false; + else if (wrapper == JSW_NONE) + wrap = false; + else if (wrapper == JSW_UNCONDITIONAL) + wrap = true; + else if (wrapper == JSW_CONDITIONAL) + wrap = count > 1 || + IsAJsonbScalar(first) || + (first->type == jbvBinary && + JsonContainerIsScalar(first->val.binary.data)); + else + { + elog(ERROR, "unrecognized json wrapper %d", wrapper); + wrap = false; + } + + if (wrap) + return JsonbPGetDatum(JsonbValueToJsonb(wrapItemsInArray(&found))); + + if (count > 1) + ereport(ERROR, + (errcode(ERRCODE_MORE_THAN_ONE_JSON_ITEM), + errmsg("more than one SQL/JSON item"))); + + if (first) + return JsonbPGetDatum(JsonbValueToJsonb(first)); + + *empty = true; + return PointerGetDatum(NULL); +} + +JsonbValue * +JsonbPathValue(Datum jb, JsonPath *jp, bool *empty, List *vars) +{ + JsonbValue *res; + JsonValueList found = { 0 }; + JsonPathExecResult jper = executeJsonPath(jp, vars, DatumGetJsonbP(jb), + &found); + int count; + + throwJsonPathError(jper); + + count = JsonValueListLength(&found); + + *empty = !count; + + if (*empty) + return NULL; + + if (count > 1) + ereport(ERROR, + (errcode(ERRCODE_MORE_THAN_ONE_JSON_ITEM), + errmsg("more than one SQL/JSON item"))); + + res = JsonValueListHead(&found); + + if (res->type == jbvBinary && + JsonContainerIsScalar(res->val.binary.data)) + JsonbExtractScalar(res->val.binary.data, res); + + if (!IsAJsonbScalar(res)) + ereport(ERROR, + (errcode(ERRCODE_JSON_SCALAR_REQUIRED), + errmsg("SQL/JSON scalar required"))); + + if (res->type == jbvNull) + return NULL; + + return res; +} diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 46ddc35..830e1a4 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -468,6 +468,8 @@ static void add_cast_to(StringInfo buf, Oid typid); static char *generate_qualified_type_name(Oid typid); static text *string_to_text(char *str); static char *flatten_reloptions(Oid relid); +static void get_json_path_spec(Node *path_spec, deparse_context *context, + bool showimplicit); #define only_marker(rte) ((rte)->inh ? "" : "ONLY ") @@ -7466,6 +7468,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags) case T_Aggref: case T_WindowFunc: case T_FuncExpr: + case T_JsonExpr: /* function-like: name(..) or name[..] */ return true; @@ -7584,6 +7587,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags) case T_Aggref: /* own parentheses */ case T_WindowFunc: /* own parentheses */ case T_CaseExpr: /* other separators */ + case T_JsonExpr: /* own parentheses */ return true; default: return false; @@ -7747,6 +7751,61 @@ get_rule_expr_paren(Node *node, deparse_context *context, /* + * get_json_path_spec - Parse back a JSON path specification + */ +static void +get_json_path_spec(Node *path_spec, deparse_context *context, bool showimplicit) +{ + if (IsA(path_spec, Const)) + get_const_expr((Const *) path_spec, context, -1); + else + get_rule_expr(path_spec, context, showimplicit); +} + +/* + * get_json_format - Parse back a JsonFormat structure + */ +static void +get_json_format(JsonFormat *format, deparse_context *context) +{ + if (format->type == JS_FORMAT_DEFAULT) + return; + + appendStringInfoString(context->buf, + format->type == JS_FORMAT_JSONB ? + " FORMAT JSONB" : " FORMAT JSON"); + + if (format->encoding != JS_ENC_DEFAULT) + { + const char *encoding = + format->encoding == JS_ENC_UTF16 ? "UTF16" : + format->encoding == JS_ENC_UTF32 ? "UTF32" : "UTF8"; + + appendStringInfo(context->buf, " ENCODING %s", encoding); + } +} + +/* + * get_json_returning - Parse back a JsonReturning structure + */ +static void +get_json_returning(JsonReturning *returning, deparse_context *context, + bool json_format_by_default) +{ + if (!OidIsValid(returning->typid)) + return; + + appendStringInfo(context->buf, " RETURNING %s", + format_type_with_typemod(returning->typid, + returning->typmod)); + + if (!json_format_by_default || + returning->format.type != + (returning->typid == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON)) + get_json_format(&returning->format, context); +} + +/* * get_coercion - Parse back a coercion */ static void @@ -7765,6 +7824,54 @@ get_coercion(Expr *arg, deparse_context *context, bool showimplicit, } } +static void +get_json_behavior(JsonBehavior *behavior, deparse_context *context, + const char *on) +{ + switch (behavior->btype) + { + case JSON_BEHAVIOR_DEFAULT: + appendStringInfoString(context->buf, " DEFAULT "); + get_rule_expr(behavior->default_expr, context, false); + break; + + case JSON_BEHAVIOR_EMPTY: + appendStringInfoString(context->buf, " EMPTY"); + break; + + case JSON_BEHAVIOR_EMPTY_ARRAY: + appendStringInfoString(context->buf, " EMPTY ARRAY"); + break; + + case JSON_BEHAVIOR_EMPTY_OBJECT: + appendStringInfoString(context->buf, " EMPTY OBJECT"); + break; + + case JSON_BEHAVIOR_ERROR: + appendStringInfoString(context->buf, " ERROR"); + break; + + case JSON_BEHAVIOR_FALSE: + appendStringInfoString(context->buf, " FALSE"); + break; + + case JSON_BEHAVIOR_NULL: + appendStringInfoString(context->buf, " NULL"); + break; + + case JSON_BEHAVIOR_TRUE: + appendStringInfoString(context->buf, " TRUE"); + break; + + case JSON_BEHAVIOR_UNKNOWN: + appendStringInfoString(context->buf, " UNKNOWN"); + break; + } + + appendStringInfo(context->buf, " ON %s", on); +} + + /* ---------- * get_rule_expr - Parse back an expression * @@ -8879,6 +8986,83 @@ get_rule_expr(Node *node, deparse_context *context, } break; + + case T_JsonValueExpr: + { + JsonValueExpr *jve = (JsonValueExpr *) node; + + get_rule_expr((Node *) jve->expr, context, false); + get_json_format(&jve->format, context); + } + break; + + case T_JsonExpr: + { + JsonExpr *jexpr = (JsonExpr *) node; + + switch (jexpr->op) + { + case IS_JSON_QUERY: + appendStringInfoString(buf, "JSON_QUERY("); + break; + case IS_JSON_VALUE: + appendStringInfoString(buf, "JSON_VALUE("); + break; + case IS_JSON_EXISTS: + appendStringInfoString(buf, "JSON_EXISTS("); + break; + } + + get_rule_expr(jexpr->raw_expr, context, showimplicit); + + get_json_format(&jexpr->format, context); + + appendStringInfoString(buf, ", "); + + get_json_path_spec(jexpr->path_spec, context, showimplicit); + + if (jexpr->passing.values) + { + ListCell *lc1, *lc2; + bool needcomma = false; + + appendStringInfoString(buf, " PASSING "); + + forboth(lc1, jexpr->passing.names, + lc2, jexpr->passing.values) + { + if (needcomma) + appendStringInfoString(buf, ", "); + needcomma = true; + + get_rule_expr((Node *) lfirst(lc2), context, showimplicit); + appendStringInfo(buf, " AS %s", + ((Value *) lfirst(lc1))->val.str); + } + } + + if (jexpr->op != IS_JSON_EXISTS) + get_json_returning(&jexpr->returning, context, + jexpr->op != IS_JSON_VALUE); + + if (jexpr->wrapper == JSW_CONDITIONAL) + appendStringInfo(buf, " WITH CONDITIONAL WRAPPER"); + + if (jexpr->wrapper == JSW_UNCONDITIONAL) + appendStringInfo(buf, " WITH UNCONDITIONAL WRAPPER"); + + if (jexpr->omit_quotes) + appendStringInfo(buf, " OMIT QUOTES"); + + if (jexpr->op != IS_JSON_EXISTS) + get_json_behavior(&jexpr->on_empty, context, "EMPTY"); + + get_json_behavior(&jexpr->on_error, context, "ERROR"); + + appendStringInfoString(buf, ")"); + } + break; + case T_List: { char *sep; @@ -8975,6 +9159,7 @@ looks_like_function(Node *node) case T_MinMaxExpr: case T_SQLValueFunction: case T_XmlExpr: + case T_JsonExpr: /* these are all accepted by func_expr_common_subexpr */ return true; default: @@ -9050,6 +9235,66 @@ get_func_opts(FuncFormat aggformat, Node *aggformatopts, deparse_context *contex { switch (aggformat) { + case FUNCFMT_JSON_OBJECT: + case FUNCFMT_JSON_OBJECTAGG: + case FUNCFMT_JSON_ARRAY: + case FUNCFMT_JSON_ARRAYAGG: + { + JsonCtorOpts *opts = castNode(JsonCtorOpts, aggformatopts); + + if (!opts) + break; + + if (opts->absent_on_null) + { + if (aggformat == FUNCFMT_JSON_OBJECT || + aggformat == FUNCFMT_JSON_OBJECTAGG) + appendStringInfoString(context->buf, " ABSENT ON NULL"); + } + else + { + if (aggformat == FUNCFMT_JSON_ARRAY || + aggformat == FUNCFMT_JSON_ARRAYAGG) + appendStringInfoString(context->buf, " NULL ON NULL"); + } + + if (opts->unique) + appendStringInfoString(context->buf, " WITH UNIQUE KEYS"); + + get_json_returning(&opts->returning, context, true); + } + break; + + case FUNCFMT_IS_JSON: + { + JsonIsPredicateOpts *opts = + castNode(JsonIsPredicateOpts, aggformatopts); + + appendStringInfoString(context->buf, " IS JSON"); + + if (!opts) + break; + + switch (opts->value_type) + { + case JS_TYPE_SCALAR: + appendStringInfoString(context->buf, " SCALAR"); + break; + case JS_TYPE_ARRAY: + appendStringInfoString(context->buf, " ARRAY"); + break; + case JS_TYPE_OBJECT: + appendStringInfoString(context->buf, " OBJECT"); + break; + default: + break; + } + + if (opts->unique_keys) + appendStringInfoString(context->buf, " WITH UNIQUE KEYS"); + } + break; + default: break; } @@ -9066,6 +9311,8 @@ get_func_expr(FuncExpr *expr, deparse_context *context, Oid funcoid = expr->funcid; Oid argtypes[FUNC_MAX_ARGS]; int nargs; + int firstarg; + int lastarg = list_length(expr->args); List *argnames; bool use_variadic; ListCell *l; @@ -9126,22 +9373,57 @@ get_func_expr(FuncExpr *expr, deparse_context *context, switch (expr->funcformat2) { + case FUNCFMT_JSON_OBJECT: + funcname = "JSON_OBJECT"; + firstarg = 2; + use_variadic = false; + break; + + case FUNCFMT_JSON_ARRAY: + funcname = "JSON_ARRAY"; + firstarg = 1; + use_variadic = false; + break; + + case FUNCFMT_IS_JSON: + funcname = NULL; + firstarg = 0; + lastarg = 0; + use_variadic = false; + break; + default: funcname = generate_function_name(funcoid, nargs, argnames, argtypes, expr->funcvariadic, &use_variadic, context->special_exprkind); + firstarg = 0; break; } - appendStringInfo(buf, "%s(", funcname); + if (funcname) + appendStringInfo(buf, "%s(", funcname); + else if (!PRETTY_PAREN(context)) + appendStringInfoChar(buf, '('); nargs = 0; foreach(l, expr->args) { - if (nargs++ > 0) - appendStringInfoString(buf, ", "); + if (nargs > lastarg) + break; + + if (nargs++ < firstarg) + continue; + + if (nargs > firstarg + 1) + { + const char *sep = expr->funcformat2 == FUNCFMT_JSON_OBJECT && + !((nargs - firstarg) % 2) ? " : " : ", "; + + appendStringInfoString(buf, sep); + } + if (use_variadic && lnext(l) == NULL) appendStringInfoString(buf, "VARIADIC "); get_rule_expr((Node *) lfirst(l), context, true); @@ -9149,7 +9431,8 @@ get_func_expr(FuncExpr *expr, deparse_context *context, get_func_opts(expr->funcformat2, expr->funcformatopts, context); - appendStringInfoChar(buf, ')'); + if (funcname || !PRETTY_PAREN(context)) + appendStringInfoChar(buf, ')'); } /* @@ -9161,8 +9444,9 @@ get_agg_expr(Aggref *aggref, deparse_context *context, { StringInfo buf = context->buf; Oid argtypes[FUNC_MAX_ARGS]; + const char *funcname; int nargs; - bool use_variadic; + bool use_variadic = false; /* * For a combining aggregate, we look up and deparse the corresponding @@ -9191,13 +9475,24 @@ get_agg_expr(Aggref *aggref, deparse_context *context, /* Extract the argument types as seen by the parser */ nargs = get_aggregate_argtypes(aggref, argtypes); + switch (aggref->aggformat) + { + case FUNCFMT_JSON_OBJECTAGG: + funcname = "JSON_OBJECTAGG"; + break; + case FUNCFMT_JSON_ARRAYAGG: + funcname = "JSON_ARRAYAGG"; + break; + default: + funcname = generate_function_name(aggref->aggfnoid, nargs, NIL, + argtypes, aggref->aggvariadic, + &use_variadic, + context->special_exprkind); + break; + } + /* 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)) @@ -9233,7 +9528,17 @@ get_agg_expr(Aggref *aggref, deparse_context *context, if (tle->resjunk) continue; if (i++ > 0) - appendStringInfoString(buf, ", "); + { + if (aggref->aggformat == FUNCFMT_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); @@ -9287,6 +9592,7 @@ get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context) int nargs; List *argnames; ListCell *l; + const char *funcname; if (list_length(wfunc->args) > FUNC_MAX_ARGS) ereport(ERROR, @@ -9304,16 +9610,37 @@ 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)); + switch (wfunc->winformat) + { + case FUNCFMT_JSON_OBJECTAGG: + funcname = "JSON_OBJECTAGG"; + break; + case FUNCFMT_JSON_ARRAYAGG: + funcname = "JSON_ARRAYAGG"; + break; + default: + funcname = generate_function_name(wfunc->winfnoid, nargs, argnames, + argtypes, false, NULL, + context->special_exprkind); + break; + } + + 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 (wfunc->winformat == FUNCFMT_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); + } get_func_opts(wfunc->winformat, wfunc->winformatopts, context); diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat index b4ce0aa..8355aba 100644 --- a/src/include/catalog/pg_aggregate.dat +++ b/src/include/catalog/pg_aggregate.dat @@ -535,14 +535,22 @@ # 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_objectagg', aggtransfn => 'json_objectagg_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_objectagg', aggtransfn => 'jsonb_objectagg_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 b1d3dd8..6a5773b 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8040,17 +8040,31 @@ proname => 'json_agg_transfn', proisstrict => 'f', provolatile => 's', prorettype => 'internal', proargtypes => 'internal anyelement', prosrc => 'json_agg_transfn' }, +{ oid => '3426', 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' }, +#define F_JSON_AGG 3175 { oid => '3175', descr => 'aggregate input into json', proname => 'json_agg', prokind => 'a', proisstrict => 'f', provolatile => 's', prorettype => 'json', proargtypes => 'anyelement', prosrc => 'aggregate_dummy' }, +#define F_JSON_AGG_STRICT 3450 +{ oid => '3424', 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 => '3427', descr => 'json object aggregate transition function', + proname => 'json_objectagg_transfn', proisstrict => 'f', provolatile => 's', + prorettype => 'internal', proargtypes => 'internal any any bool bool', + prosrc => 'json_objectagg_transfn' }, { oid => '3196', descr => 'json object aggregate final function', proname => 'json_object_agg_finalfn', proisstrict => 'f', prorettype => 'json', proargtypes => 'internal', @@ -8059,6 +8073,11 @@ proname => 'json_object_agg', prokind => 'a', proisstrict => 'f', provolatile => 's', prorettype => 'json', proargtypes => 'any any', prosrc => 'aggregate_dummy' }, +#define F_JSON_OBJECTAGG 3451 +{ oid => '3425', descr => 'aggregate input into a json object', + proname => 'json_objectagg', prokind => 'a', proisstrict => 'f', + provolatile => 's', prorettype => 'json', proargtypes => 'any any bool bool', + 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', @@ -8068,6 +8087,11 @@ proname => 'json_build_array', proisstrict => 'f', provolatile => 's', prorettype => 'json', proargtypes => '', prosrc => 'json_build_array_noargs' }, +{ oid => '3998', descr => 'build a json array from any inputs', + proname => 'json_build_array_ext', provariadic => 'any', proisstrict => 'f', + provolatile => 's', prorettype => 'json', proargtypes => 'bool any', + proallargtypes => '{bool,any}', proargmodes => '{i,v}', + prosrc => 'json_build_array_ext' }, { oid => '3200', descr => 'build a json object from pairwise key/value inputs', proname => 'json_build_object', provariadic => 'any', proisstrict => 'f', @@ -8078,6 +8102,11 @@ proname => 'json_build_object', proisstrict => 'f', provolatile => 's', prorettype => 'json', proargtypes => '', prosrc => 'json_build_object_noargs' }, +{ oid => '6066', descr => 'build a json object from pairwise key/value inputs', + proname => 'json_build_object_ext', provariadic => 'any', proisstrict => 'f', + provolatile => 's', prorettype => 'json', proargtypes => 'bool bool any', + proallargtypes => '{bool,bool,any}', proargmodes => '{i,i,v}', + prosrc => 'json_build_object_ext' }, { oid => '3202', descr => 'map text array of key value pairs to json object', proname => 'json_object', prorettype => 'json', proargtypes => '_text', prosrc => 'json_object' }, @@ -8090,6 +8119,13 @@ { oid => '3261', descr => 'remove object fields with null values from json', proname => 'json_strip_nulls', prorettype => 'json', proargtypes => 'json', prosrc => 'json_strip_nulls' }, +{ oid => '6060', descr => 'check json value type and key uniqueness', + proname => 'json_is_valid', prorettype => 'bool', + proargtypes => 'json text bool', prosrc => 'json_is_valid' }, +{ oid => '6061', + descr => 'check json text validity, value type and key uniquenes', + proname => 'json_is_valid', prorettype => 'bool', + proargtypes => 'text text bool', prosrc => 'json_is_valid' }, { oid => '3947', proname => 'json_object_field', prorettype => 'json', @@ -8893,18 +8929,32 @@ proname => 'jsonb_agg_transfn', proisstrict => 'f', provolatile => 's', prorettype => 'internal', proargtypes => 'internal anyelement', prosrc => 'jsonb_agg_transfn' }, +{ oid => '6065', 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', prosrc => 'jsonb_agg_finalfn' }, +#define F_JSONB_AGG 3267 { oid => '3267', descr => 'aggregate input into jsonb', proname => 'jsonb_agg', prokind => 'a', proisstrict => 'f', provolatile => 's', prorettype => 'jsonb', proargtypes => 'anyelement', prosrc => 'aggregate_dummy' }, +#define F_JSONB_AGG_STRICT 6063 +{ oid => '6063', 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 => '3428', descr => 'jsonb object aggregate transition function', + proname => 'jsonb_objectagg_transfn', proisstrict => 'f', provolatile => 's', + prorettype => 'internal', proargtypes => 'internal any any bool bool', + prosrc => 'jsonb_objectagg_transfn' }, { oid => '3269', descr => 'jsonb object aggregate final function', proname => 'jsonb_object_agg_finalfn', proisstrict => 'f', provolatile => 's', prorettype => 'jsonb', proargtypes => 'internal', @@ -8913,6 +8963,11 @@ proname => 'jsonb_object_agg', prokind => 'a', proisstrict => 'f', prorettype => 'jsonb', proargtypes => 'any any', prosrc => 'aggregate_dummy' }, +#define F_JSONB_OBJECTAGG 6064 +{ oid => '6064', descr => 'aggregate inputs into jsonb object', + proname => 'jsonb_objectagg', prokind => 'a', proisstrict => 'f', + prorettype => 'jsonb', proargtypes => 'any any bool bool', + 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', @@ -8922,6 +8977,11 @@ proname => 'jsonb_build_array', proisstrict => 'f', provolatile => 's', prorettype => 'jsonb', proargtypes => '', prosrc => 'jsonb_build_array_noargs' }, +{ oid => '6068', descr => 'build a jsonb array from any inputs', + proname => 'jsonb_build_array_ext', provariadic => 'any', proisstrict => 'f', + provolatile => 's', prorettype => 'jsonb', proargtypes => 'bool any', + proallargtypes => '{bool,any}', proargmodes => '{i,v}', + prosrc => 'jsonb_build_array_ext' }, { oid => '3273', descr => 'build a jsonb object from pairwise key/value inputs', proname => 'jsonb_build_object', provariadic => 'any', proisstrict => 'f', @@ -8932,9 +8992,17 @@ proname => 'jsonb_build_object', proisstrict => 'f', provolatile => 's', prorettype => 'jsonb', proargtypes => '', prosrc => 'jsonb_build_object_noargs' }, +{ oid => '6067', descr => 'build a jsonb object from pairwise key/value inputs', + proname => 'jsonb_build_object_ext', provariadic => 'any', proisstrict => 'f', + provolatile => 's', prorettype => 'jsonb', proargtypes => 'bool bool any', + proallargtypes => '{bool,bool,any}', proargmodes => '{i,i,v}', + prosrc => 'jsonb_build_object_ext' }, { oid => '3262', descr => 'remove object fields with null values from jsonb', proname => 'jsonb_strip_nulls', prorettype => 'jsonb', proargtypes => 'jsonb', prosrc => 'jsonb_strip_nulls' }, +{ oid => '6062', descr => 'check jsonb value type', + proname => 'jsonb_is_valid', prorettype => 'bool', + proargtypes => 'jsonb text', prosrc => 'jsonb_is_valid' }, { oid => '3478', proname => 'jsonb_object_field', prorettype => 'jsonb', diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index 194bf46..23c3722 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -20,6 +20,7 @@ /* forward references to avoid circularity */ struct ExprEvalStep; struct ArrayRefState; +struct JsonbValue; /* Bits in ExprState->flags (see also execnodes.h for public flag bits): */ /* expression's interpreter has been initialized */ @@ -219,6 +220,7 @@ typedef enum ExprEvalOp EEOP_WINDOW_FUNC, EEOP_SUBPLAN, EEOP_ALTERNATIVE_SUBPLAN, + EEOP_JSONEXPR, /* aggregation related nodes */ EEOP_AGG_STRICT_DESERIALIZE, @@ -641,6 +643,55 @@ typedef struct ExprEvalStep int transno; int setoff; } agg_trans; + + /* for EEOP_JSONEXPR */ + struct + { + JsonExpr *jsexpr; /* original expression node */ + + struct + { + FmgrInfo func; /* typinput function for output type */ + Oid typioparam; + } input; /* I/O info for output type */ + + struct + { + Datum value; + bool isnull; + } *raw_expr, /* raw context item value */ + *res_expr, /* result item */ + *coercion_expr, /* input for JSON item coercion */ + *pathspec; /* path specification value */ + + ExprState *formatted_expr; /* formatted context item */ + ExprState *result_expr; /* coerced to output type */ + ExprState *default_on_empty; /* ON EMPTY DEFAULT expression */ + ExprState *default_on_error; /* ON ERROR DEFAULT expression */ + List *args; /* passing arguments */ + + void *cache; /* cache for json_populate_type() */ + + struct JsonCoercionsState + { + struct JsonCoercionState + { + JsonCoercion *coercion; /* coercion expression */ + ExprState *estate; /* coercion expression state */ + } null, + string, + numeric, + boolean, + date, + time, + timetz, + timestamp, + timestamptz, + composite; + } coercions; /* states for coercion from SQL/JSON item + * types directly to the output type */ + } jsonexpr; + } d; } ExprEvalStep; @@ -741,6 +792,13 @@ extern void ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext); extern void ExecEvalSysVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext, TupleTableSlot *slot); +extern void ExecEvalJson(ExprState *state, ExprEvalStep *op, + ExprContext *econtext); +extern Datum ExecPrepareJsonItemCoercion(struct JsonbValue *item, + JsonReturning *returning, + struct JsonCoercionsState *coercions, + struct JsonCoercionState **pjcstate); +extern bool ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr); extern void ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup); extern Datum ExecAggTransReparent(AggState *aggstate, AggStatePerTrans pertrans, diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 2feec62..faf4f70 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -236,6 +236,8 @@ ExecProcNode(PlanState *node) */ extern ExprState *ExecInitExpr(Expr *node, PlanState *parent); extern ExprState *ExecInitExprWithParams(Expr *node, ParamListInfo ext_params); +extern ExprState *ExecInitExprWithCaseValue(Expr *node, PlanState *parent, + Datum *caseval, bool *casenull); extern ExprState *ExecInitQual(List *qual, PlanState *parent); extern ExprState *ExecInitCheck(List *qual, PlanState *parent); extern List *ExecInitExprList(List *nodes, PlanState *parent); diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h index 57bd52f..f7aec03 100644 --- a/src/include/nodes/makefuncs.h +++ b/src/include/nodes/makefuncs.h @@ -88,4 +88,11 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols); +extern JsonValueExpr *makeJsonValueExpr(Expr *expr, JsonFormat format); +extern JsonBehavior *makeJsonBehavior(JsonBehaviorType type, Node *expr); +extern Node *makeJsonKeyValue(Node *key, Node *value); +extern Node *makeJsonIsPredicate(Node *expr, JsonFormat format, + JsonValueType vtype, bool unique_keys); +extern JsonEncoding makeJsonEncoding(char *name); + #endif /* MAKEFUNC_H */ diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index cac6ff0e..05d3eb7 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -196,6 +196,9 @@ typedef enum NodeTag T_FromExpr, T_OnConflictExpr, T_IntoClause, + T_JsonExpr, + T_JsonCoercion, + T_JsonItemCoercions, /* * TAGS FOR EXPRESSION STATE NODES (execnodes.h) @@ -475,6 +478,22 @@ typedef enum NodeTag T_PartitionRangeDatum, T_PartitionCmd, T_VacuumRelation, + T_JsonValueExpr, + T_JsonObjectCtor, + T_JsonArrayCtor, + T_JsonArrayQueryCtor, + T_JsonObjectAgg, + T_JsonArrayAgg, + T_JsonFuncExpr, + T_JsonIsPredicate, + T_JsonExistsPredicate, + T_JsonCommon, + T_JsonArgument, + T_JsonKeyValue, + T_JsonBehavior, + T_JsonOutput, + T_JsonCtorOpts, + T_JsonIsPredicateOpts, /* * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h) diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index e5bdc1c..377297b 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1440,6 +1440,221 @@ typedef struct TriggerTransition bool isTable; } TriggerTransition; +/* Nodes for SQL/JSON support */ + +/* + * JsonQuotes - + * representation of [KEEP|OMIT] QUOTES clause for JSON_QUERY() + */ +typedef enum JsonQuotes +{ + JS_QUOTES_UNSPEC, /* unspecified */ + JS_QUOTES_KEEP, /* KEEP QUOTES */ + JS_QUOTES_OMIT /* OMIT QUOTES */ +} JsonQuotes; + +/* + * JsonPathSpec - + * representation of JSON path constant + */ +typedef char *JsonPathSpec; + +/* + * JsonOutput - + * representation of JSON output clause (RETURNING type [FORMAT format]) + */ +typedef struct JsonOutput +{ + NodeTag type; + TypeName *typeName; /* RETURNING type name, if specified */ + JsonReturning returning; /* RETURNING FORMAT clause and type Oids */ +} JsonOutput; + +/* + * JsonValueExpr - + * representation of JSON value expression (expr [FORMAT json_format]) + */ +typedef struct JsonValueExpr +{ + NodeTag type; + Expr *expr; /* raw expression */ + JsonFormat format; /* FORMAT clause, if specified */ +} JsonValueExpr; + +/* + * JsonArgument - + * representation of argument from JSON PASSING clause + */ +typedef struct JsonArgument +{ + NodeTag type; + JsonValueExpr *val; /* argument value expression */ + char *name; /* argument name */ +} JsonArgument; + +/* + * JsonCommon - + * representation of common syntax of functions using JSON path + */ +typedef struct JsonCommon +{ + NodeTag type; + JsonValueExpr *expr; /* context item expression */ + Node *pathspec; /* JSON path specification expression */ + char *pathname; /* path name, if any */ + List *passing; /* list of PASSING clause arguments, if any */ + int location; /* token location, or -1 if unknown */ +} JsonCommon; + +/* + * JsonFuncExpr - + * untransformed representation of JSON function expressions + */ +typedef struct JsonFuncExpr +{ + NodeTag type; + JsonExprOp op; /* expression type */ + JsonCommon *common; /* common syntax */ + JsonOutput *output; /* output clause, if specified */ + JsonBehavior *on_empty; /* ON EMPTY behavior, if specified */ + JsonBehavior *on_error; /* ON ERROR behavior, if specified */ + JsonWrapper wrapper; /* array wrapper behavior (JSON_QUERY only) */ + bool omit_quotes; /* omit or keep quotes? (JSON_QUERY only) */ + int location; /* token location, or -1 if unknown */ +} JsonFuncExpr; + +/* + * JsonValueType - + * representation of JSON item type in IS JSON predicate + */ +typedef enum JsonValueType +{ + JS_TYPE_ANY, /* IS JSON [VALUE] */ + JS_TYPE_OBJECT, /* IS JSON OBJECT */ + JS_TYPE_ARRAY, /* IS JSON ARRAY*/ + JS_TYPE_SCALAR /* IS JSON SCALAR */ +} JsonValueType; + +/* + * JsonIsPredicate - + * untransformed representation of IS JSON predicate + */ +typedef struct JsonIsPredicate +{ + NodeTag type; + Node *expr; /* untransformed expression */ + JsonFormat format; /* FORMAT clause, if specified */ + JsonValueType vtype; /* JSON item type */ + bool unique_keys; /* check key uniqueness? */ + int location; /* token location, or -1 if unknown */ +} JsonIsPredicate; + +typedef struct JsonIsPredicateOpts +{ + NodeTag type; + JsonValueType value_type; /* JSON item type */ + bool unique_keys; /* check key uniqueness? */ +} JsonIsPredicateOpts; + +/* + * 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; + +/* + * JsonObjectCtor - + * untransformed representation of JSON_OBJECT() constructor + */ +typedef struct JsonObjectCtor +{ + 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 */ +} JsonObjectCtor; + +/* + * JsonArrayCtor - + * untransformed representation of JSON_ARRAY(element,...) constructor + */ +typedef struct JsonArrayCtor +{ + 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 */ +} JsonArrayCtor; + +/* + * JsonArrayQueryCtor - + * untransformed representation of JSON_ARRAY(subquery) constructor + */ +typedef struct JsonArrayQueryCtor +{ + 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 */ +} JsonArrayQueryCtor; + +/* + * JsonAggCtor - + * common fields of untransformed representation of + * JSON_ARRAYAGG() and JSON_OBJECTAGG() + */ +typedef struct JsonAggCtor +{ + 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 */ +} JsonAggCtor; + +/* + * JsonObjectAgg - + * untransformed representation of JSON_OBJECTAGG() + */ +typedef struct JsonObjectAgg +{ + JsonAggCtor ctor; /* 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 +{ + JsonAggCtor ctor; /* common fields */ + JsonValueExpr *arg; /* array element expression */ + bool absent_on_null; /* skip NULL elements? */ +} JsonArrayAgg; + +typedef struct JsonCtorOpts +{ + NodeTag type; + JsonReturning returning; /* RETURNING clause */ + bool absent_on_null; /* skip NULL values? */ + bool unique; /* check key uniqueness? */ +} JsonCtorOpts; + /***************************************************************************** * Raw Grammar Output Statements *****************************************************************************/ diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index ecf6ff6..a53ab6c 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -255,6 +255,11 @@ typedef struct Param typedef enum FuncFormat { FUNCFMT_REGULAR = 0, + FUNCFMT_JSON_OBJECT = 1, + FUNCFMT_JSON_ARRAY = 2, + FUNCFMT_JSON_OBJECTAGG = 3, + FUNCFMT_JSON_ARRAYAGG = 4, + FUNCFMT_IS_JSON = 5 } FuncFormat; /* @@ -1184,6 +1189,167 @@ typedef struct XmlExpr int location; /* token location, or -1 if unknown */ } XmlExpr; +/* + * JsonExprOp - + * enumeration of JSON functions using JSON path + */ +typedef enum JsonExprOp +{ + IS_JSON_VALUE, /* JSON_VALUE() */ + IS_JSON_QUERY, /* JSON_QUERY() */ + IS_JSON_EXISTS /* JSON_EXISTS() */ +} JsonExprOp; + +/* + * JsonEncoding - + * representation of JSON ENCODING clause + */ +typedef enum JsonEncoding +{ + JS_ENC_DEFAULT, /* unspecified */ + JS_ENC_UTF8, + JS_ENC_UTF16, + JS_ENC_UTF32, +} JsonEncoding; + +/* + * JsonFormatType - + * enumeration of JSON formats used in JSON FORMAT clause + */ +typedef enum JsonFormatType +{ + JS_FORMAT_DEFAULT, /* unspecified */ + JS_FORMAT_JSON, /* FORMAT JSON [ENCODING ...] */ + JS_FORMAT_JSONB /* implicit internal format for RETURNING jsonb */ +} JsonFormatType; + +/* + * JsonBehaviorType - + * enumeration of behavior types used in JSON ON ... BEHAVIOR clause + */ +typedef enum +{ + JSON_BEHAVIOR_NULL, + JSON_BEHAVIOR_ERROR, + JSON_BEHAVIOR_EMPTY, + JSON_BEHAVIOR_TRUE, + JSON_BEHAVIOR_FALSE, + JSON_BEHAVIOR_UNKNOWN, + JSON_BEHAVIOR_EMPTY_ARRAY, + JSON_BEHAVIOR_EMPTY_OBJECT, + JSON_BEHAVIOR_DEFAULT, +} JsonBehaviorType; + +/* + * JsonWrapper - + * representation of WRAPPER clause for JSON_QUERY() + */ +typedef enum JsonWrapper +{ + JSW_NONE, + JSW_CONDITIONAL, + JSW_UNCONDITIONAL, +} JsonWrapper; + +/* + * JsonFormat - + * representation of JSON FORMAT clause + */ +typedef struct JsonFormat +{ + JsonFormatType type; /* format type */ + JsonEncoding encoding; /* JSON encoding */ + int location; /* token location, or -1 if unknown */ +} JsonFormat; + +/* + * JsonReturning - + * transformed representation of JSON RETURNING clause + */ +typedef struct JsonReturning +{ + JsonFormat format; /* output JSON format */ + Oid typid; /* target type Oid */ + int32 typmod; /* target type modifier */ +} JsonReturning; + +/* + * JsonBehavior - + * representation of JSON ON ... BEHAVIOR clause + */ +typedef struct JsonBehavior +{ + NodeTag type; + JsonBehaviorType btype; /* behavior type */ + Node *default_expr; /* default expression, if any */ +} JsonBehavior; + +/* + * JsonPassing - + * representation of JSON PASSING clause + */ +typedef struct JsonPassing +{ + List *values; /* list of PASSING argument expressions */ + List *names; /* parallel list of Value strings */ +} JsonPassing; + +/* + * JsonCoercion - + * coercion from SQL/JSON item types to SQL types + */ +typedef struct JsonCoercion +{ + NodeTag type; + Node *expr; /* resulting expression coerced to target type */ + bool via_populate; /* coerce result using json_populate_type()? */ + bool via_io; /* coerce result using type input function? */ + Oid collation; /* collation for coercion via I/O or populate */ +} JsonCoercion; + +/* + * JsonItemCoercions - + * expressions for coercion from SQL/JSON item types directly to the + * output SQL type + */ +typedef struct JsonItemCoercions +{ + NodeTag type; + JsonCoercion *null; + JsonCoercion *string; + JsonCoercion *numeric; + JsonCoercion *boolean; + JsonCoercion *date; + JsonCoercion *time; + JsonCoercion *timetz; + JsonCoercion *timestamp; + JsonCoercion *timestamptz; + JsonCoercion *composite; /* arrays and objects */ +} JsonItemCoercions; + +/* + * JsonExpr - + * transformed representation of JSON_VALUE(), JSON_QUERY(), JSON_EXISTS() + */ +typedef struct JsonExpr +{ + Expr xpr; + JsonExprOp op; /* json function ID */ + Node *raw_expr; /* raw context item expression */ + Node *formatted_expr; /* formatted context item expression */ + JsonCoercion *result_coercion; /* resulting coercion to RETURNING type */ + JsonFormat format; /* context item format (JSON/JSONB) */ + Node *path_spec; /* JSON path specification expression */ + JsonPassing passing; /* PASSING clause arguments */ + JsonReturning returning; /* RETURNING clause type/format info */ + JsonBehavior on_empty; /* ON EMPTY behavior */ + JsonBehavior on_error; /* ON ERROR behavior */ + JsonItemCoercions *coercions; /* coercions for JSON_VALUE */ + JsonWrapper wrapper; /* WRAPPER for JSON_QUERY */ + bool omit_quotes; /* KEEP/OMIT QUOTES for JSON_QUERY */ + int location; /* token location, or -1 if unknown */ +} JsonExpr; + /* ---------------- * NullTest * diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 23db401..c340d94 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -27,6 +27,7 @@ /* name, value, category */ PG_KEYWORD("abort", ABORT_P, UNRESERVED_KEYWORD) +PG_KEYWORD("absent", ABSENT, UNRESERVED_KEYWORD) PG_KEYWORD("absolute", ABSOLUTE_P, UNRESERVED_KEYWORD) PG_KEYWORD("access", ACCESS, UNRESERVED_KEYWORD) PG_KEYWORD("action", ACTION, UNRESERVED_KEYWORD) @@ -89,6 +90,7 @@ PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD) PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD) PG_KEYWORD("committed", COMMITTED, UNRESERVED_KEYWORD) PG_KEYWORD("concurrently", CONCURRENTLY, TYPE_FUNC_NAME_KEYWORD) +PG_KEYWORD("conditional", CONDITIONAL, UNRESERVED_KEYWORD) PG_KEYWORD("configuration", CONFIGURATION, UNRESERVED_KEYWORD) PG_KEYWORD("conflict", CONFLICT, UNRESERVED_KEYWORD) PG_KEYWORD("connection", CONNECTION, UNRESERVED_KEYWORD) @@ -142,11 +144,13 @@ PG_KEYWORD("double", DOUBLE_P, UNRESERVED_KEYWORD) PG_KEYWORD("drop", DROP, UNRESERVED_KEYWORD) PG_KEYWORD("each", EACH, UNRESERVED_KEYWORD) PG_KEYWORD("else", ELSE, RESERVED_KEYWORD) +PG_KEYWORD("empty", EMPTY_P, UNRESERVED_KEYWORD) PG_KEYWORD("enable", ENABLE_P, UNRESERVED_KEYWORD) PG_KEYWORD("encoding", ENCODING, UNRESERVED_KEYWORD) PG_KEYWORD("encrypted", ENCRYPTED, UNRESERVED_KEYWORD) PG_KEYWORD("end", END_P, RESERVED_KEYWORD) PG_KEYWORD("enum", ENUM_P, UNRESERVED_KEYWORD) +PG_KEYWORD("error", ERROR_P, UNRESERVED_KEYWORD) PG_KEYWORD("escape", ESCAPE, UNRESERVED_KEYWORD) PG_KEYWORD("event", EVENT, UNRESERVED_KEYWORD) PG_KEYWORD("except", EXCEPT, RESERVED_KEYWORD) @@ -169,6 +173,7 @@ PG_KEYWORD("following", FOLLOWING, UNRESERVED_KEYWORD) PG_KEYWORD("for", FOR, RESERVED_KEYWORD) PG_KEYWORD("force", FORCE, UNRESERVED_KEYWORD) PG_KEYWORD("foreign", FOREIGN, RESERVED_KEYWORD) +PG_KEYWORD("format", FORMAT, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("forward", FORWARD, UNRESERVED_KEYWORD) PG_KEYWORD("freeze", FREEZE, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("from", FROM, RESERVED_KEYWORD) @@ -221,7 +226,17 @@ PG_KEYWORD("is", IS, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("isnull", ISNULL, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("isolation", ISOLATION, UNRESERVED_KEYWORD) PG_KEYWORD("join", JOIN, TYPE_FUNC_NAME_KEYWORD) +PG_KEYWORD("json", JSON, UNRESERVED_KEYWORD) +PG_KEYWORD("json_array", JSON_ARRAY, COL_NAME_KEYWORD) +PG_KEYWORD("json_arrayagg", JSON_ARRAYAGG, COL_NAME_KEYWORD) +PG_KEYWORD("json_exists", JSON_EXISTS, COL_NAME_KEYWORD) +PG_KEYWORD("json_object", JSON_OBJECT, COL_NAME_KEYWORD) +PG_KEYWORD("json_objectagg", JSON_OBJECTAGG, COL_NAME_KEYWORD) +PG_KEYWORD("json_query", JSON_QUERY, COL_NAME_KEYWORD) +PG_KEYWORD("json_value", JSON_VALUE, COL_NAME_KEYWORD) +PG_KEYWORD("keep", KEEP, UNRESERVED_KEYWORD) PG_KEYWORD("key", KEY, UNRESERVED_KEYWORD) +PG_KEYWORD("keys", KEYS, UNRESERVED_KEYWORD) PG_KEYWORD("label", LABEL, UNRESERVED_KEYWORD) PG_KEYWORD("language", LANGUAGE, UNRESERVED_KEYWORD) PG_KEYWORD("large", LARGE_P, UNRESERVED_KEYWORD) @@ -277,6 +292,7 @@ PG_KEYWORD("off", OFF, UNRESERVED_KEYWORD) PG_KEYWORD("offset", OFFSET, RESERVED_KEYWORD) PG_KEYWORD("oids", OIDS, UNRESERVED_KEYWORD) PG_KEYWORD("old", OLD, UNRESERVED_KEYWORD) +PG_KEYWORD("omit", OMIT, UNRESERVED_KEYWORD) PG_KEYWORD("on", ON, RESERVED_KEYWORD) PG_KEYWORD("only", ONLY, RESERVED_KEYWORD) PG_KEYWORD("operator", OPERATOR, UNRESERVED_KEYWORD) @@ -318,6 +334,7 @@ PG_KEYWORD("procedures", PROCEDURES, UNRESERVED_KEYWORD) PG_KEYWORD("program", PROGRAM, UNRESERVED_KEYWORD) PG_KEYWORD("publication", PUBLICATION, UNRESERVED_KEYWORD) PG_KEYWORD("quote", QUOTE, UNRESERVED_KEYWORD) +PG_KEYWORD("quotes", QUOTES, UNRESERVED_KEYWORD) PG_KEYWORD("range", RANGE, UNRESERVED_KEYWORD) PG_KEYWORD("read", READ, UNRESERVED_KEYWORD) PG_KEYWORD("real", REAL, COL_NAME_KEYWORD) @@ -351,6 +368,7 @@ PG_KEYWORD("row", ROW, COL_NAME_KEYWORD) PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD) PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD) PG_KEYWORD("savepoint", SAVEPOINT, UNRESERVED_KEYWORD) +PG_KEYWORD("scalar", SCALAR, UNRESERVED_KEYWORD) PG_KEYWORD("schema", SCHEMA, UNRESERVED_KEYWORD) PG_KEYWORD("schemas", SCHEMAS, UNRESERVED_KEYWORD) PG_KEYWORD("scroll", SCROLL, UNRESERVED_KEYWORD) @@ -385,6 +403,7 @@ PG_KEYWORD("stdin", STDIN, UNRESERVED_KEYWORD) PG_KEYWORD("stdout", STDOUT, UNRESERVED_KEYWORD) PG_KEYWORD("storage", STORAGE, UNRESERVED_KEYWORD) PG_KEYWORD("strict", STRICT_P, UNRESERVED_KEYWORD) +PG_KEYWORD("string", STRING, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("strip", STRIP_P, UNRESERVED_KEYWORD) PG_KEYWORD("subscription", SUBSCRIPTION, UNRESERVED_KEYWORD) PG_KEYWORD("substring", SUBSTRING, COL_NAME_KEYWORD) @@ -417,6 +436,7 @@ PG_KEYWORD("type", TYPE_P, UNRESERVED_KEYWORD) PG_KEYWORD("types", TYPES_P, UNRESERVED_KEYWORD) PG_KEYWORD("unbounded", UNBOUNDED, UNRESERVED_KEYWORD) PG_KEYWORD("uncommitted", UNCOMMITTED, UNRESERVED_KEYWORD) +PG_KEYWORD("unconditional", UNCONDITIONAL, UNRESERVED_KEYWORD) PG_KEYWORD("unencrypted", UNENCRYPTED, UNRESERVED_KEYWORD) PG_KEYWORD("union", UNION, RESERVED_KEYWORD) PG_KEYWORD("unique", UNIQUE, RESERVED_KEYWORD) diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h index a6148ba..928b81f 100644 --- a/src/include/utils/jsonapi.h +++ b/src/include/utils/jsonapi.h @@ -207,6 +207,10 @@ extern text *transform_json_string_values(text *json, void *action_state, extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid, const int *tzp); +extern Datum json_populate_type(Datum json_val, Oid json_type, + Oid typid, int32 typmod, + void **cache, MemoryContext mcxt, bool *isnull); + extern Json *JsonCreate(text *json); extern JsonbIteratorToken JsonIteratorNext(JsonIterator **pit, JsonbValue *val, bool skipNested); diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h index a0f972f..7e8ec08 100644 --- a/src/include/utils/jsonb.h +++ b/src/include/utils/jsonb.h @@ -406,6 +406,9 @@ extern char *JsonbToCString(StringInfo out, JsonbContainer *in, extern char *JsonbToCStringIndent(StringInfo out, JsonbContainer *in, int estimated_len); +extern Jsonb *JsonbMakeEmptyArray(void); +extern Jsonb *JsonbMakeEmptyObject(void); +extern char *JsonbUnquote(Jsonb *jb); extern JsonbValue *JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res); #endif /* __JSONB_H__ */ diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index 3747985..4184a66 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -17,6 +17,7 @@ #include "fmgr.h" #include "utils/jsonb.h" #include "nodes/pg_list.h" +#include "nodes/primnodes.h" typedef struct { @@ -304,7 +305,15 @@ typedef struct JsonPathVariable { void *cb_arg; } JsonPathVariable; - +typedef struct JsonPathVariableEvalContext +{ + JsonPathVariable var; + struct ExprContext *econtext; + struct ExprState *estate; + Datum value; + bool isnull; + bool evaluated; +} JsonPathVariableEvalContext; typedef struct JsonValueList { @@ -317,4 +326,12 @@ JsonPathExecResult executeJsonPath(JsonPath *path, Jsonb *json, JsonValueList *foundJson); +extern bool JsonbPathExists(Datum jb, JsonPath *path, List *vars); +extern Datum JsonbPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper, + bool *empty, List *vars); +extern JsonbValue *JsonbPathValue(Datum jb, JsonPath *jp, bool *empty, + List *vars); + +extern Datum EvalJsonPathVar(void *cxt, bool *isnull); + #endif diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl index e1c0a2c..5e714be 100644 --- a/src/interfaces/ecpg/preproc/parse.pl +++ b/src/interfaces/ecpg/preproc/parse.pl @@ -45,6 +45,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 0e60407..8bf2564 100644 --- a/src/interfaces/ecpg/preproc/parser.c +++ b/src/interfaces/ecpg/preproc/parser.c @@ -84,6 +84,9 @@ filtered_base_yylex(void) case WITH: cur_token_length = 4; break; + case WITHOUT: + cur_token_length = 7; + break; default: return cur_token; } @@ -155,8 +158,22 @@ 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; + } return cur_token; diff --git a/src/test/regress/expected/json_sqljson.out b/src/test/regress/expected/json_sqljson.out new file mode 100644 index 0000000..bb62634 --- /dev/null +++ b/src/test/regress/expected/json_sqljson.out @@ -0,0 +1,15 @@ +-- JSON_EXISTS +SELECT JSON_EXISTS(NULL FORMAT JSON, '$'); +ERROR: JSON_EXISTS() is not yet implemented for json type +LINE 1: SELECT JSON_EXISTS(NULL FORMAT JSON, '$'); + ^ +-- JSON_VALUE +SELECT JSON_VALUE(NULL FORMAT JSON, '$'); +ERROR: JSON_VALUE() is not yet implemented for json type +LINE 1: SELECT JSON_VALUE(NULL FORMAT JSON, '$'); + ^ +-- JSON_QUERY +SELECT JSON_QUERY(NULL FORMAT JSON, '$'); +ERROR: JSON_QUERY() is not yet implemented for json type +LINE 1: SELECT JSON_QUERY(NULL FORMAT JSON, '$'); + ^ diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out new file mode 100644 index 0000000..e63ddbe --- /dev/null +++ b/src/test/regress/expected/jsonb_sqljson.out @@ -0,0 +1,988 @@ +-- JSON_EXISTS +SELECT JSON_EXISTS(NULL::jsonb, '$'); + json_exists +------------- + +(1 row) + +SELECT JSON_EXISTS(jsonb '[]', '$'); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(JSON_OBJECT(RETURNING jsonb), '$'); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(jsonb '1', '$'); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(jsonb 'null', '$'); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(jsonb '[]', '$'); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(jsonb '1', '$.a'); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS(jsonb '1', 'strict $.a'); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS(jsonb '1', 'strict $.a' ERROR ON ERROR); +ERROR: SQL/JSON member not found +SELECT JSON_EXISTS(jsonb 'null', '$.a'); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS(jsonb '[]', '$.a'); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS(jsonb '[1, "aaa", {"a": 1}]', 'strict $.a'); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS(jsonb '[1, "aaa", {"a": 1}]', 'lax $.a'); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(jsonb '{}', '$.a'); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS(jsonb '{"b": 1, "a": 2}', '$.a'); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(jsonb '1', '$.a.b'); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS(jsonb '{"a": {"b": 1}}', '$.a.b'); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(jsonb '{"a": 1, "b": 2}', '$.a.b'); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS(jsonb '{"a": 1, "b": 2}', '$.* ? (@ > $x)' PASSING 1 AS x); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(jsonb '{"a": 1, "b": 2}', '$.* ? (@ > $x)' PASSING '1' AS x); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS(jsonb '{"a": 1, "b": 2}', '$.* ? (@ > $x && @ < $y)' PASSING 0 AS x, 2 AS y); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(jsonb '{"a": 1, "b": 2}', '$.* ? (@ > $x && @ < $y)' PASSING 0 AS x, 1 AS y); + json_exists +------------- + f +(1 row) + +-- extension: boolean expressions +SELECT JSON_EXISTS(jsonb '1', '$ > 2'); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(jsonb '1', '$.a > 2' ERROR ON ERROR); + json_exists +------------- + t +(1 row) + +-- JSON_VALUE +SELECT JSON_VALUE(NULL::jsonb, '$'); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(jsonb 'null', '$'); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING int); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(jsonb 'true', '$'); + json_value +------------ + true +(1 row) + +SELECT JSON_VALUE(jsonb 'true', '$' RETURNING bool); + json_value +------------ + t +(1 row) + +SELECT JSON_VALUE(jsonb '123', '$'); + json_value +------------ + 123 +(1 row) + +SELECT JSON_VALUE(jsonb '123', '$' RETURNING int) + 234; + ?column? +---------- + 357 +(1 row) + +SELECT JSON_VALUE(jsonb '123', '$' RETURNING text); + json_value +------------ + 123 +(1 row) + +/* jsonb bytea ??? */ +SELECT JSON_VALUE(jsonb '123', '$' RETURNING bytea ERROR ON ERROR); +ERROR: SQL/JSON item cannot be cast to target type +SELECT JSON_VALUE(jsonb '1.23', '$'); + json_value +------------ + 1.23 +(1 row) + +SELECT JSON_VALUE(jsonb '1.23', '$' RETURNING int); + json_value +------------ + 1 +(1 row) + +SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING numeric); + json_value +------------ + 1.23 +(1 row) + +SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING int ERROR ON ERROR); +ERROR: invalid input syntax for type integer: "1.23" +SELECT JSON_VALUE(jsonb '"aaa"', '$'); + json_value +------------ + aaa +(1 row) + +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING text); + json_value +------------ + aaa +(1 row) + +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING char(5)); + json_value +------------ + aaa +(1 row) + +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING char(2)); + json_value +------------ + aa +(1 row) + +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING json); + json_value +------------ + "aaa" +(1 row) + +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING jsonb); + json_value +------------ + "aaa" +(1 row) + +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING json ERROR ON ERROR); + json_value +------------ + "aaa" +(1 row) + +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING jsonb ERROR ON ERROR); + json_value +------------ + "aaa" +(1 row) + +SELECT JSON_VALUE(jsonb '"\"aaa\""', '$' RETURNING json); + json_value +------------ + "\"aaa\"" +(1 row) + +SELECT JSON_VALUE(jsonb '"\"aaa\""', '$' RETURNING jsonb); + json_value +------------ + "\"aaa\"" +(1 row) + +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int ERROR ON ERROR); +ERROR: invalid input syntax for type integer: "aaa" +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int DEFAULT 111 ON ERROR); + json_value +------------ + 111 +(1 row) + +SELECT JSON_VALUE(jsonb '"123"', '$' RETURNING int) + 234; + ?column? +---------- + 357 +(1 row) + +SELECT JSON_VALUE(jsonb '"2017-02-20"', '$' RETURNING date) + 9; + ?column? +------------ + 03-01-2017 +(1 row) + +-- Test NULL checks execution in domain types +CREATE DOMAIN sqljsonb_int_not_null AS int NOT NULL; +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null); +ERROR: domain sqljsonb_int_not_null does not allow null values +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null NULL ON ERROR); +ERROR: domain sqljsonb_int_not_null does not allow null values +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT NULL ON ERROR); +ERROR: domain sqljsonb_int_not_null does not allow null values +SELECT JSON_VALUE(jsonb '[]', '$'); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(jsonb '[]', '$' ERROR ON ERROR); +ERROR: SQL/JSON scalar required +SELECT JSON_VALUE(jsonb '{}', '$'); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(jsonb '{}', '$' ERROR ON ERROR); +ERROR: SQL/JSON scalar required +SELECT JSON_VALUE(jsonb '1', '$.a'); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(jsonb '1', 'strict $.a' ERROR ON ERROR); +ERROR: SQL/JSON member not found +SELECT JSON_VALUE(jsonb '1', 'strict $.a' DEFAULT 'error' ON ERROR); + json_value +------------ + error +(1 row) + +SELECT JSON_VALUE(jsonb '1', 'lax $.a' ERROR ON ERROR); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(jsonb '1', 'lax $.a' ERROR ON EMPTY ERROR ON ERROR); +ERROR: no SQL/JSON item +SELECT JSON_VALUE(jsonb '1', 'strict $.a' DEFAULT 2 ON ERROR); + json_value +------------ + 2 +(1 row) + +SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT 2 ON ERROR); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT '2' ON ERROR); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(jsonb '1', 'lax $.a' NULL ON EMPTY DEFAULT '2' ON ERROR); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT '2' ON EMPTY DEFAULT '3' ON ERROR); + json_value +------------ + 2 +(1 row) + +SELECT JSON_VALUE(jsonb '1', 'lax $.a' ERROR ON EMPTY DEFAULT '3' ON ERROR); + json_value +------------ + 3 +(1 row) + +SELECT JSON_VALUE(jsonb '[1,2]', '$[*]' ERROR ON ERROR); +ERROR: more than one SQL/JSON item +SELECT JSON_VALUE(jsonb '[1,2]', '$[*]' DEFAULT '0' ON ERROR); + json_value +------------ + 0 +(1 row) + +SELECT JSON_VALUE(jsonb '[" "]', '$[*]' RETURNING int ERROR ON ERROR); +ERROR: invalid input syntax for type integer: " " +SELECT JSON_VALUE(jsonb '[" "]', '$[*]' RETURNING int DEFAULT 2 + 3 ON ERROR); + json_value +------------ + 5 +(1 row) + +SELECT JSON_VALUE(jsonb '["1"]', '$[*]' RETURNING int DEFAULT 2 + 3 ON ERROR); + json_value +------------ + 1 +(1 row) + +SELECT + x, + JSON_VALUE( + jsonb '{"a": 1, "b": 2}', + '$.* ? (@ > $x)' PASSING x AS x + RETURNING int + DEFAULT -1 ON EMPTY + DEFAULT -2 ON ERROR + ) y +FROM + generate_series(0, 2) x; + x | y +---+---- + 0 | -2 + 1 | 2 + 2 | -1 +(3 rows) + +SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )' AS a); + json_value +------------ + (1,2) +(1 row) + +SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )' AS a RETURNING point); + json_value +------------ + (1,2) +(1 row) + +-- Test timestamptz passing and output +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts); + json_value +------------------------------ + Tue Feb 20 18:34:56 2018 PST +(1 row) + +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING timestamptz); + json_value +------------------------------ + Tue Feb 20 18:34:56 2018 PST +(1 row) + +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING timestamp); + json_value +-------------------------- + Tue Feb 20 18:34:56 2018 +(1 row) + +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING json); + json_value +----------------------------- + "2018-02-21T02:34:56+00:00" +(1 row) + +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING jsonb); + json_value +----------------------------- + "2018-02-21T02:34:56+00:00" +(1 row) + +-- JSON_QUERY +SELECT + JSON_QUERY(js, '$'), + JSON_QUERY(js, '$' WITHOUT WRAPPER), + JSON_QUERY(js, '$' WITH CONDITIONAL WRAPPER), + JSON_QUERY(js, '$' WITH UNCONDITIONAL ARRAY WRAPPER), + JSON_QUERY(js, '$' WITH ARRAY WRAPPER) +FROM + (VALUES + (jsonb 'null'), + ('12.3'), + ('true'), + ('"aaa"'), + ('[1, null, "2"]'), + ('{"a": 1, "b": [2]}') + ) foo(js); + json_query | json_query | json_query | json_query | json_query +--------------------+--------------------+--------------------+----------------------+---------------------- + null | null | [null] | [null] | [null] + 12.3 | 12.3 | [12.3] | [12.3] | [12.3] + true | true | [true] | [true] | [true] + "aaa" | "aaa" | ["aaa"] | ["aaa"] | ["aaa"] + [1, null, "2"] | [1, null, "2"] | [1, null, "2"] | [[1, null, "2"]] | [[1, null, "2"]] + {"a": 1, "b": [2]} | {"a": 1, "b": [2]} | {"a": 1, "b": [2]} | [{"a": 1, "b": [2]}] | [{"a": 1, "b": [2]}] +(6 rows) + +SELECT + JSON_QUERY(js, 'strict $[*]') AS "unspec", + JSON_QUERY(js, 'strict $[*]' WITHOUT WRAPPER) AS "without", + JSON_QUERY(js, 'strict $[*]' WITH CONDITIONAL WRAPPER) AS "with cond", + JSON_QUERY(js, 'strict $[*]' WITH UNCONDITIONAL ARRAY WRAPPER) AS "with uncond", + JSON_QUERY(js, 'strict $[*]' WITH ARRAY WRAPPER) AS "with" +FROM + (VALUES + (jsonb '1'), + ('[]'), + ('[null]'), + ('[12.3]'), + ('[true]'), + ('["aaa"]'), + ('[[1, 2, 3]]'), + ('[{"a": 1, "b": [2]}]'), + ('[1, "2", null, [3]]') + ) foo(js); + unspec | without | with cond | with uncond | with +--------------------+--------------------+---------------------+----------------------+---------------------- + | | | | + | | | | + null | null | [null] | [null] | [null] + 12.3 | 12.3 | [12.3] | [12.3] | [12.3] + true | true | [true] | [true] | [true] + "aaa" | "aaa" | ["aaa"] | ["aaa"] | ["aaa"] + [1, 2, 3] | [1, 2, 3] | [1, 2, 3] | [[1, 2, 3]] | [[1, 2, 3]] + {"a": 1, "b": [2]} | {"a": 1, "b": [2]} | {"a": 1, "b": [2]} | [{"a": 1, "b": [2]}] | [{"a": 1, "b": [2]}] + | | [1, "2", null, [3]] | [1, "2", null, [3]] | [1, "2", null, [3]] +(9 rows) + +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text); + json_query +------------ + "aaa" +(1 row) + +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text KEEP QUOTES); + json_query +------------ + "aaa" +(1 row) + +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text KEEP QUOTES ON SCALAR STRING); + json_query +------------ + "aaa" +(1 row) + +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text OMIT QUOTES); + json_query +------------ + aaa +(1 row) + +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text OMIT QUOTES ON SCALAR STRING); + json_query +------------ + aaa +(1 row) + +SELECT JSON_QUERY(jsonb '"aaa"', '$' OMIT QUOTES ERROR ON ERROR); +ERROR: invalid input syntax for type json +DETAIL: Token "aaa" is invalid. +CONTEXT: JSON data, line 1: aaa +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING json OMIT QUOTES ERROR ON ERROR); +ERROR: invalid input syntax for type json +DETAIL: Token "aaa" is invalid. +CONTEXT: JSON data, line 1: aaa +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING bytea FORMAT JSON OMIT QUOTES ERROR ON ERROR); + json_query +------------ + \x616161 +(1 row) + +-- QUOTES behavior should not be specified when WITH WRAPPER used: +-- Should fail +SELECT JSON_QUERY(jsonb '[1]', '$' WITH WRAPPER OMIT QUOTES); +ERROR: SQL/JSON QUOTES behavior shall not be specified when WITH WRAPPER is used +LINE 1: SELECT JSON_QUERY(jsonb '[1]', '$' WITH WRAPPER OMIT QUOTES)... + ^ +SELECT JSON_QUERY(jsonb '[1]', '$' WITH WRAPPER KEEP QUOTES); +ERROR: SQL/JSON QUOTES behavior shall not be specified when WITH WRAPPER is used +LINE 1: SELECT JSON_QUERY(jsonb '[1]', '$' WITH WRAPPER KEEP QUOTES)... + ^ +SELECT JSON_QUERY(jsonb '[1]', '$' WITH CONDITIONAL WRAPPER KEEP QUOTES); +ERROR: SQL/JSON QUOTES behavior shall not be specified when WITH WRAPPER is used +LINE 1: ...N_QUERY(jsonb '[1]', '$' WITH CONDITIONAL WRAPPER KEEP QUOTE... + ^ +SELECT JSON_QUERY(jsonb '[1]', '$' WITH CONDITIONAL WRAPPER OMIT QUOTES); +ERROR: SQL/JSON QUOTES behavior shall not be specified when WITH WRAPPER is used +LINE 1: ...N_QUERY(jsonb '[1]', '$' WITH CONDITIONAL WRAPPER OMIT QUOTE... + ^ +-- Should succeed +SELECT JSON_QUERY(jsonb '[1]', '$' WITHOUT WRAPPER OMIT QUOTES); + json_query +------------ + [1] +(1 row) + +SELECT JSON_QUERY(jsonb '[1]', '$' WITHOUT WRAPPER KEEP QUOTES); + json_query +------------ + [1] +(1 row) + +SELECT JSON_QUERY(jsonb '[]', '$[*]'); + json_query +------------ + +(1 row) + +SELECT JSON_QUERY(jsonb '[]', '$[*]' NULL ON EMPTY); + json_query +------------ + +(1 row) + +SELECT JSON_QUERY(jsonb '[]', '$[*]' EMPTY ARRAY ON EMPTY); + json_query +------------ + [] +(1 row) + +SELECT JSON_QUERY(jsonb '[]', '$[*]' EMPTY OBJECT ON EMPTY); + json_query +------------ + {} +(1 row) + +SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY); + json_query +------------ + +(1 row) + +SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY NULL ON ERROR); + json_query +------------ + +(1 row) + +SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY EMPTY ARRAY ON ERROR); + json_query +------------ + [] +(1 row) + +SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY EMPTY OBJECT ON ERROR); + json_query +------------ + {} +(1 row) + +SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY ERROR ON ERROR); +ERROR: no SQL/JSON item +SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON ERROR); + json_query +------------ + +(1 row) + +SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' ERROR ON ERROR); +ERROR: more than one SQL/JSON item +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING json); + json_query +------------ + [1, 2] +(1 row) + +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING json FORMAT JSON); + json_query +------------ + [1, 2] +(1 row) + +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING jsonb); + json_query +------------ + [1, 2] +(1 row) + +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING jsonb FORMAT JSON); + json_query +------------ + [1, 2] +(1 row) + +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING text); + json_query +------------ + [1, 2] +(1 row) + +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING char(10)); + json_query +------------ + [1, 2] +(1 row) + +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING char(3)); + json_query +------------ + [1, +(1 row) + +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING text FORMAT JSON); + json_query +------------ + [1, 2] +(1 row) + +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING bytea); + json_query +---------------- + \x5b312c20325d +(1 row) + +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING bytea FORMAT JSON); + json_query +---------------- + \x5b312c20325d +(1 row) + +SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING bytea EMPTY OBJECT ON ERROR); + json_query +------------ + \x7b7d +(1 row) + +SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING bytea FORMAT JSON EMPTY OBJECT ON ERROR); + json_query +------------ + \x7b7d +(1 row) + +SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING json EMPTY OBJECT ON ERROR); + json_query +------------ + {} +(1 row) + +SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING jsonb EMPTY OBJECT ON ERROR); + json_query +------------ + {} +(1 row) + +SELECT + x, y, + JSON_QUERY( + jsonb '[1,2,3,4,5,null]', + '$[*] ? (@ >= $x && @ <= $y)' + PASSING x AS x, y AS y + WITH CONDITIONAL WRAPPER + EMPTY ARRAY ON EMPTY + ) list +FROM + generate_series(0, 4) x, + generate_series(0, 4) y; + x | y | list +---+---+-------------- + 0 | 0 | [] + 0 | 1 | [1] + 0 | 2 | [1, 2] + 0 | 3 | [1, 2, 3] + 0 | 4 | [1, 2, 3, 4] + 1 | 0 | [] + 1 | 1 | [1] + 1 | 2 | [1, 2] + 1 | 3 | [1, 2, 3] + 1 | 4 | [1, 2, 3, 4] + 2 | 0 | [] + 2 | 1 | [] + 2 | 2 | [2] + 2 | 3 | [2, 3] + 2 | 4 | [2, 3, 4] + 3 | 0 | [] + 3 | 1 | [] + 3 | 2 | [] + 3 | 3 | [3] + 3 | 4 | [3, 4] + 4 | 0 | [] + 4 | 1 | [] + 4 | 2 | [] + 4 | 3 | [] + 4 | 4 | [4] +(25 rows) + +-- Extension: record types returning +CREATE TYPE sqljsonb_rec AS (a int, t text, js json, jb jsonb, jsa json[]); +CREATE TYPE sqljsonb_reca AS (reca sqljsonb_rec[]); +SELECT JSON_QUERY(jsonb '[{"a": 1, "b": "foo", "t": "aaa", "js": [1, "2", {}], "jb": {"x": [1, "2", {}]}}, {"a": 2}]', '$[0]' RETURNING sqljsonb_rec); + json_query +----------------------------------------------------- + (1,aaa,"[1, ""2"", {}]","{""x"": [1, ""2"", {}]}",) +(1 row) + +SELECT * FROM unnest((JSON_QUERY(jsonb '{"jsa": [{"a": 1, "b": ["foo"]}, {"a": 2, "c": {}}, 123]}', '$' RETURNING sqljsonb_rec)).jsa); + unnest +------------------------ + {"a": 1, "b": ["foo"]} + {"a": 2, "c": {}} + 123 +(3 rows) + +SELECT * FROM unnest((JSON_QUERY(jsonb '{"reca": [{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]}', '$' RETURNING sqljsonb_reca)).reca); + a | t | js | jb | jsa +---+-------------+----+------------+----- + 1 | ["foo", []] | | | + 2 | | | [{}, true] | +(2 rows) + +-- Extension: array types returning +SELECT JSON_QUERY(jsonb '[1,2,null,"3"]', '$[*]' RETURNING int[] WITH WRAPPER); + json_query +-------------- + {1,2,NULL,3} +(1 row) + +SELECT * FROM unnest(JSON_QUERY(jsonb '[{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]', '$' RETURNING sqljsonb_rec[])); + a | t | js | jb | jsa +---+-------------+----+------------+----- + 1 | ["foo", []] | | | + 2 | | | [{}, true] | +(2 rows) + +-- Extension: domain types returning +SELECT JSON_QUERY(jsonb '{"a": 1}', '$.a' RETURNING sqljsonb_int_not_null); + json_query +------------ + 1 +(1 row) + +SELECT JSON_QUERY(jsonb '{"a": 1}', '$.b' RETURNING sqljsonb_int_not_null); +ERROR: domain sqljsonb_int_not_null does not allow null values +-- Test timestamptz passing and output +SELECT JSON_QUERY(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts); + json_query +----------------------------- + "2018-02-21T02:34:56+00:00" +(1 row) + +SELECT JSON_QUERY(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING json); + json_query +----------------------------- + "2018-02-21T02:34:56+00:00" +(1 row) + +SELECT JSON_QUERY(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING jsonb); + json_query +----------------------------- + "2018-02-21T02:34:56+00:00" +(1 row) + +-- Test constraints +CREATE TABLE test_jsonb_constraints ( + js text, + i int, + x jsonb DEFAULT JSON_QUERY(jsonb '[1,2]', '$[*]' WITH WRAPPER) + CONSTRAINT test_jsonb_constraint1 + CHECK (js IS JSON) + CONSTRAINT test_jsonb_constraint2 + CHECK (JSON_EXISTS(js::jsonb, '$.a' PASSING i + 5 AS int, i::text AS txt, array[1,2,3] as arr)) + CONSTRAINT test_jsonb_constraint3 + CHECK (JSON_VALUE(js::jsonb, '$.a' RETURNING int DEFAULT ('12' || i)::int ON EMPTY ERROR ON ERROR) > i) + CONSTRAINT test_jsonb_constraint4 + CHECK (JSON_QUERY(js::jsonb, '$.a' WITH CONDITIONAL WRAPPER EMPTY OBJECT ON ERROR) < jsonb '[10]') + CONSTRAINT test_jsonb_constraint5 + CHECK (JSON_QUERY(js::jsonb, '$.a' RETURNING char(5) OMIT QUOTES EMPTY ARRAY ON EMPTY) > 'a') +); +\d test_jsonb_constraints + Table "public.test_jsonb_constraints" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+------------------------------------------------------------------------------------------------------------ + js | text | | | + i | integer | | | + x | jsonb | | | JSON_QUERY('[1, 2]'::jsonb, '$[*]' RETURNING jsonb WITH UNCONDITIONAL WRAPPER NULL ON EMPTY NULL ON ERROR) +Check constraints: + "test_jsonb_constraint1" CHECK (js IS JSON) + "test_jsonb_constraint2" CHECK (JSON_EXISTS(js::jsonb, '$."a"' PASSING i + 5 AS int, i::text AS txt, ARRAY[1, 2, 3] AS arr FALSE ON ERROR)) + "test_jsonb_constraint3" CHECK (JSON_VALUE(js::jsonb, '$."a"' RETURNING integer DEFAULT ('12'::text || i)::integer ON EMPTY ERROR ON ERROR) > i) + "test_jsonb_constraint4" CHECK (JSON_QUERY(js::jsonb, '$."a"' RETURNING jsonb WITH CONDITIONAL WRAPPER NULL ON EMPTY EMPTY OBJECT ON ERROR) < '[10]'::jsonb) + "test_jsonb_constraint5" CHECK (JSON_QUERY(js::jsonb, '$."a"' RETURNING character(5) OMIT QUOTES EMPTY ARRAY ON EMPTY NULL ON ERROR) > 'a'::bpchar) + +SELECT check_clause +FROM information_schema.check_constraints +WHERE constraint_name LIKE 'test_jsonb_constraint%'; + check_clause +----------------------------------------------------------------------------------------------------------------------------------- + ((js IS JSON)) + (JSON_EXISTS((js)::jsonb, '$."a"' PASSING (i + 5) AS int, (i)::text AS txt, ARRAY[1, 2, 3] AS arr FALSE ON ERROR)) + ((JSON_VALUE((js)::jsonb, '$."a"' RETURNING integer DEFAULT (('12'::text || i))::integer ON EMPTY ERROR ON ERROR) > i)) + ((JSON_QUERY((js)::jsonb, '$."a"' RETURNING jsonb WITH CONDITIONAL WRAPPER NULL ON EMPTY EMPTY OBJECT ON ERROR) < '[10]'::jsonb)) + ((JSON_QUERY((js)::jsonb, '$."a"' RETURNING character(5) OMIT QUOTES EMPTY ARRAY ON EMPTY NULL ON ERROR) > 'a'::bpchar)) +(5 rows) + +SELECT pg_get_expr(adbin, adrelid) FROM pg_attrdef WHERE adrelid = 'test_jsonb_constraints'::regclass; + pg_get_expr +------------------------------------------------------------------------------------------------------------ + JSON_QUERY('[1, 2]'::jsonb, '$[*]' RETURNING jsonb WITH UNCONDITIONAL WRAPPER NULL ON EMPTY NULL ON ERROR) +(1 row) + +INSERT INTO test_jsonb_constraints VALUES ('', 1); +ERROR: new row for relation "test_jsonb_constraints" violates check constraint "test_jsonb_constraint1" +DETAIL: Failing row contains (, 1, [1, 2]). +INSERT INTO test_jsonb_constraints VALUES ('1', 1); +ERROR: new row for relation "test_jsonb_constraints" violates check constraint "test_jsonb_constraint2" +DETAIL: Failing row contains (1, 1, [1, 2]). +INSERT INTO test_jsonb_constraints VALUES ('[]'); +ERROR: new row for relation "test_jsonb_constraints" violates check constraint "test_jsonb_constraint2" +DETAIL: Failing row contains ([], null, [1, 2]). +INSERT INTO test_jsonb_constraints VALUES ('{"b": 1}', 1); +ERROR: new row for relation "test_jsonb_constraints" violates check constraint "test_jsonb_constraint2" +DETAIL: Failing row contains ({"b": 1}, 1, [1, 2]). +INSERT INTO test_jsonb_constraints VALUES ('{"a": 1}', 1); +ERROR: new row for relation "test_jsonb_constraints" violates check constraint "test_jsonb_constraint3" +DETAIL: Failing row contains ({"a": 1}, 1, [1, 2]). +INSERT INTO test_jsonb_constraints VALUES ('{"a": 7}', 1); +ERROR: new row for relation "test_jsonb_constraints" violates check constraint "test_jsonb_constraint5" +DETAIL: Failing row contains ({"a": 7}, 1, [1, 2]). +INSERT INTO test_jsonb_constraints VALUES ('{"a": 10}', 1); +ERROR: new row for relation "test_jsonb_constraints" violates check constraint "test_jsonb_constraint4" +DETAIL: Failing row contains ({"a": 10}, 1, [1, 2]). +DROP TABLE test_jsonb_constraints; +-- Extension: non-constant JSON path +SELECT JSON_EXISTS(jsonb '{"a": 123}', '$' || '.' || 'a'); + json_exists +------------- + t +(1 row) + +SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'a'); + json_value +------------ + 123 +(1 row) + +SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'b' DEFAULT 'foo' ON EMPTY); + json_value +------------ + foo +(1 row) + +SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a'); + json_query +------------ + 123 +(1 row) + +SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a' WITH WRAPPER); + json_query +------------ + [123] +(1 row) + +-- Should fail (invalid path) +SELECT JSON_QUERY(jsonb '{"a": 123}', 'error' || ' ' || 'error'); +ERROR: bad jsonpath representation +DETAIL: syntax error, unexpected IDENT_P at or near " " +-- Test parallel JSON_VALUE() +CREATE TABLE test_parallel_jsonb_value AS +SELECT i::text::jsonb AS js +FROM generate_series(1, 1000000) i; +-- Should be non-parallel due to subtransactions +EXPLAIN (COSTS OFF) +SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jsonb_value; + QUERY PLAN +--------------------------------------------- + Aggregate + -> Seq Scan on test_parallel_jsonb_value +(2 rows) + +SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jsonb_value; + sum +-------------- + 500000500000 +(1 row) + +-- Should be parallel +EXPLAIN (COSTS OFF) +SELECT sum(JSON_VALUE(js, '$' RETURNING numeric ERROR ON ERROR)) FROM test_parallel_jsonb_value; + QUERY PLAN +------------------------------------------------------------------ + Finalize Aggregate + -> Gather + Workers Planned: 2 + -> Partial Aggregate + -> Parallel Seq Scan on test_parallel_jsonb_value +(5 rows) + +SELECT sum(JSON_VALUE(js, '$' RETURNING numeric ERROR ON ERROR)) FROM test_parallel_jsonb_value; + sum +-------------- + 500000500000 +(1 row) + diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 719549b..18046a4 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -210,11 +210,12 @@ WHERE p1.oid != p2.oid AND ORDER BY 1, 2; proargtypes | proargtypes -------------+------------- + 25 | 114 25 | 1042 25 | 1043 1114 | 1184 1560 | 1562 -(4 rows) +(5 rows) SELECT DISTINCT p1.proargtypes[1], p2.proargtypes[1] FROM pg_proc AS p1, pg_proc AS p2 @@ -1343,8 +1344,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 0000000..be2add5 --- /dev/null +++ b/src/test/regress/expected/sqljson.out @@ -0,0 +1,940 @@ +-- 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 + 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 + 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 3 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", "1.23" : 123, "c" : [ 1,true,{ } ], "d" : {"x": 123.45}} +(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 key "1" +SELECT JSON_OBJECT(1: 1, '1': NULL ABSENT ON NULL WITH UNIQUE RETURNING jsonb); +ERROR: duplicate JSON key "1" +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 key "1" +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 key "1" +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 key "1" +-- 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 FORMAT 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 FORMAT 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; +-- IS JSON predicate +SELECT NULL IS JSON; + ?column? +---------- + +(1 row) + +SELECT NULL IS NOT JSON; + ?column? +---------- + +(1 row) + +SELECT NULL::json IS JSON; + ?column? +---------- + +(1 row) + +SELECT NULL::jsonb IS JSON; + ?column? +---------- + +(1 row) + +SELECT NULL::text IS JSON; + ?column? +---------- + +(1 row) + +SELECT NULL::bytea IS JSON; + ?column? +---------- + +(1 row) + +SELECT NULL::int IS JSON; +ERROR: cannot use type integer in IS JSON predicate +SELECT '' IS JSON; + ?column? +---------- + f +(1 row) + +SELECT bytea '\x00' IS JSON; +ERROR: invalid byte sequence for encoding "UTF8": 0x00 +CREATE TABLE test_is_json (js text); +INSERT INTO test_is_json VALUES + (NULL), + (''), + ('123'), + ('"aaa "'), + ('true'), + ('null'), + ('[]'), + ('[1, "2", {}]'), + ('{}'), + ('{ "a": 1, "b": null }'), + ('{ "a": 1, "a": null }'), + ('{ "a": 1, "b": [{ "a": 1 }, { "a": 2 }] }'), + ('{ "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] }'), + ('aaa'), + ('{a:1}'), + ('["a",]'); +SELECT + js, + js IS JSON "IS JSON", + js IS NOT JSON "IS NOT JSON", + js IS JSON VALUE "IS VALUE", + js IS JSON OBJECT "IS OBJECT", + js IS JSON ARRAY "IS ARRAY", + js IS JSON SCALAR "IS SCALAR", + js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE", + js IS JSON WITH UNIQUE KEYS "WITH UNIQUE" +FROM + test_is_json; + js | IS JSON | IS NOT JSON | IS VALUE | IS OBJECT | IS ARRAY | IS SCALAR | WITHOUT UNIQUE | WITH UNIQUE +-----------------------------------------------+---------+-------------+----------+-----------+----------+-----------+----------------+------------- + | | | | | | | | + | f | t | f | f | f | f | f | f + 123 | t | f | t | f | f | t | t | t + "aaa " | t | f | t | f | f | t | t | t + true | t | f | t | f | f | t | t | t + null | t | f | t | f | f | t | t | t + [] | t | f | t | f | t | f | t | t + [1, "2", {}] | t | f | t | f | t | f | t | t + {} | t | f | t | t | f | f | t | t + { "a": 1, "b": null } | t | f | t | t | f | f | t | t + { "a": 1, "a": null } | t | f | t | t | f | f | t | f + { "a": 1, "b": [{ "a": 1 }, { "a": 2 }] } | t | f | t | t | f | f | t | t + { "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] } | t | f | t | t | f | f | t | f + aaa | f | t | f | f | f | f | f | f + {a:1} | f | t | f | f | f | f | f | f + ["a",] | f | t | f | f | f | f | f | f +(16 rows) + +SELECT + js, + js IS JSON "IS JSON", + js IS NOT JSON "IS NOT JSON", + js IS JSON VALUE "IS VALUE", + js IS JSON OBJECT "IS OBJECT", + js IS JSON ARRAY "IS ARRAY", + js IS JSON SCALAR "IS SCALAR", + js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE", + js IS JSON WITH UNIQUE KEYS "WITH UNIQUE" +FROM + (SELECT js::json FROM test_is_json WHERE js IS JSON) foo(js); + js | IS JSON | IS NOT JSON | IS VALUE | IS OBJECT | IS ARRAY | IS SCALAR | WITHOUT UNIQUE | WITH UNIQUE +-----------------------------------------------+---------+-------------+----------+-----------+----------+-----------+----------------+------------- + 123 | t | f | t | f | f | t | t | t + "aaa " | t | f | t | f | f | t | t | t + true | t | f | t | f | f | t | t | t + null | t | f | t | f | f | t | t | t + [] | t | f | t | f | t | f | t | t + [1, "2", {}] | t | f | t | f | t | f | t | t + {} | t | f | t | t | f | f | t | t + { "a": 1, "b": null } | t | f | t | t | f | f | t | t + { "a": 1, "a": null } | t | f | t | t | f | f | t | f + { "a": 1, "b": [{ "a": 1 }, { "a": 2 }] } | t | f | t | t | f | f | t | t + { "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] } | t | f | t | t | f | f | t | f +(11 rows) + +SELECT + js0, + js IS JSON "IS JSON", + js IS NOT JSON "IS NOT JSON", + js IS JSON VALUE "IS VALUE", + js IS JSON OBJECT "IS OBJECT", + js IS JSON ARRAY "IS ARRAY", + js IS JSON SCALAR "IS SCALAR", + js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE", + js IS JSON WITH UNIQUE KEYS "WITH UNIQUE" +FROM + (SELECT js, js::bytea FROM test_is_json WHERE js IS JSON) foo(js0, js); + js0 | IS JSON | IS NOT JSON | IS VALUE | IS OBJECT | IS ARRAY | IS SCALAR | WITHOUT UNIQUE | WITH UNIQUE +-----------------------------------------------+---------+-------------+----------+-----------+----------+-----------+----------------+------------- + 123 | t | f | t | f | f | t | t | t + "aaa " | t | f | t | f | f | t | t | t + true | t | f | t | f | f | t | t | t + null | t | f | t | f | f | t | t | t + [] | t | f | t | f | t | f | t | t + [1, "2", {}] | t | f | t | f | t | f | t | t + {} | t | f | t | t | f | f | t | t + { "a": 1, "b": null } | t | f | t | t | f | f | t | t + { "a": 1, "a": null } | t | f | t | t | f | f | t | f + { "a": 1, "b": [{ "a": 1 }, { "a": 2 }] } | t | f | t | t | f | f | t | t + { "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] } | t | f | t | t | f | f | t | f +(11 rows) + +SELECT + js, + js IS JSON "IS JSON", + js IS NOT JSON "IS NOT JSON", + js IS JSON VALUE "IS VALUE", + js IS JSON OBJECT "IS OBJECT", + js IS JSON ARRAY "IS ARRAY", + js IS JSON SCALAR "IS SCALAR", + js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE", + js IS JSON WITH UNIQUE KEYS "WITH UNIQUE" +FROM + (SELECT js::jsonb FROM test_is_json WHERE js IS JSON) foo(js); + js | IS JSON | IS NOT JSON | IS VALUE | IS OBJECT | IS ARRAY | IS SCALAR | WITHOUT UNIQUE | WITH UNIQUE +-------------------------------------+---------+-------------+----------+-----------+----------+-----------+----------------+------------- + 123 | t | f | t | f | f | t | t | t + "aaa " | t | f | t | f | f | t | t | t + true | t | f | t | f | f | t | t | t + null | t | f | t | f | f | t | t | t + [] | t | f | t | f | t | f | t | t + [1, "2", {}] | t | f | t | f | t | f | t | t + {} | t | f | t | t | f | f | t | t + {"a": 1, "b": null} | t | f | t | t | f | f | t | t + {"a": null} | t | f | t | t | f | f | t | t + {"a": 1, "b": [{"a": 1}, {"a": 2}]} | t | f | t | t | f | f | t | t + {"a": 1, "b": [{"a": 2, "b": 0}]} | t | f | t | t | f | f | t | t +(11 rows) + +-- Test IS JSON deparsing +EXPLAIN (VERBOSE, COSTS OFF) +SELECT '1' IS JSON AS "any", ('1' || i) IS JSON SCALAR AS "scalar", '[]' IS NOT JSON ARRAY AS "array", '{}' IS JSON OBJECT WITH UNIQUE AS "object" FROM generate_series(1, 3) i; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------- + Function Scan on pg_catalog.generate_series i + Output: ('1'::text IS JSON), (('1'::text || (i)::text) IS JSON SCALAR), (NOT ('[]'::text IS JSON ARRAY)), ('{}'::text IS JSON OBJECT WITH UNIQUE KEYS) + Function Call: generate_series(1, 3) +(3 rows) + +CREATE VIEW is_json_view AS +SELECT '1' IS JSON AS "any", ('1' || i) IS JSON SCALAR AS "scalar", '[]' IS NOT JSON ARRAY AS "array", '{}' IS JSON OBJECT WITH UNIQUE AS "object" FROM generate_series(1, 3) i; +\sv is_json_view +CREATE OR REPLACE VIEW public.is_json_view AS + SELECT '1'::text IS JSON AS "any", + '1'::text || i.i IS JSON SCALAR AS scalar, + NOT '[]'::text IS JSON ARRAY AS "array", + '{}'::text IS JSON OBJECT WITH UNIQUE KEYS AS object + FROM generate_series(1, 3) i(i) +DROP VIEW is_json_view; diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 177e031..1b68d1e 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -109,7 +109,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 json_jsonpath jsonb_jsonpath +test: json jsonb json_encoding jsonpath json_jsonpath jsonb_jsonpath jsonb_sqljson sqljson # ---------- # Another group of parallel tests diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index fa6a1d2..7f12f42 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -159,6 +159,9 @@ test: json_encoding test: jsonpath test: json_jsonpath test: jsonb_jsonpath +test: sqljson +test: json_sqljson +test: jsonb_sqljson test: indirect_toast test: equivclass test: plancache diff --git a/src/test/regress/sql/json_sqljson.sql b/src/test/regress/sql/json_sqljson.sql new file mode 100644 index 0000000..4f30fa4 --- /dev/null +++ b/src/test/regress/sql/json_sqljson.sql @@ -0,0 +1,11 @@ +-- JSON_EXISTS + +SELECT JSON_EXISTS(NULL FORMAT JSON, '$'); + +-- JSON_VALUE + +SELECT JSON_VALUE(NULL FORMAT JSON, '$'); + +-- JSON_QUERY + +SELECT JSON_QUERY(NULL FORMAT JSON, '$'); diff --git a/src/test/regress/sql/jsonb_sqljson.sql b/src/test/regress/sql/jsonb_sqljson.sql new file mode 100644 index 0000000..8bb9e01 --- /dev/null +++ b/src/test/regress/sql/jsonb_sqljson.sql @@ -0,0 +1,303 @@ +-- JSON_EXISTS + +SELECT JSON_EXISTS(NULL::jsonb, '$'); + +SELECT JSON_EXISTS(jsonb '[]', '$'); +SELECT JSON_EXISTS(JSON_OBJECT(RETURNING jsonb), '$'); + +SELECT JSON_EXISTS(jsonb '1', '$'); +SELECT JSON_EXISTS(jsonb 'null', '$'); +SELECT JSON_EXISTS(jsonb '[]', '$'); + +SELECT JSON_EXISTS(jsonb '1', '$.a'); +SELECT JSON_EXISTS(jsonb '1', 'strict $.a'); +SELECT JSON_EXISTS(jsonb '1', 'strict $.a' ERROR ON ERROR); +SELECT JSON_EXISTS(jsonb 'null', '$.a'); +SELECT JSON_EXISTS(jsonb '[]', '$.a'); +SELECT JSON_EXISTS(jsonb '[1, "aaa", {"a": 1}]', 'strict $.a'); +SELECT JSON_EXISTS(jsonb '[1, "aaa", {"a": 1}]', 'lax $.a'); +SELECT JSON_EXISTS(jsonb '{}', '$.a'); +SELECT JSON_EXISTS(jsonb '{"b": 1, "a": 2}', '$.a'); + +SELECT JSON_EXISTS(jsonb '1', '$.a.b'); +SELECT JSON_EXISTS(jsonb '{"a": {"b": 1}}', '$.a.b'); +SELECT JSON_EXISTS(jsonb '{"a": 1, "b": 2}', '$.a.b'); + +SELECT JSON_EXISTS(jsonb '{"a": 1, "b": 2}', '$.* ? (@ > $x)' PASSING 1 AS x); +SELECT JSON_EXISTS(jsonb '{"a": 1, "b": 2}', '$.* ? (@ > $x)' PASSING '1' AS x); +SELECT JSON_EXISTS(jsonb '{"a": 1, "b": 2}', '$.* ? (@ > $x && @ < $y)' PASSING 0 AS x, 2 AS y); +SELECT JSON_EXISTS(jsonb '{"a": 1, "b": 2}', '$.* ? (@ > $x && @ < $y)' PASSING 0 AS x, 1 AS y); + +-- extension: boolean expressions +SELECT JSON_EXISTS(jsonb '1', '$ > 2'); +SELECT JSON_EXISTS(jsonb '1', '$.a > 2' ERROR ON ERROR); + +-- JSON_VALUE + +SELECT JSON_VALUE(NULL::jsonb, '$'); + +SELECT JSON_VALUE(jsonb 'null', '$'); +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING int); + +SELECT JSON_VALUE(jsonb 'true', '$'); +SELECT JSON_VALUE(jsonb 'true', '$' RETURNING bool); + +SELECT JSON_VALUE(jsonb '123', '$'); +SELECT JSON_VALUE(jsonb '123', '$' RETURNING int) + 234; +SELECT JSON_VALUE(jsonb '123', '$' RETURNING text); +/* jsonb bytea ??? */ +SELECT JSON_VALUE(jsonb '123', '$' RETURNING bytea ERROR ON ERROR); + +SELECT JSON_VALUE(jsonb '1.23', '$'); +SELECT JSON_VALUE(jsonb '1.23', '$' RETURNING int); +SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING numeric); +SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING int ERROR ON ERROR); + +SELECT JSON_VALUE(jsonb '"aaa"', '$'); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING text); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING char(5)); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING char(2)); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING json); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING jsonb); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING json ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING jsonb ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '"\"aaa\""', '$' RETURNING json); +SELECT JSON_VALUE(jsonb '"\"aaa\""', '$' RETURNING jsonb); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int DEFAULT 111 ON ERROR); +SELECT JSON_VALUE(jsonb '"123"', '$' RETURNING int) + 234; + +SELECT JSON_VALUE(jsonb '"2017-02-20"', '$' RETURNING date) + 9; + +-- Test NULL checks execution in domain types +CREATE DOMAIN sqljsonb_int_not_null AS int NOT NULL; +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null); +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null NULL ON ERROR); +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT NULL ON ERROR); + +SELECT JSON_VALUE(jsonb '[]', '$'); +SELECT JSON_VALUE(jsonb '[]', '$' ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '{}', '$'); +SELECT JSON_VALUE(jsonb '{}', '$' ERROR ON ERROR); + +SELECT JSON_VALUE(jsonb '1', '$.a'); +SELECT JSON_VALUE(jsonb '1', 'strict $.a' ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'strict $.a' DEFAULT 'error' ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'lax $.a' ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'lax $.a' ERROR ON EMPTY ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'strict $.a' DEFAULT 2 ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT 2 ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT '2' ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'lax $.a' NULL ON EMPTY DEFAULT '2' ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT '2' ON EMPTY DEFAULT '3' ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'lax $.a' ERROR ON EMPTY DEFAULT '3' ON ERROR); + +SELECT JSON_VALUE(jsonb '[1,2]', '$[*]' ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '[1,2]', '$[*]' DEFAULT '0' ON ERROR); +SELECT JSON_VALUE(jsonb '[" "]', '$[*]' RETURNING int ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '[" "]', '$[*]' RETURNING int DEFAULT 2 + 3 ON ERROR); +SELECT JSON_VALUE(jsonb '["1"]', '$[*]' RETURNING int DEFAULT 2 + 3 ON ERROR); + +SELECT + x, + JSON_VALUE( + jsonb '{"a": 1, "b": 2}', + '$.* ? (@ > $x)' PASSING x AS x + RETURNING int + DEFAULT -1 ON EMPTY + DEFAULT -2 ON ERROR + ) y +FROM + generate_series(0, 2) x; + +SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )' AS a); +SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )' AS a RETURNING point); + +-- Test timestamptz passing and output +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts); +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING timestamptz); +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING timestamp); +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING json); +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING jsonb); + +-- JSON_QUERY + +SELECT + JSON_QUERY(js, '$'), + JSON_QUERY(js, '$' WITHOUT WRAPPER), + JSON_QUERY(js, '$' WITH CONDITIONAL WRAPPER), + JSON_QUERY(js, '$' WITH UNCONDITIONAL ARRAY WRAPPER), + JSON_QUERY(js, '$' WITH ARRAY WRAPPER) +FROM + (VALUES + (jsonb 'null'), + ('12.3'), + ('true'), + ('"aaa"'), + ('[1, null, "2"]'), + ('{"a": 1, "b": [2]}') + ) foo(js); + +SELECT + JSON_QUERY(js, 'strict $[*]') AS "unspec", + JSON_QUERY(js, 'strict $[*]' WITHOUT WRAPPER) AS "without", + JSON_QUERY(js, 'strict $[*]' WITH CONDITIONAL WRAPPER) AS "with cond", + JSON_QUERY(js, 'strict $[*]' WITH UNCONDITIONAL ARRAY WRAPPER) AS "with uncond", + JSON_QUERY(js, 'strict $[*]' WITH ARRAY WRAPPER) AS "with" +FROM + (VALUES + (jsonb '1'), + ('[]'), + ('[null]'), + ('[12.3]'), + ('[true]'), + ('["aaa"]'), + ('[[1, 2, 3]]'), + ('[{"a": 1, "b": [2]}]'), + ('[1, "2", null, [3]]') + ) foo(js); + +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text); +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text KEEP QUOTES); +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text KEEP QUOTES ON SCALAR STRING); +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text OMIT QUOTES); +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text OMIT QUOTES ON SCALAR STRING); +SELECT JSON_QUERY(jsonb '"aaa"', '$' OMIT QUOTES ERROR ON ERROR); +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING json OMIT QUOTES ERROR ON ERROR); +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING bytea FORMAT JSON OMIT QUOTES ERROR ON ERROR); + +-- QUOTES behavior should not be specified when WITH WRAPPER used: +-- Should fail +SELECT JSON_QUERY(jsonb '[1]', '$' WITH WRAPPER OMIT QUOTES); +SELECT JSON_QUERY(jsonb '[1]', '$' WITH WRAPPER KEEP QUOTES); +SELECT JSON_QUERY(jsonb '[1]', '$' WITH CONDITIONAL WRAPPER KEEP QUOTES); +SELECT JSON_QUERY(jsonb '[1]', '$' WITH CONDITIONAL WRAPPER OMIT QUOTES); +-- Should succeed +SELECT JSON_QUERY(jsonb '[1]', '$' WITHOUT WRAPPER OMIT QUOTES); +SELECT JSON_QUERY(jsonb '[1]', '$' WITHOUT WRAPPER KEEP QUOTES); + +SELECT JSON_QUERY(jsonb '[]', '$[*]'); +SELECT JSON_QUERY(jsonb '[]', '$[*]' NULL ON EMPTY); +SELECT JSON_QUERY(jsonb '[]', '$[*]' EMPTY ARRAY ON EMPTY); +SELECT JSON_QUERY(jsonb '[]', '$[*]' EMPTY OBJECT ON EMPTY); +SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY); + +SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY NULL ON ERROR); +SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY EMPTY ARRAY ON ERROR); +SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY EMPTY OBJECT ON ERROR); +SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY ERROR ON ERROR); +SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON ERROR); + +SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' ERROR ON ERROR); + +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING json); +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING json FORMAT JSON); +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING jsonb); +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING jsonb FORMAT JSON); +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING text); +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING char(10)); +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING char(3)); +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING text FORMAT JSON); +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING bytea); +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING bytea FORMAT JSON); + +SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING bytea EMPTY OBJECT ON ERROR); +SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING bytea FORMAT JSON EMPTY OBJECT ON ERROR); +SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING json EMPTY OBJECT ON ERROR); +SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING jsonb EMPTY OBJECT ON ERROR); + +SELECT + x, y, + JSON_QUERY( + jsonb '[1,2,3,4,5,null]', + '$[*] ? (@ >= $x && @ <= $y)' + PASSING x AS x, y AS y + WITH CONDITIONAL WRAPPER + EMPTY ARRAY ON EMPTY + ) list +FROM + generate_series(0, 4) x, + generate_series(0, 4) y; + +-- Extension: record types returning +CREATE TYPE sqljsonb_rec AS (a int, t text, js json, jb jsonb, jsa json[]); +CREATE TYPE sqljsonb_reca AS (reca sqljsonb_rec[]); + +SELECT JSON_QUERY(jsonb '[{"a": 1, "b": "foo", "t": "aaa", "js": [1, "2", {}], "jb": {"x": [1, "2", {}]}}, {"a": 2}]', '$[0]' RETURNING sqljsonb_rec); +SELECT * FROM unnest((JSON_QUERY(jsonb '{"jsa": [{"a": 1, "b": ["foo"]}, {"a": 2, "c": {}}, 123]}', '$' RETURNING sqljsonb_rec)).jsa); +SELECT * FROM unnest((JSON_QUERY(jsonb '{"reca": [{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]}', '$' RETURNING sqljsonb_reca)).reca); + +-- Extension: array types returning +SELECT JSON_QUERY(jsonb '[1,2,null,"3"]', '$[*]' RETURNING int[] WITH WRAPPER); +SELECT * FROM unnest(JSON_QUERY(jsonb '[{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]', '$' RETURNING sqljsonb_rec[])); + +-- Extension: domain types returning +SELECT JSON_QUERY(jsonb '{"a": 1}', '$.a' RETURNING sqljsonb_int_not_null); +SELECT JSON_QUERY(jsonb '{"a": 1}', '$.b' RETURNING sqljsonb_int_not_null); + +-- Test timestamptz passing and output +SELECT JSON_QUERY(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts); +SELECT JSON_QUERY(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING json); +SELECT JSON_QUERY(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING jsonb); + +-- Test constraints + +CREATE TABLE test_jsonb_constraints ( + js text, + i int, + x jsonb DEFAULT JSON_QUERY(jsonb '[1,2]', '$[*]' WITH WRAPPER) + CONSTRAINT test_jsonb_constraint1 + CHECK (js IS JSON) + CONSTRAINT test_jsonb_constraint2 + CHECK (JSON_EXISTS(js::jsonb, '$.a' PASSING i + 5 AS int, i::text AS txt, array[1,2,3] as arr)) + CONSTRAINT test_jsonb_constraint3 + CHECK (JSON_VALUE(js::jsonb, '$.a' RETURNING int DEFAULT ('12' || i)::int ON EMPTY ERROR ON ERROR) > i) + CONSTRAINT test_jsonb_constraint4 + CHECK (JSON_QUERY(js::jsonb, '$.a' WITH CONDITIONAL WRAPPER EMPTY OBJECT ON ERROR) < jsonb '[10]') + CONSTRAINT test_jsonb_constraint5 + CHECK (JSON_QUERY(js::jsonb, '$.a' RETURNING char(5) OMIT QUOTES EMPTY ARRAY ON EMPTY) > 'a') +); + +\d test_jsonb_constraints + +SELECT check_clause +FROM information_schema.check_constraints +WHERE constraint_name LIKE 'test_jsonb_constraint%'; + +SELECT pg_get_expr(adbin, adrelid) FROM pg_attrdef WHERE adrelid = 'test_jsonb_constraints'::regclass; + +INSERT INTO test_jsonb_constraints VALUES ('', 1); +INSERT INTO test_jsonb_constraints VALUES ('1', 1); +INSERT INTO test_jsonb_constraints VALUES ('[]'); +INSERT INTO test_jsonb_constraints VALUES ('{"b": 1}', 1); +INSERT INTO test_jsonb_constraints VALUES ('{"a": 1}', 1); +INSERT INTO test_jsonb_constraints VALUES ('{"a": 7}', 1); +INSERT INTO test_jsonb_constraints VALUES ('{"a": 10}', 1); + +DROP TABLE test_jsonb_constraints; + +-- Extension: non-constant JSON path +SELECT JSON_EXISTS(jsonb '{"a": 123}', '$' || '.' || 'a'); +SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'a'); +SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'b' DEFAULT 'foo' ON EMPTY); +SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a'); +SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a' WITH WRAPPER); +-- Should fail (invalid path) +SELECT JSON_QUERY(jsonb '{"a": 123}', 'error' || ' ' || 'error'); + +-- Test parallel JSON_VALUE() +CREATE TABLE test_parallel_jsonb_value AS +SELECT i::text::jsonb AS js +FROM generate_series(1, 1000000) i; + +-- Should be non-parallel due to subtransactions +EXPLAIN (COSTS OFF) +SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jsonb_value; +SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jsonb_value; + +-- Should be parallel +EXPLAIN (COSTS OFF) +SELECT sum(JSON_VALUE(js, '$' RETURNING numeric ERROR ON ERROR)) FROM test_parallel_jsonb_value; +SELECT sum(JSON_VALUE(js, '$' RETURNING numeric ERROR ON ERROR)) FROM test_parallel_jsonb_value; + diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql index 91c68f4..7f035ba 100644 --- a/src/test/regress/sql/opr_sanity.sql +++ b/src/test/regress/sql/opr_sanity.sql @@ -842,8 +842,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 0000000..4f3c06d --- /dev/null +++ b/src/test/regress/sql/sqljson.sql @@ -0,0 +1,378 @@ +-- 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; + +-- IS JSON predicate +SELECT NULL IS JSON; +SELECT NULL IS NOT JSON; +SELECT NULL::json IS JSON; +SELECT NULL::jsonb IS JSON; +SELECT NULL::text IS JSON; +SELECT NULL::bytea IS JSON; +SELECT NULL::int IS JSON; + +SELECT '' IS JSON; + +SELECT bytea '\x00' IS JSON; + +CREATE TABLE test_is_json (js text); + +INSERT INTO test_is_json VALUES + (NULL), + (''), + ('123'), + ('"aaa "'), + ('true'), + ('null'), + ('[]'), + ('[1, "2", {}]'), + ('{}'), + ('{ "a": 1, "b": null }'), + ('{ "a": 1, "a": null }'), + ('{ "a": 1, "b": [{ "a": 1 }, { "a": 2 }] }'), + ('{ "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] }'), + ('aaa'), + ('{a:1}'), + ('["a",]'); + +SELECT + js, + js IS JSON "IS JSON", + js IS NOT JSON "IS NOT JSON", + js IS JSON VALUE "IS VALUE", + js IS JSON OBJECT "IS OBJECT", + js IS JSON ARRAY "IS ARRAY", + js IS JSON SCALAR "IS SCALAR", + js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE", + js IS JSON WITH UNIQUE KEYS "WITH UNIQUE" +FROM + test_is_json; + +SELECT + js, + js IS JSON "IS JSON", + js IS NOT JSON "IS NOT JSON", + js IS JSON VALUE "IS VALUE", + js IS JSON OBJECT "IS OBJECT", + js IS JSON ARRAY "IS ARRAY", + js IS JSON SCALAR "IS SCALAR", + js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE", + js IS JSON WITH UNIQUE KEYS "WITH UNIQUE" +FROM + (SELECT js::json FROM test_is_json WHERE js IS JSON) foo(js); + +SELECT + js0, + js IS JSON "IS JSON", + js IS NOT JSON "IS NOT JSON", + js IS JSON VALUE "IS VALUE", + js IS JSON OBJECT "IS OBJECT", + js IS JSON ARRAY "IS ARRAY", + js IS JSON SCALAR "IS SCALAR", + js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE", + js IS JSON WITH UNIQUE KEYS "WITH UNIQUE" +FROM + (SELECT js, js::bytea FROM test_is_json WHERE js IS JSON) foo(js0, js); + +SELECT + js, + js IS JSON "IS JSON", + js IS NOT JSON "IS NOT JSON", + js IS JSON VALUE "IS VALUE", + js IS JSON OBJECT "IS OBJECT", + js IS JSON ARRAY "IS ARRAY", + js IS JSON SCALAR "IS SCALAR", + js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE", + js IS JSON WITH UNIQUE KEYS "WITH UNIQUE" +FROM + (SELECT js::jsonb FROM test_is_json WHERE js IS JSON) foo(js); + +-- Test IS JSON deparsing +EXPLAIN (VERBOSE, COSTS OFF) +SELECT '1' IS JSON AS "any", ('1' || i) IS JSON SCALAR AS "scalar", '[]' IS NOT JSON ARRAY AS "array", '{}' IS JSON OBJECT WITH UNIQUE AS "object" FROM generate_series(1, 3) i; + +CREATE VIEW is_json_view AS +SELECT '1' IS JSON AS "any", ('1' || i) IS JSON SCALAR AS "scalar", '[]' IS NOT JSON ARRAY AS "array", '{}' IS JSON OBJECT WITH UNIQUE AS "object" FROM generate_series(1, 3) i; + +\sv is_json_view + +DROP VIEW is_json_view; -- 2.7.4