0009-sqljson-v09.patch
text/x-patch
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
Series: patch v9-0009
| File | + | − |
|---|---|---|
| contrib/pg_stat_statements/pg_stat_statements.c | 41 | 0 |
| src/backend/executor/execExpr.c | 210 | 0 |
| src/backend/executor/execExprInterp.c | 472 | 0 |
| src/backend/nodes/copyfuncs.c | 367 | 0 |
| src/backend/nodes/equalfuncs.c | 120 | 0 |
| src/backend/nodes/makefuncs.c | 85 | 0 |
| src/backend/nodes/nodeFuncs.c | 289 | 0 |
| src/backend/nodes/outfuncs.c | 110 | 0 |
| src/backend/nodes/readfuncs.c | 133 | 0 |
| src/backend/optimizer/path/costsize.c | 2 | 1 |
| src/backend/parser/gram.y | 677 | 19 |
| src/backend/parser/parse_collate.c | 4 | 0 |
| src/backend/parser/parse_expr.c | 1250 | 0 |
| src/backend/parser/parser.c | 17 | 1 |
| src/backend/parser/parse_target.c | 15 | 0 |
| src/backend/utils/adt/jsonb.c | 369 | 22 |
| src/backend/utils/adt/json.c | 481 | 24 |
| src/backend/utils/adt/jsonfuncs.c | 44 | 0 |
| src/backend/utils/adt/jsonpath_exec.c | 101 | 0 |
| src/backend/utils/adt/ruleutils.c | 345 | 18 |
| src/include/catalog/pg_aggregate.h | 4 | 0 |
| src/include/catalog/pg_proc.h | 37 | 0 |
| src/include/executor/execExpr.h | 56 | 0 |
| src/include/nodes/makefuncs.h | 7 | 0 |
| src/include/nodes/nodes.h | 19 | 0 |
| src/include/nodes/parsenodes.h | 215 | 0 |
| src/include/nodes/primnodes.h | 166 | 0 |
| src/include/parser/kwlist.h | 20 | 0 |
| src/include/utils/jsonapi.h | 4 | 0 |
| src/include/utils/jsonb.h | 3 | 0 |
| src/include/utils/jsonpath.h | 18 | 1 |
| src/interfaces/ecpg/preproc/parse.pl | 2 | 0 |
| src/interfaces/ecpg/preproc/parser.c | 17 | 0 |
| src/test/regress/expected/jsonb_sqljson.out | 905 | 0 |
| src/test/regress/expected/json_sqljson.out | 15 | 0 |
| src/test/regress/expected/opr_sanity.out | 2 | 1 |
| src/test/regress/expected/sqljson.out | 940 | 0 |
| src/test/regress/parallel_schedule | 1 | 1 |
| src/test/regress/serial_schedule | 3 | 0 |
| src/test/regress/sql/jsonb_sqljson.sql | 275 | 0 |
| src/test/regress/sql/json_sqljson.sql | 11 | 0 |
| src/test/regress/sql/sqljson.sql | 378 | 0 |
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index afc56a1..89e66de 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -2811,6 +2811,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 c6eb3eb..7195aae 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -32,6 +32,7 @@
#include "access/nbtree.h"
#include "catalog/objectaccess.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "executor/execExpr.h"
#include "executor/nodeSubplan.h"
@@ -44,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"
@@ -77,6 +79,7 @@ static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate,
ExprEvalStep *scratch,
FunctionCallInfo fcinfo, AggStatePerTrans pertrans,
int transno, int setno, int setoff, bool ishash);
+static char getJsonExprVolatility(JsonExpr *jsexpr);
/*
@@ -2109,6 +2112,112 @@ 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 =
+ ExecInitExpr((Expr *) jexpr->formatted_expr,
+ state->parent);
+
+ scratch.d.jsonexpr.result_expr = jexpr->result_coercion
+ ? ExecInitExpr((Expr *) jexpr->result_coercion->expr,
+ state->parent)
+ : 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;
+
+ for (cstate = &scratch.d.jsonexpr.coercions.null,
+ coercion = &jexpr->coercions->null;
+ coercion <= &jexpr->coercions->composite;
+ coercion++, cstate++)
+ {
+ cstate->coercion = *coercion;
+ cstate->estate = *coercion ?
+ ExecInitExpr((Expr *)(*coercion)->expr,
+ state->parent) : NULL;
+ }
+ }
+
+ if (jexpr->on_error.btype != JSON_BEHAVIOR_ERROR)
+ scratch.d.jsonexpr.volatility = getJsonExprVolatility(jexpr);
+
+ ExprEvalPushStep(state, &scratch);
+ }
+ break;
+
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
@@ -3193,3 +3302,104 @@ ExecBuildAggTransCall(ExprState *state, AggState *aggstate,
as->d.agg_strict_trans_check.jumpnull = state->steps_len;
}
}
+
+static char
+getExprVolatility(Node *expr)
+{
+ if (contain_volatile_functions(expr))
+ return PROVOLATILE_VOLATILE;
+
+ if (contain_mutable_functions(expr))
+ return PROVOLATILE_STABLE;
+
+ return PROVOLATILE_IMMUTABLE;
+}
+
+static char
+getJsonCoercionVolatility(JsonCoercion *coercion, JsonReturning *returning)
+{
+ if (!coercion)
+ return PROVOLATILE_IMMUTABLE;
+
+ if (coercion->expr)
+ return getExprVolatility(coercion->expr);
+
+ if (coercion->via_io)
+ {
+ Oid typinput;
+ Oid typioparam;
+
+ getTypeInputInfo(returning->typid, &typinput, &typioparam);
+
+ return func_volatile(typinput);
+ }
+
+ if (coercion->via_populate)
+ return PROVOLATILE_STABLE;
+
+ return PROVOLATILE_IMMUTABLE;
+}
+
+static char
+getJsonExprVolatility(JsonExpr *jsexpr)
+{
+ char volatility;
+ char volatility2;
+ ListCell *lc;
+
+ volatility = getExprVolatility(jsexpr->formatted_expr);
+
+ if (volatility == PROVOLATILE_VOLATILE)
+ return PROVOLATILE_VOLATILE;
+
+ volatility2 = getJsonCoercionVolatility(jsexpr->result_coercion,
+ &jsexpr->returning);
+
+ if (volatility2 == PROVOLATILE_VOLATILE)
+ return PROVOLATILE_VOLATILE;
+
+ if (volatility2 == PROVOLATILE_STABLE)
+ volatility = PROVOLATILE_STABLE;
+
+ if (jsexpr->coercions)
+ {
+ JsonCoercion **coercion;
+
+ for (coercion = &jsexpr->coercions->null;
+ coercion <= &jsexpr->coercions->composite;
+ coercion++)
+ {
+ volatility2 = getJsonCoercionVolatility(*coercion, &jsexpr->returning);
+
+ if (volatility2 == PROVOLATILE_VOLATILE)
+ return PROVOLATILE_VOLATILE;
+
+ if (volatility2 == PROVOLATILE_STABLE)
+ volatility = PROVOLATILE_STABLE;
+ }
+ }
+
+ if (jsexpr->on_empty.btype == JSON_BEHAVIOR_DEFAULT)
+ {
+ volatility2 = getExprVolatility(jsexpr->on_empty.default_expr);
+
+ if (volatility2 == PROVOLATILE_VOLATILE)
+ return PROVOLATILE_VOLATILE;
+
+ if (volatility2 == PROVOLATILE_STABLE)
+ volatility = PROVOLATILE_STABLE;
+ }
+
+ foreach(lc, jsexpr->passing.values)
+ {
+ volatility2 = getExprVolatility(lfirst(lc));
+
+ if (volatility2 == PROVOLATILE_VOLATILE)
+ return PROVOLATILE_VOLATILE;
+
+ if (volatility2 == PROVOLATILE_STABLE)
+ volatility = PROVOLATILE_STABLE;
+ }
+
+ return volatility;
+}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index f646fd9..894cea8 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,13 +66,19 @@
#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/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"
@@ -382,6 +390,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,
@@ -1751,7 +1760,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();
}
@@ -4025,3 +4040,460 @@ ExecEvalAggOrderedTransTuple(ExprState *state, ExprEvalStep *op,
ExecStoreVirtualTuple(pertrans->sortslot);
tuplesort_puttupleslot(pertrans->sortstates[setno], pertrans->sortslot);
}
+
+/*
+ * Evaluate a expression substituting specified value in its CaseTestExpr nodes.
+ */
+static Datum
+ExecEvalExprPassingCaseValue(ExprState *estate, ExprContext *econtext,
+ bool *isnull,
+ Datum caseval_datum, bool caseval_isnull)
+{
+ Datum res;
+ Datum save_datum = econtext->caseValue_datum;
+ bool save_isNull = econtext->caseValue_isNull;
+
+ econtext->caseValue_datum = caseval_datum;
+ econtext->caseValue_isNull = caseval_isnull;
+
+ PG_TRY();
+ {
+ res = ExecEvalExpr(estate, econtext, isnull);
+ }
+ PG_CATCH();
+ {
+ econtext->caseValue_datum = save_datum;
+ econtext->caseValue_isNull = save_isNull;
+
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ econtext->caseValue_datum = save_datum;
+ econtext->caseValue_isNull = save_isNull;
+
+ return res;
+}
+
+/*
+ * 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)
+ res = ExecEvalExprPassingCaseValue(op->d.jsonexpr.result_expr, econtext,
+ isNull, res, *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;
+
+ item = ExecEvalExprPassingCaseValue(op->d.jsonexpr.formatted_expr,
+ econtext, &isnull, item, false);
+ 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)
+ break;
+
+ *resnull = false;
+
+ res = ExecPrepareJsonItemCoercion(jbv,
+ &op->d.jsonexpr.jsexpr->returning,
+ &op->d.jsonexpr.coercions,
+ &jcstate);
+
+ /* coerce item datum to the output type */
+ if ((jcstate->coercion &&
+ (jcstate->coercion->via_io ||
+ jcstate->coercion->via_populate)) || /* ignored for scalars jsons */
+ jexpr->returning.typid == JSONOID ||
+ jexpr->returning.typid == JSONBOID)
+ {
+ /* use coercion via I/O from json[b] to the output type */
+ res = JsonbPGetDatum(JsonbValueToJsonb(jbv));
+ res = ExecEvalJsonExprCoercion(op, econtext, res, resnull);
+ }
+ else if (jcstate->estate)
+ {
+ res = ExecEvalExprPassingCaseValue(jcstate->estate,
+ econtext,
+ resnull,
+ res, false);
+ }
+ /* else no coercion */
+ }
+ break;
+
+ case IS_JSON_EXISTS:
+ res = BoolGetDatum(JsonbPathExists(item, path, op->d.jsonexpr.args));
+ *resnull = false;
+ break;
+
+ 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);
+ }
+
+ if (jexpr->op != IS_JSON_EXISTS &&
+ (!empty ? jexpr->op != IS_JSON_VALUE :
+ /* result is already coerced in DEFAULT behavior case */
+ jexpr->on_empty.btype != JSON_BEHAVIOR_DEFAULT))
+ res = ExecEvalJsonExprCoercion(op, econtext, res, resnull);
+
+ return res;
+}
+
+/* ----------------------------------------------------------------
+ * 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 (jexpr->on_error.btype == JSON_BEHAVIOR_ERROR)
+ {
+ /* 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.
+ */
+ char volatility = op->d.jsonexpr.volatility;
+ bool useSubTransaction = volatility == PROVOLATILE_VOLATILE;
+ bool useSubResourceOwner = volatility == PROVOLATILE_STABLE;
+ MemoryContext oldcontext = CurrentMemoryContext;
+ ResourceOwner oldowner = CurrentResourceOwner;
+ ResourceOwner newowner = NULL;
+ ExprContext *newecontext = econtext;
+
+ if (useSubTransaction)
+ {
+ BeginInternalSubTransaction(NULL);
+ /* Want to execute expressions inside function's memory context */
+ MemoryContextSwitchTo(oldcontext);
+ /*
+ * We need to execute expressions with a new econtext
+ * that belongs to the current subtransaction; if we try to use
+ * the outer econtext then ExprContext shutdown callbacks will be
+ * called at the wrong times.
+ */
+ newecontext = CreateExprContext(econtext->ecxt_estate);
+ }
+ else if (useSubResourceOwner)
+ {
+ newowner = ResourceOwnerCreate(CurrentResourceOwner, "JsonExpr");
+ CurrentResourceOwner = newowner;
+ }
+
+ PG_TRY();
+ {
+ res = ExecEvalJsonExpr(state, op, newecontext, jexpr, path, item,
+ op->resnull);
+
+ if (useSubTransaction)
+ {
+ /* Commit the inner transaction, return to outer xact context */
+ ReleaseCurrentSubTransaction();
+ MemoryContextSwitchTo(oldcontext);
+ CurrentResourceOwner = oldowner;
+ FreeExprContext(newecontext, true);
+ }
+ else if (useSubResourceOwner)
+ {
+ /* buffer pins are released here: */
+ CurrentResourceOwner = oldowner;
+ ResourceOwnerRelease(newowner,
+ RESOURCE_RELEASE_BEFORE_LOCKS,
+ false, true);
+ ResourceOwnerDelete(newowner);
+ }
+ }
+ PG_CATCH();
+ {
+ ErrorData *edata;
+
+ /* Save error info in oldcontext */
+ MemoryContextSwitchTo(oldcontext);
+ edata = CopyErrorData();
+ FlushErrorState();
+
+ if (useSubTransaction)
+ {
+ /* Abort the inner transaction */
+ RollbackAndReleaseCurrentSubTransaction();
+ MemoryContextSwitchTo(oldcontext);
+ CurrentResourceOwner = oldowner;
+ FreeExprContext(newecontext, false);
+ }
+ else if (useSubResourceOwner)
+ {
+ /* buffer pins are released here: */
+ CurrentResourceOwner = oldowner;
+ ResourceOwnerRelease(newowner,
+ RESOURCE_RELEASE_BEFORE_LOCKS,
+ false, true);
+ ResourceOwnerDelete(newowner);
+ }
+
+ 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/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 16a8460..60a2486 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2138,6 +2138,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;
+}
+
+/*
+ * _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;
+}
+
+/*
+ * _copyJsonOutput
+ */
+static JsonOutput *
+_copyJsonOutput(const JsonOutput *from)
+{
+ JsonOutput *newnode = makeNode(JsonOutput);
+
+ COPY_NODE_FIELD(typename);
+ COPY_SCALAR_FIELD(returning);
+
+ 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
*
@@ -5026,6 +5339,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_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_JsonOutput:
+ retval = _copyJsonOutput(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 8e713e9..1f8a75f 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
*/
@@ -3171,6 +3273,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 1bd2599..ebc41ea 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"
@@ -628,3 +629,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 6c76c41..79cb602 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;
@@ -2218,6 +2282,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));
@@ -3035,6 +3150,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));
@@ -3679,6 +3853,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 a406da1..9ac2b24 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1713,6 +1713,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.
@@ -4264,6 +4356,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 745d3f3..620c878 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1335,6 +1335,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.
*/
@@ -2689,6 +2810,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 29fea48..f1b9db5 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -3899,7 +3899,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/parser/gram.y b/src/backend/parser/gram.y
index d99f2be..8b212b5 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 <node> stmt schema_stmt
@@ -583,6 +590,72 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <list> hash_partbound partbound_datum_list range_datum_list
%type <defelt> hash_partbound_elem
+%type <node> 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 <list> json_arguments
+ json_passing_clause_opt
+ json_name_and_value_list
+ json_value_expr_list
+ json_array_aggregate_order_by_clause_opt
+
+%type <typnam> json_returning_clause_opt
+
+%type <str> json_table_path_name
+ json_as_path_name_clause_opt
+
+%type <ival> json_encoding
+ json_encoding_clause_opt
+ json_wrapper_clause_opt
+ json_wrapper_behavior
+ json_conditional_or_unconditional_opt
+ json_predicate_type_constraint_opt
+
+%type <jsformat> json_format_clause_opt
+ json_representation
+
+%type <jsbehavior> 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 <on_behavior> json_value_on_behavior_clause_opt
+ json_query_on_behavior_clause_opt
+
+%type <js_quotes> json_quotes_behavior
+ json_quotes_clause_opt
+
+%type <boolean> 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
@@ -605,7 +678,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
*/
/* ordinary key words in alphabetical order */
-%token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
+%token <keyword> 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
@@ -615,8 +688,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
@@ -626,12 +699,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
@@ -642,9 +715,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
@@ -656,7 +730,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
@@ -664,17 +738,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
@@ -682,8 +756,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
@@ -707,11 +781,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
@@ -750,6 +824,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 '+' '-'
@@ -774,6 +850,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
+
%%
/*
@@ -12770,7 +12849,7 @@ ConstInterval:
opt_timezone:
WITH_LA TIME ZONE { $$ = true; }
- | WITHOUT TIME ZONE { $$ = false; }
+ | WITHOUT_LA TIME ZONE { $$ = false; }
| /*EMPTY*/ { $$ = false; }
;
@@ -13271,6 +13350,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
{
/*
@@ -13363,6 +13484,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.
*
@@ -13623,6 +13763,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; }
;
@@ -13636,6 +13783,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; }
;
/*
@@ -13857,6 +14005,8 @@ func_expr_common_subexpr:
n->location = @1;
$$ = (Node *)n;
}
+ | json_func_expr
+ { $$ = $1; }
;
/*
@@ -14545,6 +14695,494 @@ 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;
+ }
+ /* | 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; }
+ ;
/*****************************************************************************
*
@@ -14938,6 +15576,7 @@ ColLabel: IDENT { $$ = $1; }
*/
unreserved_keyword:
ABORT_P
+ | ABSENT
| ABSOLUTE_P
| ACCESS
| ACTION
@@ -14974,6 +15613,7 @@ unreserved_keyword:
| COMMENTS
| COMMIT
| COMMITTED
+ | CONDITIONAL
| CONFIGURATION
| CONFLICT
| CONNECTION
@@ -15009,10 +15649,12 @@ unreserved_keyword:
| DOUBLE_P
| DROP
| EACH
+ | EMPTY_P
| ENABLE_P
| ENCODING
| ENCRYPTED
| ENUM_P
+ | ERROR_P
| ESCAPE
| EVENT
| EXCLUDE
@@ -15057,7 +15699,10 @@ unreserved_keyword:
| INSTEAD
| INVOKER
| ISOLATION
+ | JSON
+ | KEEP
| KEY
+ | KEYS
| LABEL
| LANGUAGE
| LARGE_P
@@ -15095,6 +15740,7 @@ unreserved_keyword:
| OFF
| OIDS
| OLD
+ | OMIT
| OPERATOR
| OPTION
| OPTIONS
@@ -15124,6 +15770,7 @@ unreserved_keyword:
| PROGRAM
| PUBLICATION
| QUOTE
+ | QUOTES
| RANGE
| READ
| REASSIGN
@@ -15152,6 +15799,7 @@ unreserved_keyword:
| ROWS
| RULE
| SAVEPOINT
+ | SCALAR
| SCHEMA
| SCHEMAS
| SCROLL
@@ -15200,6 +15848,7 @@ unreserved_keyword:
| TYPES_P
| UNBOUNDED
| UNCOMMITTED
+ | UNCONDITIONAL
| UNENCRYPTED
| UNKNOWN
| UNLISTEN
@@ -15257,6 +15906,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
@@ -15271,6 +15927,7 @@ col_name_keyword:
| ROW
| SETOF
| SMALLINT
+ | STRING
| SUBSTRING
| TIME
| TIMESTAMP
@@ -15308,6 +15965,7 @@ type_func_name_keyword:
| CONCURRENTLY
| CROSS
| CURRENT_SCHEMA
+ | FORMAT
| FREEZE
| FULL
| ILIKE
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 385e54a..67ee55c 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,1209 @@ 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, Oid aggfnoid,
+ Oid aggtype, FuncFormat format, JsonCtorOpts *formatopts)
+{
+ Node *node;
+ Expr *aggfilter = agg_ctor->agg_filter ? (Expr *)
+ transformWhereClause(pstate, agg_ctor->agg_filter,
+ EXPR_KIND_FILTER, "FILTER") : NULL;
+
+ 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;
+ Oid aggfnoid;
+ 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)
+ {
+ aggfnoid = F_JSONB_OBJECTAGG;
+ aggtype = JSONBOID;
+ }
+ else
+ {
+ aggfnoid = F_JSON_OBJECTAGG;
+ aggtype = JSONOID;
+ }
+
+ return transformJsonAggCtor(pstate, &agg->ctor, &returning, args, aggfnoid,
+ 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;
+ Oid aggfnoid;
+ Oid aggtype;
+
+ transformJsonOutput(pstate, agg->ctor.output, true, &returning);
+
+ arg = transformJsonValueExprDefault(pstate, agg->arg);
+
+ if (returning.format.type == JS_FORMAT_JSONB)
+ {
+ aggfnoid = agg->absent_on_null ? F_JSONB_AGG_STRICT : F_JSONB_AGG;
+ aggtype = JSONBOID;
+ }
+ else
+ {
+ aggfnoid = agg->absent_on_null ? F_JSON_AGG_STRICT : F_JSON_AGG;
+ aggtype = JSONOID;
+ }
+
+ return transformJsonAggCtor(pstate, &agg->ctor, &returning, list_make1(arg),
+ aggfnoid, 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 ea209cd..112c776 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1917,6 +1917,21 @@ FigureColnameInternal(Node *node, char **name)
case T_XmlSerialize:
*name = "xmlserialize";
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 ff8e5fc..ce892b6 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,
@@ -1943,8 +1976,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;
@@ -1984,9 +2017,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))
{
@@ -1998,7 +2036,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))
{
@@ -2016,6 +2054,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
*/
@@ -2039,18 +2096,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))
{
@@ -2071,6 +2225,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);
@@ -2098,7 +2256,6 @@ json_object_agg_transfn(PG_FUNCTION_ARGS)
else
{
state = (JsonAggState *) PG_GETARG_POINTER(0);
- appendStringInfoString(state->str, ", ");
}
/*
@@ -2114,11 +2271,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))
@@ -2133,6 +2320,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
@@ -2149,6 +2356,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, " }"));
}
@@ -2173,11 +2382,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;
@@ -2186,9 +2393,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();
@@ -2203,19 +2412,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, " : ");
@@ -2225,23 +2468,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;
@@ -2252,7 +2515,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();
@@ -2263,6 +2527,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);
@@ -2274,6 +2541,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
@@ -2504,6 +2789,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 cd2716b..148e658 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);
@@ -1114,11 +1125,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;
@@ -1126,9 +1247,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();
@@ -1143,26 +1266,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
@@ -1178,11 +1344,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;
@@ -1192,7 +1356,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();
@@ -1202,7 +1367,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);
@@ -1210,6 +1380,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
@@ -1460,12 +1648,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;
@@ -1513,6 +1697,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);
@@ -1582,6 +1769,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)
{
@@ -1614,11 +1819,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;
@@ -1632,6 +1835,7 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS)
*jbval;
JsonbValue v;
JsonbIteratorToken type;
+ bool skip;
if (!AggCheckCallContext(fcinfo, &aggcontext))
{
@@ -1651,6 +1855,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);
@@ -1686,6 +1895,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));
@@ -1741,6 +1959,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;
@@ -1813,6 +2043,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)
{
@@ -1847,6 +2097,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 *
@@ -1869,3 +2154,65 @@ JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
return res;
}
+
+/*
+ * 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 fa78451..2fe2f01 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -3047,6 +3047,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 e24712f..2db592c 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -2623,3 +2623,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 95eb0e9..7320c1f 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -463,6 +463,8 @@ static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2);
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 ")
@@ -7332,6 +7334,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;
@@ -7450,6 +7453,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;
@@ -7613,6 +7617,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
@@ -7631,6 +7690,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
*
@@ -8745,6 +8852,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;
@@ -8841,6 +9025,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:
@@ -8916,6 +9101,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;
}
@@ -8932,6 +9177,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;
@@ -8992,22 +9239,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);
@@ -9015,7 +9297,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, ')');
}
/*
@@ -9027,8 +9310,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
@@ -9057,13 +9341,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))
@@ -9099,7 +9394,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);
@@ -9153,6 +9458,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,
@@ -9170,16 +9476,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.h b/src/include/catalog/pg_aggregate.h
index 125bb5b..0d5668d 100644
--- a/src/include/catalog/pg_aggregate.h
+++ b/src/include/catalog/pg_aggregate.h
@@ -311,11 +311,15 @@ DATA(insert ( 3545 n 0 bytea_string_agg_transfn bytea_string_agg_finalfn - - - -
/* json */
DATA(insert ( 3175 n 0 json_agg_transfn json_agg_finalfn - - - - - - f f r r 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3450 n 0 json_agg_strict_transfn json_agg_finalfn - - - - - - f f r r 0 2281 0 0 0 _null_ _null_ ));
DATA(insert ( 3197 n 0 json_object_agg_transfn json_object_agg_finalfn - - - - - - f f r r 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3451 n 0 json_objectagg_transfn json_object_agg_finalfn - - - - - - f f r r 0 2281 0 0 0 _null_ _null_ ));
/* jsonb */
DATA(insert ( 3267 n 0 jsonb_agg_transfn jsonb_agg_finalfn - - - - - - f f r r 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 6063 n 0 jsonb_agg_strict_transfn jsonb_agg_finalfn - - - - - - f f r r 0 2281 0 0 0 _null_ _null_ ));
DATA(insert ( 3270 n 0 jsonb_object_agg_transfn jsonb_object_agg_finalfn - - - - - - f f r r 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 6064 n 0 jsonb_objectagg_transfn jsonb_object_agg_finalfn - - - - - - f f r r 0 2281 0 0 0 _null_ _null_ ));
/* ordered-set and hypothetical-set aggregates */
DATA(insert ( 3972 o 1 ordered_set_transition percentile_disc_final - - - - - - t f s s 0 2281 0 0 0 _null_ _null_ ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index aaeee04..6653cc3 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4537,24 +4537,39 @@ DATA(insert OID = 3156 ( row_to_json PGNSP PGUID 12 1 0 0 0 f f f f t f s s
DESCR("map row to json with optional pretty printing");
DATA(insert OID = 3173 ( json_agg_transfn PGNSP PGUID 12 1 0 0 0 f f f f f f s s 2 0 2281 "2281 2283" _null_ _null_ _null_ _null_ _null_ json_agg_transfn _null_ _null_ _null_ ));
DESCR("json aggregate transition function");
+DATA(insert OID = 3452 ( json_agg_strict_transfn PGNSP PGUID 12 1 0 0 0 f f f f f f s s 2 0 2281 "2281 2283" _null_ _null_ _null_ _null_ _null_ json_agg_strict_transfn _null_ _null_ _null_ ));
+DESCR("json aggregate transition function");
DATA(insert OID = 3174 ( json_agg_finalfn PGNSP PGUID 12 1 0 0 0 f f f f f f i s 1 0 114 "2281" _null_ _null_ _null_ _null_ _null_ json_agg_finalfn _null_ _null_ _null_ ));
DESCR("json aggregate final function");
DATA(insert OID = 3175 ( json_agg PGNSP PGUID 12 1 0 0 0 t f f f f f s s 1 0 114 "2283" _null_ _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
DESCR("aggregate input into json");
+#define F_JSON_AGG 3175
+DATA(insert OID = 3450 ( json_agg_strict PGNSP PGUID 12 1 0 0 0 t f f f f f s s 1 0 114 "2283" _null_ _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+DESCR("aggregate input into json");
+#define F_JSON_AGG_STRICT 3450
DATA(insert OID = 3180 ( json_object_agg_transfn PGNSP PGUID 12 1 0 0 0 f f f f f f s s 3 0 2281 "2281 2276 2276" _null_ _null_ _null_ _null_ _null_ json_object_agg_transfn _null_ _null_ _null_ ));
DESCR("json object aggregate transition function");
+DATA(insert OID = 3453 ( json_objectagg_transfn PGNSP PGUID 12 1 0 0 0 f f f f f f s s 5 0 2281 "2281 2276 2276 16 16" _null_ _null_ _null_ _null_ _null_ json_objectagg_transfn _null_ _null_ _null_ ));
+DESCR("json object aggregate transition function");
DATA(insert OID = 3196 ( json_object_agg_finalfn PGNSP PGUID 12 1 0 0 0 f f f f f f i s 1 0 114 "2281" _null_ _null_ _null_ _null_ _null_ json_object_agg_finalfn _null_ _null_ _null_ ));
DESCR("json object aggregate final function");
DATA(insert OID = 3197 ( json_object_agg PGNSP PGUID 12 1 0 0 0 t f f f f f s s 2 0 114 "2276 2276" _null_ _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
DESCR("aggregate input into a json object");
+DATA(insert OID = 3451 ( json_objectagg PGNSP PGUID 12 1 0 0 0 t f f f f f s s 4 0 114 "2276 2276 16 16" _null_ _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+DESCR("aggregate input into a json object");
+#define F_JSON_OBJECTAGG 3451
DATA(insert OID = 3198 ( json_build_array PGNSP PGUID 12 1 0 2276 0 f f f f f f s s 1 0 114 "2276" "{2276}" "{v}" _null_ _null_ _null_ json_build_array _null_ _null_ _null_ ));
DESCR("build a json array from any inputs");
DATA(insert OID = 3199 ( json_build_array PGNSP PGUID 12 1 0 0 0 f f f f f f s s 0 0 114 "" _null_ _null_ _null_ _null_ _null_ json_build_array_noargs _null_ _null_ _null_ ));
DESCR("build an empty json array");
+DATA(insert OID = 3998 ( json_build_array_ext PGNSP PGUID 12 1 0 2276 0 f f f f f f s s 2 0 114 "16 2276" "{16,2276}" "{i,v}" _null_ _null_ _null_ json_build_array_ext _null_ _null_ _null_ ));
+DESCR("build a json array from any inputs");
DATA(insert OID = 3200 ( json_build_object PGNSP PGUID 12 1 0 2276 0 f f f f f f s s 1 0 114 "2276" "{2276}" "{v}" _null_ _null_ _null_ json_build_object _null_ _null_ _null_ ));
DESCR("build a json object from pairwise key/value inputs");
DATA(insert OID = 3201 ( json_build_object PGNSP PGUID 12 1 0 0 0 f f f f f f s s 0 0 114 "" _null_ _null_ _null_ _null_ _null_ json_build_object_noargs _null_ _null_ _null_ ));
DESCR("build an empty json object");
+DATA(insert OID = 6066 ( json_build_object_ext PGNSP PGUID 12 1 0 2276 0 f f f f f f s s 3 0 114 "16 16 2276" "{16,16,2276}" "{i,i,v}" _null_ _null_ _null_ json_build_object_ext _null_ _null_ _null_ ));
+DESCR("build a json object from pairwise key/value inputs");
DATA(insert OID = 3202 ( json_object PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 114 "1009" _null_ _null_ _null_ _null_ _null_ json_object _null_ _null_ _null_ ));
DESCR("map text array of key value pairs to json object");
DATA(insert OID = 3203 ( json_object PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 114 "1009 1009" _null_ _null_ _null_ _null_ _null_ json_object_two_arg _null_ _null_ _null_ ));
@@ -4563,6 +4578,10 @@ DATA(insert OID = 3176 ( to_json PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0
DESCR("map input to json");
DATA(insert OID = 3261 ( json_strip_nulls PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 114 "114" _null_ _null_ _null_ _null_ _null_ json_strip_nulls _null_ _null_ _null_ ));
DESCR("remove object fields with null values from json");
+DATA(insert OID = 6060 ( json_is_valid PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 16 "114 25 16" _null_ _null_ _null_ _null_ _null_ json_is_valid _null_ _null_ _null_ ));
+DESCR("check json value type and key uniqueness");
+DATA(insert OID = 6061 ( json_is_valid PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 16 "25 25 16" _null_ _null_ _null_ _null_ _null_ json_is_valid _null_ _null_ _null_ ));
+DESCR("check json text validity, value type and key uniqueness");
DATA(insert OID = 3947 ( json_object_field PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 114 "114 25" _null_ _null_ "{from_json, field_name}" _null_ _null_ json_object_field _null_ _null_ _null_ ));
DATA(insert OID = 3948 ( json_object_field_text PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 25 "114 25" _null_ _null_ "{from_json, field_name}" _null_ _null_ json_object_field_text _null_ _null_ _null_ ));
@@ -4997,26 +5016,44 @@ DATA(insert OID = 3787 ( to_jsonb PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0
DESCR("map input to jsonb");
DATA(insert OID = 3265 ( jsonb_agg_transfn PGNSP PGUID 12 1 0 0 0 f f f f f f s s 2 0 2281 "2281 2283" _null_ _null_ _null_ _null_ _null_ jsonb_agg_transfn _null_ _null_ _null_ ));
DESCR("jsonb aggregate transition function");
+DATA(insert OID = 6065 ( jsonb_agg_strict_transfn PGNSP PGUID 12 1 0 0 0 f f f f f f s s 2 0 2281 "2281 2283" _null_ _null_ _null_ _null_ _null_ jsonb_agg_strict_transfn _null_ _null_ _null_ ));
+DESCR("jsonb aggregate transition function");
DATA(insert OID = 3266 ( jsonb_agg_finalfn PGNSP PGUID 12 1 0 0 0 f f f f f f s s 1 0 3802 "2281" _null_ _null_ _null_ _null_ _null_ jsonb_agg_finalfn _null_ _null_ _null_ ));
DESCR("jsonb aggregate final function");
DATA(insert OID = 3267 ( jsonb_agg PGNSP PGUID 12 1 0 0 0 t f f f f f s s 1 0 3802 "2283" _null_ _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
DESCR("aggregate input into jsonb");
+#define F_JSONB_AGG 3267
+DATA(insert OID = 6063 ( jsonb_agg_strict PGNSP PGUID 12 1 0 0 0 t f f f f f s s 1 0 3802 "2283" _null_ _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+DESCR("aggregate input into jsonb skipping nulls");
+#define F_JSONB_AGG_STRICT 6063
DATA(insert OID = 3268 ( jsonb_object_agg_transfn PGNSP PGUID 12 1 0 0 0 f f f f f f s s 3 0 2281 "2281 2276 2276" _null_ _null_ _null_ _null_ _null_ jsonb_object_agg_transfn _null_ _null_ _null_ ));
DESCR("jsonb object aggregate transition function");
+DATA(insert OID = 3449 ( jsonb_objectagg_transfn PGNSP PGUID 12 1 0 0 0 f f f f f f s s 5 0 2281 "2281 2276 2276 16 16" _null_ _null_ _null_ _null_ _null_ jsonb_objectagg_transfn _null_ _null_ _null_ ));
+DESCR("jsonb object aggregate transition function");
DATA(insert OID = 3269 ( jsonb_object_agg_finalfn PGNSP PGUID 12 1 0 0 0 f f f f f f s s 1 0 3802 "2281" _null_ _null_ _null_ _null_ _null_ jsonb_object_agg_finalfn _null_ _null_ _null_ ));
DESCR("jsonb object aggregate final function");
DATA(insert OID = 3270 ( jsonb_object_agg PGNSP PGUID 12 1 0 0 0 t f f f f f i s 2 0 3802 "2276 2276" _null_ _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
DESCR("aggregate inputs into jsonb object");
+DATA(insert OID = 6064 ( jsonb_objectagg PGNSP PGUID 12 1 0 0 0 t f f f f f i s 4 0 3802 "2276 2276 16 16" _null_ _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+DESCR("aggregate inputs into jsonb object");
+#define F_JSONB_OBJECTAGG 6064
DATA(insert OID = 3271 ( jsonb_build_array PGNSP PGUID 12 1 0 2276 0 f f f f f f s s 1 0 3802 "2276" "{2276}" "{v}" _null_ _null_ _null_ jsonb_build_array _null_ _null_ _null_ ));
DESCR("build a jsonb array from any inputs");
DATA(insert OID = 3272 ( jsonb_build_array PGNSP PGUID 12 1 0 0 0 f f f f f f s s 0 0 3802 "" _null_ _null_ _null_ _null_ _null_ jsonb_build_array_noargs _null_ _null_ _null_ ));
DESCR("build an empty jsonb array");
+DATA(insert OID = 6068 ( jsonb_build_array_ext PGNSP PGUID 12 1 0 2276 0 f f f f f f s s 2 0 3802 "16 2276" "{16,2276}" "{i,v}" _null_ _null_ _null_ jsonb_build_array_ext _null_ _null_ _null_ ));
+DESCR("build a jsonb array from any inputs");
DATA(insert OID = 3273 ( jsonb_build_object PGNSP PGUID 12 1 0 2276 0 f f f f f f s s 1 0 3802 "2276" "{2276}" "{v}" _null_ _null_ _null_ jsonb_build_object _null_ _null_ _null_ ));
DESCR("build a jsonb object from pairwise key/value inputs");
DATA(insert OID = 3274 ( jsonb_build_object PGNSP PGUID 12 1 0 0 0 f f f f f f s s 0 0 3802 "" _null_ _null_ _null_ _null_ _null_ jsonb_build_object_noargs _null_ _null_ _null_ ));
DESCR("build an empty jsonb object");
+DATA(insert OID = 6067 ( jsonb_build_object_ext PGNSP PGUID 12 1 0 2276 0 f f f f f f s s 3 0 3802 "16 16 2276" "{16,16,2276}" "{i,i,v}" _null_ _null_ _null_ jsonb_build_object_ext _null_ _null_ _null_ ));
+DESCR("build a jsonb object from pairwise key/value inputs");
DATA(insert OID = 3262 ( jsonb_strip_nulls PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 3802 "3802" _null_ _null_ _null_ _null_ _null_ jsonb_strip_nulls _null_ _null_ _null_ ));
DESCR("remove object fields with null values from jsonb");
+DATA(insert OID = 6062 ( jsonb_is_valid PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "17 25" _null_ _null_ _null_ _null_ _null_ jsonb_is_valid _null_ _null_ _null_ ));
+DESCR("check jsonb value type");
+
DATA(insert OID = 3478 ( jsonb_object_field PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 3802 "3802 25" _null_ _null_ "{from_json, field_name}" _null_ _null_ jsonb_object_field _null_ _null_ _null_ ));
DATA(insert OID = 3214 ( jsonb_object_field_text PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 25 "3802 25" _null_ _null_ "{from_json, field_name}" _null_ _null_ jsonb_object_field_text _null_ _null_ _null_ ));
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 117fc89..0ce2e40 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 */
@@ -218,6 +219,7 @@ typedef enum ExprEvalOp
EEOP_WINDOW_FUNC,
EEOP_SUBPLAN,
EEOP_ALTERNATIVE_SUBPLAN,
+ EEOP_JSONEXPR,
/* aggregation related nodes */
EEOP_AGG_STRICT_DESERIALIZE,
@@ -634,6 +636,54 @@ typedef struct ExprEvalStep
int transno;
int setoff;
} agg_trans;
+
+ /* for EEOP_JSONEXPR */
+ struct
+ {
+ JsonExpr *jsexpr; /* original expression node */
+ char volatility; /* volatility of subexpressions */
+
+ 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 */
+ *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;
@@ -729,6 +779,12 @@ extern void ExecEvalAlternativeSubPlan(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
extern void ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
+extern void ExecEvalJson(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
+extern Datum ExecPrepareJsonItemCoercion(struct JsonbValue *item,
+ JsonReturning *returning,
+ struct JsonCoercionsState *coercions,
+ struct JsonCoercionState **pjcstate);
extern void ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup);
extern Datum ExecAggTransReparent(AggState *aggstate, AggStatePerTrans pertrans,
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 74b094a..14c387a 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -191,6 +191,9 @@ typedef enum NodeTag
T_FromExpr,
T_OnConflictExpr,
T_IntoClause,
+ T_JsonExpr,
+ T_JsonCoercion,
+ T_JsonItemCoercions,
/*
* TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -471,6 +474,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 c7a43b8..4b6732e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1425,6 +1425,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 641500e..4bfa016 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -252,6 +252,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;
/*
@@ -1169,6 +1174,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 cf32197..233e18d 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)
@@ -220,7 +225,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)
@@ -276,6 +291,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)
@@ -317,6 +333,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)
@@ -350,6 +367,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)
@@ -384,6 +402,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, COL_NAME_KEYWORD)
PG_KEYWORD("strip", STRIP_P, UNRESERVED_KEYWORD)
PG_KEYWORD("subscription", SUBSCRIPTION, UNRESERVED_KEYWORD)
PG_KEYWORD("substring", SUBSTRING, COL_NAME_KEYWORD)
@@ -416,6 +435,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 9a8d77f..51529bc 100644
--- a/src/include/utils/jsonapi.h
+++ b/src/include/utils/jsonapi.h
@@ -192,6 +192,10 @@ extern text *transform_json_string_values(text *json, void *action_state,
extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid);
+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 5498b8a..c2a5e17 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -400,6 +400,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 87dae0d..61cf2b7 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
{
@@ -256,7 +257,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
{
@@ -269,4 +278,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 e68cc26..529a5b7 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 e5a8f9d..e576202 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..c78096c
--- /dev/null
+++ b/src/test/regress/expected/jsonb_sqljson.out
@@ -0,0 +1,905 @@
+-- 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);
+ json_value
+------------
+ \x313233
+(1 row)
+
+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 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 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 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)
+
+-- 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 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 adsrc FROM pg_attrdef WHERE adrelid = 'test_jsonb_constraints'::regclass;
+ adsrc
+------------------------------------------------------------------------------------------------------------
+ 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 " "
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index db0bab2..cc82b6a 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -205,11 +205,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
diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out
new file mode 100644
index 0000000..0fd0ea5
--- /dev/null
+++ b/src/test/regress/expected/sqljson.out
@@ -0,0 +1,940 @@
+-- JSON_OBJECT()
+SELECT JSON_OBJECT();
+ ?column?
+----------
+ {}
+(1 row)
+
+SELECT JSON_OBJECT(RETURNING json);
+ ?column?
+----------
+ {}
+(1 row)
+
+SELECT JSON_OBJECT(RETURNING json FORMAT JSON);
+ ?column?
+----------
+ {}
+(1 row)
+
+SELECT JSON_OBJECT(RETURNING jsonb);
+ ?column?
+----------
+ {}
+(1 row)
+
+SELECT JSON_OBJECT(RETURNING jsonb FORMAT JSON);
+ ?column?
+----------
+ {}
+(1 row)
+
+SELECT JSON_OBJECT(RETURNING text);
+ ?column?
+----------
+ {}
+(1 row)
+
+SELECT JSON_OBJECT(RETURNING text FORMAT JSON);
+ ?column?
+----------
+ {}
+(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);
+ ?column?
+----------
+ \x7b7d
+(1 row)
+
+SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON);
+ ?column?
+----------
+ \x7b7d
+(1 row)
+
+SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF8);
+ ?column?
+----------
+ \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
+ ?column?
+----------------
+ {"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
+ ?column?
+----------------
+ {"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);
+ ?column?
+-----------
+ {"a" : 5}
+(1 row)
+
+SELECT JSON_OBJECT('a' VALUE 2 + 3);
+ ?column?
+-----------
+ {"a" : 5}
+(1 row)
+
+--SELECT JSON_OBJECT(KEY 'a' VALUE 2 + 3);
+SELECT JSON_OBJECT('a' || 2: 1);
+ ?column?
+------------
+ {"a2" : 1}
+(1 row)
+
+SELECT JSON_OBJECT(('a' || 2) VALUE 1);
+ ?column?
+------------
+ {"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);
+ ?column?
+-------------
+ {"a" : "2"}
+(1 row)
+
+SELECT JSON_OBJECT('a' VALUE 2::text);
+ ?column?
+-------------
+ {"a" : "2"}
+(1 row)
+
+--SELECT JSON_OBJECT(KEY 'a' VALUE 2::text);
+SELECT JSON_OBJECT(1::text: 2);
+ ?column?
+-----------
+ {"1" : 2}
+(1 row)
+
+SELECT JSON_OBJECT((1::text) VALUE 2);
+ ?column?
+-----------
+ {"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 }'
+);
+ ?column?
+------------------------------------------------------------------------
+ {"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
+);
+ ?column?
+-------------------------------------------------------------------
+ {"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'));
+ ?column?
+-----------------------------------------------
+ {"a" : "123", "b" : {"a" : 111, "b" : "aaa"}}
+(1 row)
+
+SELECT JSON_OBJECT('a': '123', 'b': JSON_OBJECT('a': 111, 'b': 'aaa' RETURNING jsonb));
+ ?column?
+---------------------------------------------
+ {"a" : "123", "b" : {"a": 111, "b": "aaa"}}
+(1 row)
+
+SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING text));
+ ?column?
+-----------------------
+ {"a" : "{\"b\" : 1}"}
+(1 row)
+
+SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING text) FORMAT JSON);
+ ?column?
+-------------------
+ {"a" : {"b" : 1}}
+(1 row)
+
+SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING bytea));
+ ?column?
+---------------------------------
+ {"a" : "\\x7b226222203a20317d"}
+(1 row)
+
+SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING bytea) FORMAT JSON);
+ ?column?
+-------------------
+ {"a" : {"b" : 1}}
+(1 row)
+
+SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2);
+ ?column?
+----------------------------------
+ {"a" : "1", "b" : null, "c" : 2}
+(1 row)
+
+SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2 NULL ON NULL);
+ ?column?
+----------------------------------
+ {"a" : "1", "b" : null, "c" : 2}
+(1 row)
+
+SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2 ABSENT ON NULL);
+ ?column?
+----------------------
+ {"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);
+ ?column?
+--------------------
+ {"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);
+ ?column?
+----------
+ {"1": 1}
+(1 row)
+
+SELECT JSON_OBJECT(1: 1, '2': NULL, '3': 1, 4: NULL, '5': 'a' ABSENT ON NULL WITH UNIQUE RETURNING jsonb);
+ ?column?
+----------------------------
+ {"1": 1, "3": 1, "5": "a"}
+(1 row)
+
+-- JSON_ARRAY()
+SELECT JSON_ARRAY();
+ ?column?
+----------
+ []
+(1 row)
+
+SELECT JSON_ARRAY(RETURNING json);
+ ?column?
+----------
+ []
+(1 row)
+
+SELECT JSON_ARRAY(RETURNING json FORMAT JSON);
+ ?column?
+----------
+ []
+(1 row)
+
+SELECT JSON_ARRAY(RETURNING jsonb);
+ ?column?
+----------
+ []
+(1 row)
+
+SELECT JSON_ARRAY(RETURNING jsonb FORMAT JSON);
+ ?column?
+----------
+ []
+(1 row)
+
+SELECT JSON_ARRAY(RETURNING text);
+ ?column?
+----------
+ []
+(1 row)
+
+SELECT JSON_ARRAY(RETURNING text FORMAT JSON);
+ ?column?
+----------
+ []
+(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);
+ ?column?
+----------
+ \x5b5d
+(1 row)
+
+SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON);
+ ?column?
+----------
+ \x5b5d
+(1 row)
+
+SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF8);
+ ?column?
+----------
+ \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]');
+ ?column?
+---------------------------------------------------
+ ["aaa", 111, true, [1,2,3], {"a": [1]}, ["a", 3]]
+(1 row)
+
+SELECT JSON_ARRAY('a', NULL, 'b' NULL ON NULL);
+ ?column?
+------------------
+ ["a", null, "b"]
+(1 row)
+
+SELECT JSON_ARRAY('a', NULL, 'b' ABSENT ON NULL);
+ ?column?
+------------
+ ["a", "b"]
+(1 row)
+
+SELECT JSON_ARRAY(NULL, NULL, 'b' ABSENT ON NULL);
+ ?column?
+----------
+ ["b"]
+(1 row)
+
+SELECT JSON_ARRAY('a', NULL, 'b' NULL ON NULL RETURNING jsonb);
+ ?column?
+------------------
+ ["a", null, "b"]
+(1 row)
+
+SELECT JSON_ARRAY('a', NULL, 'b' ABSENT ON NULL RETURNING jsonb);
+ ?column?
+------------
+ ["a", "b"]
+(1 row)
+
+SELECT JSON_ARRAY(NULL, NULL, 'b' ABSENT ON NULL RETURNING jsonb);
+ ?column?
+----------
+ ["b"]
+(1 row)
+
+SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' RETURNING text));
+ ?column?
+-------------------------------
+ ["[\"{ \\\"a\\\" : 123 }\"]"]
+(1 row)
+
+SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING text));
+ ?column?
+-----------------------
+ ["[{ \"a\" : 123 }]"]
+(1 row)
+
+SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING text) FORMAT JSON);
+ ?column?
+-------------------
+ [[{ "a" : 123 }]]
+(1 row)
+
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i));
+ ?column?
+-----------
+ [1, 2, 4]
+(1 row)
+
+SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i));
+ ?column?
+----------
+ [[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);
+ ?column?
+------------------
+ [[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);
+ ?column?
+-----------
+ [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;
+ ?column? | ?column?
+-----------------+-----------------
+ [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;
+ ?column?
+-----------------
+ [5, 4, 3, 2, 1]
+(1 row)
+
+SELECT JSON_ARRAYAGG(i::text::json)
+FROM generate_series(1, 5) i;
+ ?column?
+-----------------
+ [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;
+ ?column?
+------------------------------------------
+ [[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);
+ ?column? | ?column?
+----------+----------
+ [] | []
+(1 row)
+
+SELECT JSON_ARRAYAGG(NULL NULL ON NULL),
+ JSON_ARRAYAGG(NULL NULL ON NULL RETURNING jsonb)
+FROM generate_series(1, 5);
+ ?column? | ?column?
+--------------------------------+--------------------------------
+ [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);
+ ?column? | ?column? | ?column? | ?column? | ?column? | ?column? | ?column? | ?column? | ?column? | ?column?
+-----------------+-----------------+-----------------+-----------------+-----------------------------------------+-----------------------------------------+-----------------+--------------------------------------------------------------------------------------------------------------------------+--------------+--------------------------------------
+ [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 | ?column?
+-----+-----------
+ 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;
+ ?column? | ?column?
+-------------------------------------------------+------------------------------------------
+ { "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);
+ ?column? | ?column? | ?column? | ?column? | ?column? | ?column?
+----------------------------------------------+----------------------------------------------+----------------------+--------------------------------+--------------------------------+------------------
+ { "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);
+ ?column?
+----------------------
+ { "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)
+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)
+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)
+ 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)
+ 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)
+ FROM ( SELECT foo.i
+ FROM ( VALUES (1), (2), (NULL::integer), (4)) foo(i)) q(a))
+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 ccec68e..7065d54 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 f22a682..bf341e7 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -161,6 +161,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..bdb0f41
--- /dev/null
+++ b/src/test/regress/sql/jsonb_sqljson.sql
@@ -0,0 +1,275 @@
+-- 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);
+
+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);
+
+-- 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 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 adsrc 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');
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;