From e97e972472956335d87a798a8b610050b73795a5 Mon Sep 17 00:00:00 2001 From: Amit Langote Date: Tue, 18 Jul 2023 17:58:35 +0900 Subject: [PATCH v9 3/5] SQL/JSON query functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This introduces the SQL/JSON functions for querying JSON data using jsonpath expressions. The functions are: JSON_EXISTS() JSON_QUERY() JSON_VALUE() All of these functions only operate on jsonb. The workaround for now is to cast the argument to jsonb. JSON_EXISTS() tests if the jsonpath expression applied to the jsonb value yields any values. JSON_VALUE() must return a single value, and an error occurs if it tries to return multiple values. JSON_QUERY() must return a json object or array, and there are various WRAPPER options for handling scalar or multi-value results. Both these functions have options for handling EMPTY and ERROR conditions. Author: Nikita Glukhov Author: Teodor Sigaev Author: Oleg Bartunov Author: Alexander Korotkov Author: Andrew Dunstan Author: Amit Langote Reviewers have included (in no particular order) Andres Freund, Alexander Korotkov, Pavel Stehule, Andrew Alsup, Erik Rijkers, Zihong Yu, Himanshu Upadhyaya, Daniel Gustafsson, Justin Pryzby, Álvaro Herrera Discussion: https://postgr.es/m/cd0bb935-0158-78a7-08b5-904886deac4b@postgrespro.ru Discussion: https://postgr.es/m/20220616233130.rparivafipt6doj3@alap3.anarazel.de Discussion: https://postgr.es/m/abd9b83b-aa66-f230-3d6d-734817f0995d%40postgresql.org Discussion: https://postgr.es/m/CA+HiwqE4XTdfb1nW=Ojoy_tQSRhYt-q_kb6i5d4xcKyrLC1Nbg@mail.gmail.com --- doc/src/sgml/func.sgml | 147 +++ src/backend/executor/execExpr.c | 432 ++++++++ src/backend/executor/execExprInterp.c | 502 ++++++++- src/backend/jit/llvm/llvmjit_expr.c | 250 +++++ src/backend/jit/llvm/llvmjit_types.c | 5 + src/backend/nodes/makefuncs.c | 15 + src/backend/nodes/nodeFuncs.c | 183 ++++ src/backend/optimizer/path/costsize.c | 3 +- src/backend/optimizer/util/clauses.c | 19 + src/backend/parser/gram.y | 348 ++++++- src/backend/parser/parse_collate.c | 7 + src/backend/parser/parse_expr.c | 519 ++++++++- src/backend/parser/parse_target.c | 15 + src/backend/utils/adt/formatting.c | 44 + src/backend/utils/adt/jsonb.c | 62 ++ src/backend/utils/adt/jsonfuncs.c | 170 ++- src/backend/utils/adt/jsonpath.c | 255 +++++ src/backend/utils/adt/jsonpath_exec.c | 391 ++++++- src/backend/utils/adt/ruleutils.c | 136 +++ src/include/executor/execExpr.h | 172 +++ src/include/executor/executor.h | 1 + src/include/nodes/execnodes.h | 3 + src/include/nodes/makefuncs.h | 1 + src/include/nodes/parsenodes.h | 48 + src/include/nodes/primnodes.h | 109 ++ src/include/parser/kwlist.h | 11 + src/include/utils/formatting.h | 2 +- src/include/utils/jsonb.h | 3 + src/include/utils/jsonfuncs.h | 5 + src/include/utils/jsonpath.h | 27 + src/interfaces/ecpg/preproc/ecpg.trailer | 28 + src/test/regress/expected/json_sqljson.out | 18 + src/test/regress/expected/jsonb_sqljson.out | 1042 +++++++++++++++++++ src/test/regress/parallel_schedule | 2 +- src/test/regress/sql/json_sqljson.sql | 11 + src/test/regress/sql/jsonb_sqljson.sql | 327 ++++++ src/tools/pgindent/typedefs.list | 14 + 37 files changed, 5234 insertions(+), 93 deletions(-) create mode 100644 src/test/regress/expected/json_sqljson.out create mode 100644 src/test/regress/expected/jsonb_sqljson.out create mode 100644 src/test/regress/sql/json_sqljson.sql create mode 100644 src/test/regress/sql/jsonb_sqljson.sql diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index b8102eee22..ce001c4a03 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -16989,6 +16989,153 @@ array w/o UK? | t + + + details the SQL/JSON + functions that can be used to query JSON data. + + + + + SQL/JSON paths can only be applied to the jsonb type, so it + might be necessary to cast the context_item + argument of these functions to jsonb. + + + + + SQL/JSON Query Functions + + + + + Function signature + + + Description + + + Example(s) + + + + + + + json_exists + json_exists ( + context_item, path_expression PASSING { value AS varname } , ... + RETURNING data_type + { TRUE | FALSE | UNKNOWN | ERROR } ON ERROR ) + + + Returns true if the SQL/JSON path_expression + applied to the context_item using the + values yields any items. + The ON ERROR clause specifies what is returned if + an error occurs. Note that if the path_expression + is strict, an error is generated if it yields no items. + The default value is UNKNOWN which causes a NULL + result. + + + json_exists(jsonb '{"key1": [1,2,3]}', 'strict $.key1[*] ? (@ > 2)') + t + + + json_exists(jsonb '{"a": [1,2,3]}', 'lax $.a[5]' ERROR ON ERROR) + f + + + json_exists(jsonb '{"a": [1,2,3]}', 'strict $.a[5]' ERROR ON ERROR) + ERROR: jsonpath array subscript is out of bounds + + + + + json_value + json_value ( + context_item, path_expression + PASSING { value AS varname } , ... + RETURNING data_type + { ERROR | NULL | DEFAULT expression } ON EMPTY + { ERROR | NULL | DEFAULT expression } ON ERROR ) + + + Returns the result of applying the + path_expression to the + context_item using the + values. The extracted value must be + a single SQL/JSON scalar item. For results that + are objects or arrays, use the json_query + function instead. + The returned data_type has the same semantics + as for constructor functions like json_objectagg. + The default returned type is text. + The ON EMPTY clause specifies the behavior if the + path_expression yields no value at all. + The ON ERROR clause specifies the behavior if an + error occurs as a result of jsonpath evaluation + (including cast to the output type) or execution of + ON EMPTY behavior (that was caused by empty result + of jsonpath evaluation). + + + json_value(jsonb '"123.45"', '$' RETURNING float) + 123.45 + + + json_value(jsonb '"03:04 2015-02-01"', '$.datetime("HH24:MI YYYY-MM-DD")' RETURNING date) + 2015-02-01 + + + json_value(jsonb '[1,2]', 'strict $[*]' DEFAULT 9 ON ERROR) + 9 + + + + + json_query + json_query ( + context_item, path_expression PASSING { value AS varname } , ... + RETURNING data_type FORMAT JSON ENCODING UTF8 + { WITHOUT | WITH { CONDITIONAL | UNCONDITIONAL } } ARRAY WRAPPER + { KEEP | OMIT } QUOTES ON SCALAR STRING + { ERROR | NULL | EMPTY { ARRAY | OBJECT } | DEFAULT expression } ON EMPTY + { ERROR | NULL | EMPTY { ARRAY | OBJECT } | DEFAULT expression } ON ERROR ) + + + Returns the result of applying the + path_expression to the + context_item using the + values. + This function must return a JSON string, so if the path expression + returns multiple SQL/JSON items, you must wrap the result using the + WITH WRAPPER clause. If the wrapper is + UNCONDITIONAL, an array wrapper will always + be applied, even if the returned value is already a single JSON object + or array, but if it is CONDITIONAL, it will not be + applied to a single array or object. UNCONDITIONAL + is the default. + If the result is a scalar string, by default the value returned will have + surrounding quotes making it a valid JSON value. However, this behavior + is reversed if OMIT QUOTES is specified. + The ON ERROR and ON EMPTY + clauses have similar semantics to those clauses for + json_value. + The returned data_type has the same semantics + as for constructor functions like json_objectagg. + The default returned type is text. + + + json_query(jsonb '[1,[2,3],null]', 'lax $[*][1]' WITH CONDITIONAL WRAPPER) + [3] + + + + +
+ diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index 6ca4098bef..d3d2ce00d1 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -49,6 +49,7 @@ #include "utils/builtins.h" #include "utils/datum.h" #include "utils/jsonfuncs.h" +#include "utils/jsonpath.h" #include "utils/lsyscache.h" #include "utils/typcache.h" @@ -88,6 +89,16 @@ static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate, FunctionCallInfo fcinfo, AggStatePerTrans pertrans, int transno, int setno, int setoff, bool ishash, bool nullcheck); +static void ExecInitJsonExpr(JsonExpr *jexpr, ExprState *state, + Datum *resv, bool *resnull, + ExprEvalStep *scratch); +static Datum GetJsonBehaviorValue(JsonBehavior *behavior, bool *is_null); +static JsonCoercionState *ExecInitJsonCoercion(ExprEvalStep *scratch, ExprState *state, + JsonCoercion *coercion, + Datum *resv, bool *resnull); +static JsonItemCoercionsState *ExecInitJsonItemCoercions(ExprEvalStep *scratch, ExprState *state, + JsonItemCoercions *coercions, + Datum *resv, bool *resnull); /* @@ -2411,6 +2422,14 @@ ExecInitExprRec(Expr *node, ExprState *state, break; } + case T_JsonExpr: + { + JsonExpr *jexpr = castNode(JsonExpr, node); + + ExecInitJsonExpr(jexpr, state, resv, resnull, &scratch); + break; + } + case T_NullTest: { NullTest *ntest = (NullTest *) node; @@ -4178,3 +4197,416 @@ ExecBuildParamSetEqual(TupleDesc desc, return state; } + +/* + * Push steps to evaluate a JsonExpr and its various subsidiary expressions. + */ +static void +ExecInitJsonExpr(JsonExpr *jexpr, ExprState *state, + Datum *resv, bool *resnull, + ExprEvalStep *scratch) +{ + JsonExprState *jsestate = palloc0(sizeof(JsonExprState)); + JsonExprPreEvalState *pre_eval = &jsestate->pre_eval; + ListCell *argexprlc; + ListCell *argnamelc; + int skip_step_off = -1; + int passing_args_step_off = -1; + int coercion_step_off = -1; + int coercion_finish_step_off = -1; + int behavior_step_off = -1; + int onempty_expr_step_off = -1; + int onempty_jump_step_off = -1; + int onerror_expr_step_off = -1; + int onerror_jump_step_off = -1; + List *adjust_jumps = NIL; + ListCell *lc; + ExprEvalStep *as; + + jsestate->jsexpr = jexpr; + + /* + * Add steps to compute formatted_expr, pathspec, and PASSING arg + * expressions as things that must be evaluated *before* the actual JSON + * path expression. + */ + ExecInitExprRec((Expr *) jexpr->formatted_expr, state, + &pre_eval->formatted_expr.value, + &pre_eval->formatted_expr.isnull); + ExecInitExprRec((Expr *) jexpr->path_spec, state, + &pre_eval->pathspec.value, + &pre_eval->pathspec.isnull); + + /* + * Before pushing steps for PASSING args, push a step to decide whether to + * skip evaluating the args and the JSON path expression depending on + * whether either of formatted_expr and pathspec is NULL; see + * ExecEvalJsonExprSkip(). + */ + scratch->opcode = EEOP_JSONEXPR_SKIP; + scratch->d.jsonexpr_skip.jsestate = jsestate; + skip_step_off = state->steps_len; + ExprEvalPushStep(state, scratch); + + /* PASSING args. */ + jsestate->pre_eval.args = NIL; + passing_args_step_off = state->steps_len; + forboth(argexprlc, jexpr->passing_values, + argnamelc, jexpr->passing_names) + { + Expr *argexpr = (Expr *) lfirst(argexprlc); + String *argname = lfirst_node(String, argnamelc); + JsonPathVariable *var = palloc(sizeof(*var)); + + var->name = pstrdup(argname->sval); + var->typid = exprType((Node *) argexpr); + var->typmod = exprTypmod((Node *) argexpr); + + ExecInitExprRec((Expr *) argexpr, state, &var->value, &var->isnull); + + pre_eval->args = lappend(pre_eval->args, var); + } + + /* + * Step for the actual JSON path evaluation; see ExecEvalJson() and + * ExecEvalJsonExpr(). + */ + scratch->opcode = EEOP_JSONEXPR_PATH; + scratch->d.jsonexpr.jsestate = jsestate; + ExprEvalPushStep(state, scratch); + + /* + * Push steps to control the evaluation of expressions based on the result + * of JSON path evaluation. + */ + + /* + * Step to handle ON ERROR and ON EMPTY behavior; see + * ExecEvalJsonExprBehavior(). + */ + scratch->opcode = EEOP_JSONEXPR_BEHAVIOR; + scratch->d.jsonexpr_behavior.jsestate = jsestate; + behavior_step_off = state->steps_len; + ExprEvalPushStep(state, scratch); + + /* Step(s) to evaluate ON EMPTY expression */ + onempty_expr_step_off = state->steps_len; + if (jexpr->on_empty && + jexpr->on_empty->btype != JSON_BEHAVIOR_ERROR) + { + if (jexpr->on_empty->default_expr) + { + ExecInitExprRec((Expr *) jexpr->on_empty->default_expr, + state, resv, resnull); + + /* + * Emit JUMP step to jump to end of JsonExpr code, because the + * default expression has already been coerced, so there's nothing + * more to do. + */ + scratch->opcode = EEOP_JUMP; + scratch->d.jump.jumpdone = -1; /* set below */ + ExprEvalPushStep(state, scratch); + adjust_jumps = lappend_int(adjust_jumps, state->steps_len - 1); + } + else + { + Datum constvalue; + bool constisnull; + + constvalue = GetJsonBehaviorValue(jexpr->on_empty, &constisnull); + scratch->opcode = EEOP_CONST; + scratch->d.constval.value = constvalue; + scratch->d.constval.isnull = constisnull; + + ExprEvalPushStep(state, scratch); + + /* + * Emit JUMP step to jump to the coercion step to coerce the above + * value to the desired output type. + */ + onempty_jump_step_off = state->steps_len; + scratch->opcode = EEOP_JUMP; + scratch->d.jump.jumpdone = -1; /* set below */ + ExprEvalPushStep(state, scratch); + } + } + + /* Step(s) to evaluate ON ERROR expression */ + onerror_expr_step_off = state->steps_len; + if (jexpr->on_error && + jexpr->on_error->btype != JSON_BEHAVIOR_ERROR) + { + if (jexpr->on_error->default_expr) + { + ExecInitExprRec((Expr *) jexpr->on_error->default_expr, + state, resv, resnull); + + /* + * Emit JUMP step to jump to end of JsonExpr code, because the + * default expression has already been coerced, so there's nothing + * more to do. + */ + scratch->opcode = EEOP_JUMP; + scratch->d.jump.jumpdone = -1; /* set below */ + ExprEvalPushStep(state, scratch); + adjust_jumps = lappend_int(adjust_jumps, state->steps_len - 1); + } + else + { + Datum constvalue; + bool constisnull; + + constvalue = GetJsonBehaviorValue(jexpr->on_error, &constisnull); + scratch->opcode = EEOP_CONST; + scratch->d.constval.value = constvalue; + scratch->d.constval.isnull = constisnull; + + ExprEvalPushStep(state, scratch); + + /* + * Emit JUMP step to jump to the coercion step to coerce the above + * value to the desired output type. + */ + onerror_jump_step_off = state->steps_len; + scratch->opcode = EEOP_JUMP; + scratch->d.jump.jumpdone = -1; /* set later */ + ExprEvalPushStep(state, scratch); + } + } + + /* + * Step to handle applying coercion to the JSON item returned by + * EEOP_JSONEXPR_PATH or to the ON EMPTY/ERROR expression as + * EEOP_JSONEXPR_BEHAVIOR decides; see ExecEvalJsonExprCoercion(). + */ + scratch->opcode = EEOP_JSONEXPR_COERCION; + scratch->d.jsonexpr_coercion.jsestate = jsestate; + coercion_step_off = state->steps_len; + ExprEvalPushStep(state, scratch); + + /* Initialize coercion expression(s). */ + if (jexpr->result_coercion) + { + jsestate->result_jcstate = + ExecInitJsonCoercion(scratch, state, jexpr->result_coercion, + resv, resnull); + /* See the comment above. */ + scratch->opcode = EEOP_JUMP; + scratch->d.jump.jumpdone = -1; /* computed later */ + ExprEvalPushStep(state, scratch); + adjust_jumps = lappend_int(adjust_jumps, state->steps_len - 1); + } + if (jexpr->coercions) + { + /* + * ExecPrepareJsonItemCoercion() called by ExecEvalJsonExpr() chooses + * one for a given JSON item returned by JsonPathValue(). + */ + jsestate->item_jcstates = + ExecInitJsonItemCoercions(scratch, state, jexpr->coercions, + resv, resnull); + } + + /* + * And a step to clean up after the coercion step; see + * ExecEvalJsonExprCoercionFinish(). + */ + scratch->opcode = EEOP_JSONEXPR_COERCION_FINISH; + scratch->d.jsonexpr_coercion.jsestate = jsestate; + coercion_finish_step_off = state->steps_len; + ExprEvalPushStep(state, scratch); + + /* + * Adjust jump target addresses in various post-eval steps now that we + * have all the steps in place. + */ + + /* EEOP_JSONEXPR_SKIP */ + Assert(skip_step_off >= 0); + as = &state->steps[skip_step_off]; + as->d.jsonexpr_skip.jump_coercion = coercion_step_off; + as->d.jsonexpr_skip.jump_passing_args = passing_args_step_off; + + /* EEOP_JSONEXPR_BEHAVIOR */ + Assert(behavior_step_off >= 0); + as = &state->steps[behavior_step_off]; + as->d.jsonexpr_behavior.jump_onerror_expr = onerror_expr_step_off; + as->d.jsonexpr_behavior.jump_onempty_expr = onempty_expr_step_off; + as->d.jsonexpr_behavior.jump_coercion = coercion_step_off; + as->d.jsonexpr_behavior.jump_skip_coercion = coercion_finish_step_off + 1; + + /* EEOP_JSONEXPR_COERCION */ + Assert(coercion_step_off >= 0); + as = &state->steps[coercion_step_off]; + as->d.jsonexpr_coercion.jump_coercion_error = behavior_step_off; + as->d.jsonexpr_coercion.jump_coercion_done = coercion_finish_step_off + 1; + + /* EEOP_JSONEXPR_COERCION_FINISH */ + Assert(coercion_finish_step_off >= 0); + as = &state->steps[coercion_finish_step_off]; + as->d.jsonexpr_coercion_finish.jump_coercion_error = behavior_step_off; + as->d.jsonexpr_coercion_finish.jump_coercion_done = coercion_finish_step_off + 1; + + /* EEOP_JUMP steps */ + + /* + * Ones after ON EMPTY and ON ERROR non-default expressions should jump to + * the coercion step. + */ + if (onempty_jump_step_off >= 0) + { + as = &state->steps[onempty_jump_step_off]; + as->d.jump.jumpdone = coercion_step_off; + } + if (onerror_jump_step_off >= 0) + { + as = &state->steps[onerror_jump_step_off]; + as->d.jump.jumpdone = coercion_step_off; + } + + /* The rest should jump to the end. */ + foreach(lc, adjust_jumps) + { + as = &state->steps[lfirst_int(lc)]; + as->d.jump.jumpdone = state->steps_len; + } + + /* + * Set RETURNING type's input function used by ExecEvalJsonExprCoercion(). + */ + if (jexpr->omit_quotes || + (jexpr->result_coercion && jexpr->result_coercion->via_io)) + { + Oid typinput; + FmgrInfo *finfo; + + /* lookup the result type's input function */ + getTypeInputInfo(jexpr->returning->typid, &typinput, + &jsestate->input.typioparam); + finfo = palloc0(sizeof(FmgrInfo)); + fmgr_info(typinput, finfo); + jsestate->input.finfo = finfo; + } +} + +/* + * Evaluate a JSON error/empty behavior result. + */ +static Datum +GetJsonBehaviorValue(JsonBehavior *behavior, 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: + case JSON_BEHAVIOR_EMPTY: + *is_null = true; + return (Datum) 0; + + case JSON_BEHAVIOR_DEFAULT: + /* Always handled in the caller. */ + Assert(false); + return (Datum) 0; + + default: + elog(ERROR, "unrecognized SQL/JSON behavior %d", behavior->btype); + return (Datum) 0; + } +} + +/* Initialize one JsonCoercion for execution. */ +static JsonCoercionState * +ExecInitJsonCoercion(ExprEvalStep *scratch, ExprState *state, + JsonCoercion *coercion, + Datum *resv, bool *resnull) +{ + JsonCoercionState *jcstate = palloc0(sizeof(JsonCoercionState)); + + jcstate->coercion = coercion; + if (coercion && coercion->expr) + { + Datum *save_innermost_caseval; + bool *save_innermost_casenull; + + jcstate->jump_eval_expr = state->steps_len; + + /* Push step(s) to compute cstate->coercion. */ + save_innermost_caseval = state->innermost_caseval; + save_innermost_casenull = state->innermost_casenull; + + state->innermost_caseval = resv; + state->innermost_casenull = resnull; + + ExecInitExprRec((Expr *) coercion->expr, state, resv, resnull); + + state->innermost_caseval = save_innermost_caseval; + state->innermost_casenull = save_innermost_casenull; + } + else + jcstate->jump_eval_expr = -1; + + return jcstate; +} + +/* + * Push steps to evaluate coercions from a given JsonItemCoercions, which + * contains all possible coercions that may need to be applied to JSON + * items coming from evaluating the parent JsonExpr. + */ +static JsonItemCoercionsState * +ExecInitJsonItemCoercions(ExprEvalStep *scratch, ExprState *state, + JsonItemCoercions *coercions, + Datum *resv, bool *resnull) +{ + JsonCoercion **coercion; + JsonItemCoercionsState *item_jcstates = palloc0(sizeof(JsonItemCoercionsState)); + JsonCoercionState **item_jcstate; + ExprEvalStep *as; + List *adjust_jumps = NIL; + ListCell *lc; + + /* Push the steps of individual coercions. */ + for (coercion = &coercions->null, + item_jcstate = &item_jcstates->null; + coercion <= &coercions->composite; + coercion++, item_jcstate++) + { + *item_jcstate = ExecInitJsonCoercion(scratch, state, *coercion, + resv, resnull); + + /* Emit JUMP step to skip past other coercions' steps. */ + scratch->opcode = EEOP_JUMP; + + /* + * Remember JUMP step address to set the actual jump target address + * below. + */ + adjust_jumps = lappend_int(adjust_jumps, state->steps_len); + ExprEvalPushStep(state, scratch); + } + + foreach(lc, adjust_jumps) + { + int jump_step_id = lfirst_int(lc); + + as = &state->steps[jump_step_id]; + as->d.jump.jumpdone = state->steps_len; + } + + return item_jcstates; +} diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 76e59691e5..4c97e714ea 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -74,6 +74,7 @@ #include "utils/json.h" #include "utils/jsonb.h" #include "utils/jsonfuncs.h" +#include "utils/jsonpath.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/timestamp.h" @@ -152,6 +153,9 @@ static TupleDesc get_cached_rowtype(Oid type_id, int32 typmod, bool *changed); static void ExecEvalRowNullInt(ExprState *state, ExprEvalStep *op, ExprContext *econtext, bool checkisnull); +static Datum ExecPrepareJsonItemCoercion(JsonbValue *item, + JsonItemCoercionsState *item_jcstates, + JsonCoercionState **p_item_jcstate); /* fast-path evaluation functions */ static Datum ExecJustInnerVar(ExprState *state, ExprContext *econtext, bool *isnull); @@ -480,6 +484,11 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) &&CASE_EEOP_XMLEXPR, &&CASE_EEOP_JSON_CONSTRUCTOR, &&CASE_EEOP_IS_JSON, + &&CASE_EEOP_JSONEXPR_SKIP, + &&CASE_EEOP_JSONEXPR_PATH, + &&CASE_EEOP_JSONEXPR_BEHAVIOR, + &&CASE_EEOP_JSONEXPR_COERCION, + &&CASE_EEOP_JSONEXPR_COERCION_FINISH, &&CASE_EEOP_AGGREF, &&CASE_EEOP_GROUPING_FUNC, &&CASE_EEOP_WINDOW_FUNC, @@ -1186,8 +1195,14 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) /* second and third arguments are already set up */ fcinfo_in->isnull = false; + + /* Pass down soft-error capture node if any. */ + fcinfo_in->context = state->escontext; + d = FunctionCallInvoke(fcinfo_in); *op->resvalue = d; + if (SOFT_ERROR_OCCURRED(state->escontext)) + *op->resnull = true; /* Should get null result if and only if str is NULL */ if (str == NULL) @@ -1195,7 +1210,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) Assert(*op->resnull); Assert(fcinfo_in->isnull); } - else + else if (!SOFT_ERROR_OCCURRED(state->escontext)) { Assert(!*op->resnull); Assert(!fcinfo_in->isnull); @@ -1543,6 +1558,38 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } + EEO_CASE(EEOP_JSONEXPR_PATH) + { + /* too complex for an inline implementation */ + ExecEvalJsonExpr(state, op, econtext); + EEO_NEXT(); + } + + EEO_CASE(EEOP_JSONEXPR_SKIP) + { + /* too complex for an inline implementation */ + EEO_JUMP(ExecEvalJsonExprSkip(state, op)); + } + + EEO_CASE(EEOP_JSONEXPR_BEHAVIOR) + { + /* too complex for an inline implementation */ + EEO_JUMP(ExecEvalJsonExprBehavior(state, op)); + } + + EEO_CASE(EEOP_JSONEXPR_COERCION) + { + /* too complex for an inline implementation */ + EEO_JUMP(ExecEvalJsonExprCoercion(state, op, econtext, + *op->resvalue, *op->resnull)); + } + + EEO_CASE(EEOP_JSONEXPR_COERCION_FINISH) + { + /* too complex for an inline implementation */ + EEO_JUMP(ExecEvalJsonExprCoercionFinish(state, op)); + } + EEO_CASE(EEOP_AGGREF) { /* @@ -3745,7 +3792,7 @@ ExecEvalConstraintCheck(ExprState *state, ExprEvalStep *op) { if (!*op->d.domaincheck.checknull && !DatumGetBool(*op->d.domaincheck.checkvalue)) - ereport(ERROR, + errsave(state->escontext, (errcode(ERRCODE_CHECK_VIOLATION), errmsg("value for domain %s violates check constraint \"%s\"", format_type_be(op->d.domaincheck.resulttype), @@ -4138,6 +4185,457 @@ ExecEvalJsonIsPredicate(ExprState *state, ExprEvalStep *op) *op->resvalue = BoolGetDatum(res); } +/* + * Evaluate given JsonExpr by performing the specified JSON operation. + * + * This also populates the JsonExprPostEvalState with the information needed + * by the subsequent steps that handle the specified JsonBehavior. + */ +void +ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext) +{ + JsonExprState *jsestate = op->d.jsonexpr.jsestate; + JsonExprPreEvalState *pre_eval = &jsestate->pre_eval; + JsonExprPostEvalState *post_eval = &jsestate->post_eval; + JsonExpr *jexpr = jsestate->jsexpr; + Datum item; + Datum res = (Datum) 0; + bool resnull = true; + JsonPath *path; + bool *error; + bool *empty; + + item = pre_eval->formatted_expr.value; + path = DatumGetJsonPathP(pre_eval->pathspec.value); + + /* Reset JsonExprPostEvalState for this evaluation. */ + memset(post_eval, 0, sizeof(*post_eval)); + + /* Respect ON ERROR behavior during path evaluation. */ + jsestate->throw_error = (jexpr->on_error->btype == JSON_BEHAVIOR_ERROR); + + /* + * Be sure to save the error (if needed) and empty statuses for the + * EEOP_JSONEXPR_BEHAVIOR step to peruse. + */ + error = !jsestate->throw_error ? &post_eval->error : NULL; + empty = &post_eval->empty; + + switch (jexpr->op) + { + case JSON_QUERY_OP: + res = JsonPathQuery(item, path, jexpr->wrapper, empty, error, + pre_eval->args); + if (error && *error) + { + *op->resnull = true; + *op->resvalue = (Datum) 0; + return; + } + resnull = !DatumGetPointer(res); + break; + + case JSON_VALUE_OP: + { + JsonbValue *jbv = JsonPathValue(item, path, empty, error, + pre_eval->args); + + if (error && *error) + { + *op->resnull = true; + *op->resvalue = (Datum) 0; + return; + } + + if (!jbv) /* NULL or empty */ + { + resnull = true; + break; + } + + Assert(!*empty); + + resnull = false; + + /* coerce scalar item to the output type */ + if (jexpr->returning->typid == JSONOID || + jexpr->returning->typid == JSONBOID) + { + /* Use result coercion from json[b] to the output type */ + res = JsonbPGetDatum(JsonbValueToJsonb(jbv)); + break; + } + + /* + * Error out if no cast exists to coerce SQL/JSON item to the + * the output type. + */ + Assert(post_eval->item_jcstate == NULL); + res = ExecPrepareJsonItemCoercion(jbv, + jsestate->item_jcstates, + &post_eval->item_jcstate); + if (post_eval->item_jcstate && + post_eval->item_jcstate->coercion && + (post_eval->item_jcstate->coercion->via_io || + post_eval->item_jcstate->coercion->via_populate)) + { + if (error) + { + *error = true; + *op->resnull = true; + *op->resvalue = (Datum) 0; + return; + } + + /* + * Coercion via I/O means here that the cast to the target + * type simply does not exist. + */ + ereport(ERROR, + (errcode(ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE), + errmsg("SQL/JSON item cannot be cast to target type"))); + } + break; + } + + case JSON_EXISTS_OP: + { + bool exists = JsonPathExists(item, path, + pre_eval->args, + error); + + resnull = error && *error; + res = BoolGetDatum(exists); + break; + } + + default: + elog(ERROR, "unrecognized SQL/JSON expression op %d", jexpr->op); + *op->resnull = true; + *op->resvalue = (Datum) 0; + return; + } + + /* + * If the ON EMPTY behavior is to cause an error, do so here. Other + * behaviors will be handled by the caller. + */ + if (*empty) + { + Assert(jexpr->on_empty); /* it is not JSON_EXISTS */ + + if (jexpr->on_empty->btype == JSON_BEHAVIOR_ERROR) + { + if (error) + { + *error = true; + *op->resnull = true; + *op->resvalue = (Datum) 0; + return; + } + + ereport(ERROR, + (errcode(ERRCODE_NO_SQL_JSON_ITEM), + errmsg("no SQL/JSON item"))); + } + } + + *op->resvalue = res; + *op->resnull = resnull; +} + +/* + * Skip calling ExecEvalJson() on the given JsonExpr? + * + * Returns the step address to be performed next. + */ +int +ExecEvalJsonExprSkip(ExprState *state, ExprEvalStep *op) +{ + JsonExprState *jsestate = op->d.jsonexpr_skip.jsestate; + + /* + * Skip if either of the input expressions has turned out to be NULL, + * though do execute domain checks for NULLs, which are handled by the + * coercion step. + */ + if (jsestate->pre_eval.formatted_expr.isnull || + jsestate->pre_eval.pathspec.isnull) + { + *op->resvalue = (Datum) 0; + *op->resnull = true; + return op->d.jsonexpr_skip.jump_coercion; + } + + /* + * Go evaluate the PASSING args if any and subsequently JSON path itself. + */ + return op->d.jsonexpr_skip.jump_passing_args; +} + +/* + * Returns the step address to perform the JsonBehavior applicable to + * the JSON item that resulted from evaluating the given JsonExpr. + * + * Returns the step address to be performed next. + */ +int +ExecEvalJsonExprBehavior(ExprState *state, ExprEvalStep *op) +{ + JsonExprState *jsestate = op->d.jsonexpr_behavior.jsestate; + JsonExprPostEvalState *post_eval = &jsestate->post_eval; + JsonBehavior *behavior = NULL; + int jump_to = -1; + + if (post_eval->error || post_eval->coercion_error) + { + behavior = jsestate->jsexpr->on_error; + jump_to = op->d.jsonexpr_behavior.jump_onerror_expr; + } + else if (post_eval->empty) + { + behavior = jsestate->jsexpr->on_empty; + jump_to = op->d.jsonexpr_behavior.jump_onempty_expr; + } + else if (!post_eval->coercion_done) + { + /* + * If no error or the JSON item is not empty, directly go to the + * coercion step to coerce the item as is. + */ + return op->d.jsonexpr_behavior.jump_coercion; + } + + Assert(behavior); + + /* + * Set up for coercion step that will run to coerce a non-default behavior + * value. It should use result_coercion, if any. Errors that may occur + * should be thrown for JSON ops other than JSON_VALUE_OP. + */ + if (behavior->btype != JSON_BEHAVIOR_DEFAULT) + { + post_eval->item_jcstate = NULL; + jsestate->throw_error = (jsestate->jsexpr->op != JSON_VALUE_OP); + } + + Assert(jump_to >= 0); + return jump_to; +} + +/* + * Evaluate or return the step address to evaluate a coercion of a JSON item + * to the target type. The former if the coercion must be done right away by + * calling its input function or by calling json_populate_type(). + * + * Returns the step address to be performed next. + */ +int +ExecEvalJsonExprCoercion(ExprState *state, ExprEvalStep *op, + ExprContext *econtext, + Datum res, bool resnull) +{ + JsonExprState *jsestate = op->d.jsonexpr_coercion.jsestate; + JsonExpr *jexpr = jsestate->jsexpr; + JsonExprPostEvalState *post_eval = &jsestate->post_eval; + JsonCoercionState *item_jcstate = post_eval->item_jcstate; + JsonCoercionState *result_jcstate = jsestate->result_jcstate; + + if (item_jcstate == NULL && + jsestate->jsexpr->op != JSON_EXISTS_OP) + { + ErrorSaveContext escontext = {T_ErrorSaveContext}; + Node *escontext_p; + JsonCoercion *coercion; + Jsonb *jb; + + escontext_p = !jsestate->throw_error ? (Node *) &escontext : NULL; + coercion = result_jcstate ? result_jcstate->coercion : NULL; + jb = resnull ? NULL : DatumGetJsonbP(res); + + if ((coercion && coercion->via_io) || + (jexpr->omit_quotes && !resnull && + JB_ROOT_IS_SCALAR(jb))) + { + /* strip quotes and call typinput function */ + char *str = resnull ? NULL : JsonbUnquote(jb); + bool type_is_domain; + + type_is_domain = (getBaseType(jexpr->returning->typid) != + jexpr->returning->typid); + + /* + * Catch errors only if the type is not a domain, because errors + * caused by a domain's constraint failure must be thrown right + * away. + */ + if (!InputFunctionCallSafe(jsestate->input.finfo, str, + jsestate->input.typioparam, + jexpr->returning->typmod, + !type_is_domain ? escontext_p : NULL, + op->resvalue)) + { + post_eval->error = true; + *op->resnull = true; + *op->resvalue = (Datum) 0; + return op->d.jsonexpr_coercion.jump_coercion_error; + } + + post_eval->coercion_done = true; + return op->d.jsonexpr_coercion.jump_coercion_done; + } + else if (coercion && coercion->via_populate) + { + *op->resvalue = json_populate_type(res, JSONBOID, + jexpr->returning->typid, + jexpr->returning->typmod, + &post_eval->cache, + econtext->ecxt_per_query_memory, + op->resnull, + escontext_p); + if (SOFT_ERROR_OCCURRED(escontext_p)) + { + post_eval->error = true; + *op->resvalue = (Datum) 0; + *op->resnull = true; + return op->d.jsonexpr_coercion.jump_coercion_error; + } + + post_eval->coercion_done = true; + return op->d.jsonexpr_coercion.jump_coercion_done; + } + } + + if (!jsestate->throw_error) + { + post_eval->escontext.type = T_ErrorSaveContext; + state->escontext = (Node *) &post_eval->escontext; + } + + if (item_jcstate && item_jcstate->jump_eval_expr >= 0) + return item_jcstate->jump_eval_expr; + else if (result_jcstate && result_jcstate->jump_eval_expr >= 0) + return result_jcstate->jump_eval_expr; + + post_eval->coercion_done = true; + return op->d.jsonexpr_coercion.jump_coercion_done; +} + +/* + * Checks if the coercion evaluation led to an error. If an error did occur, + * this returns the address of the step that handles the error, otherwise + * the step after the coercion step, which finishes the JsonExpr processing. + */ +int +ExecEvalJsonExprCoercionFinish(ExprState *state, ExprEvalStep *op) +{ + JsonExprPostEvalState *post_eval = + &op->d.jsonexpr_coercion_finish.jsestate->post_eval; + + if (SOFT_ERROR_OCCURRED(state->escontext)) + { + *op->resvalue = (Datum) 0; + *op->resnull = true; + post_eval->coercion_error = true; + return op->d.jsonexpr_coercion_finish.jump_coercion_error; + } + + /* Reset. */ + state->escontext = NULL; + post_eval->coercion_done = true; + return op->d.jsonexpr_coercion_finish.jump_coercion_done; +} + +/* + * Prepare SQL/JSON item coercion to the output type. Returned a datum of the + * corresponding SQL type and a pointer to the coercion state. + */ +static Datum +ExecPrepareJsonItemCoercion(JsonbValue *item, + JsonItemCoercionsState *item_jcstates, + JsonCoercionState **p_item_jcstate) +{ + JsonCoercionState *item_jcstate; + Datum res; + JsonbValue buf; + + if (item->type == jbvBinary && + JsonContainerIsScalar(item->val.binary.data)) + { + bool res PG_USED_FOR_ASSERTS_ONLY; + + res = JsonbExtractScalar(item->val.binary.data, &buf); + item = &buf; + Assert(res); + } + + /* get coercion state reference and datum of the corresponding SQL type */ + switch (item->type) + { + case jbvNull: + item_jcstate = item_jcstates->null; + res = (Datum) 0; + break; + + case jbvString: + item_jcstate = item_jcstates->string; + res = PointerGetDatum(cstring_to_text_with_len(item->val.string.val, + item->val.string.len)); + break; + + case jbvNumeric: + item_jcstate = item_jcstates->numeric; + res = NumericGetDatum(item->val.numeric); + break; + + case jbvBool: + item_jcstate = item_jcstates->boolean; + res = BoolGetDatum(item->val.boolean); + break; + + case jbvDatetime: + res = item->val.datetime.value; + switch (item->val.datetime.typid) + { + case DATEOID: + item_jcstate = item_jcstates->date; + break; + case TIMEOID: + item_jcstate = item_jcstates->time; + break; + case TIMETZOID: + item_jcstate = item_jcstates->timetz; + break; + case TIMESTAMPOID: + item_jcstate = item_jcstates->timestamp; + break; + case TIMESTAMPTZOID: + item_jcstate = item_jcstates->timestamptz; + break; + default: + elog(ERROR, "unexpected jsonb datetime type oid %u", + item->val.datetime.typid); + return (Datum) 0; + } + break; + + case jbvArray: + case jbvObject: + case jbvBinary: + item_jcstate = item_jcstates->composite; + res = JsonbPGetDatum(JsonbValueToJsonb(item)); + break; + + default: + elog(ERROR, "unexpected jsonb value type %d", item->type); + return (Datum) 0; + } + + *p_item_jcstate = item_jcstate; + + return res; +} + /* * ExecEvalGroupingFunc diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c index 00d7b8110b..da3870cba6 100644 --- a/src/backend/jit/llvm/llvmjit_expr.c +++ b/src/backend/jit/llvm/llvmjit_expr.c @@ -1860,6 +1860,256 @@ llvm_compile_expr(ExprState *state) LLVMBuildBr(b, opblocks[opno + 1]); break; + case EEOP_JSONEXPR_PATH: + build_EvalXFunc(b, mod, "ExecEvalJsonExpr", + v_state, op, v_econtext); + LLVMBuildBr(b, opblocks[opno + 1]); + break; + + case EEOP_JSONEXPR_SKIP: + { + LLVMValueRef params[2]; + LLVMValueRef v_ret; + + /* + * Call ExecEvalJsonExprSkip() to decide if JSON path + * evaluation can be skipped. This returns the step + * address to jump to. + */ + params[0] = v_state; + params[1] = l_ptr_const(op, l_ptr(StructExprEvalStep)); + v_ret = LLVMBuildCall(b, + llvm_pg_func(mod, "ExecEvalJsonExprSkip"), + params, lengthof(params), ""); + + /* + * Jump to coercion step if the returned address is the + * same as jsonexpr_skip.jump_coercion, which signifies + * skipping of JSON path evaluation, else to the next step + * which must point to the steps to evaluate PASSING args, + * if any, or to the JSON path evaluation. + */ + LLVMBuildCondBr(b, + LLVMBuildICmp(b, + LLVMIntEQ, + v_ret, + l_int32_const(op->d.jsonexpr_skip.jump_coercion), + ""), + opblocks[op->d.jsonexpr_skip.jump_coercion], + opblocks[opno + 1]); + break; + } + + case EEOP_JSONEXPR_BEHAVIOR: + { + LLVMValueRef params[2]; + LLVMValueRef v_ret; + LLVMBasicBlockRef b_jump_onerror_default; + + /* + * Call ExecEvalJsonExprBehavior() to decide if ON EMPTY + * or ON ERROR behavior must be invoked depending on what + * JSON path evaluation returned. This returns the step + * address to jump to. + */ + params[0] = v_state; + params[1] = l_ptr_const(op, l_ptr(StructExprEvalStep)); + v_ret = LLVMBuildCall(b, + llvm_pg_func(mod, "ExecEvalJsonExprBehavior"), + params, lengthof(params), ""); + + b_jump_onerror_default = + l_bb_before_v(opblocks[opno + 1], + "op.%d.jsonexpr_behavior_jump_onerror_default", opno); + + /* + * Jump to coercion step if the returned address is the + * same as jsonexpr_behavior.jump_coercion, else to the + * next block, one that checks whether to evaluate the ON + * ERROR default expression. + */ + LLVMBuildCondBr(b, + LLVMBuildICmp(b, + LLVMIntEQ, + v_ret, + l_int32_const(op->d.jsonexpr_behavior.jump_coercion), + ""), + opblocks[op->d.jsonexpr_behavior.jump_coercion], + b_jump_onerror_default); + + /* + * Block that checks whether to evaluate the ON ERROR + * default expression. + * + * Jump to evaluate the ON ERROR default expression if the + * returned address is the same as + * jsonexpr_behavior.jump_onerror_default, else jump to + * evaluate the ON EMPTY default expression. + */ + LLVMPositionBuilderAtEnd(b, b_jump_onerror_default); + LLVMBuildCondBr(b, + LLVMBuildICmp(b, + LLVMIntEQ, + v_ret, + l_int32_const(op->d.jsonexpr_behavior.jump_onerror_expr), + ""), + opblocks[op->d.jsonexpr_behavior.jump_onerror_expr], + opblocks[op->d.jsonexpr_behavior.jump_onempty_expr]); + break; + } + case EEOP_JSONEXPR_COERCION: + { + JsonExprState *jsestate = op->d.jsonexpr_coercion.jsestate; + JsonItemCoercionsState *item_jcstates = jsestate->item_jcstates; + JsonCoercionState *result_jcstate = jsestate->result_jcstate; + LLVMValueRef v_ret; + LLVMValueRef params[5]; + + /* + * Call ExecEvalJsonExprCoercion() to evaluate appropriate + * coercion. This will return the step address to jump + * to. + */ + params[0] = v_state; + params[1] = l_ptr_const(op, l_ptr(StructExprEvalStep)); + params[2] = v_econtext; + params[3] = v_resvaluep; + params[4] = v_resnullp; + v_ret = LLVMBuildCall(b, + llvm_pg_func(mod, "ExecEvalJsonExprCoercion"), + params, lengthof(params), ""); + + /* + * Jump to handle a coercion error if the returned address + * is the same as jsonexpr_coercion.jump_coercion_error, + * else to the step after coercion (coercion done!). + */ + LLVMBuildCondBr(b, + LLVMBuildICmp(b, + LLVMIntEQ, + v_ret, + l_int32_const(op->d.jsonexpr_coercion.jump_coercion_error), + ""), + opblocks[op->d.jsonexpr_coercion.jump_coercion_error], + opblocks[op->d.jsonexpr_coercion.jump_coercion_done]); + + if (item_jcstates) + { + JsonCoercionState **item_jcstate; + int n_coercions = (int) + (item_jcstates->composite - item_jcstates->null) + 1; + int i; + LLVMBasicBlockRef *b_coercions; + + + /* + * Will create a block for each coercion below to + * check whether to evaluate the coercion's expression + * if there's one or to skip to the end if not. + */ + b_coercions = palloc((n_coercions + 1) * sizeof(LLVMBasicBlockRef)); + for (i = 0; i < n_coercions + 1; i++) + b_coercions[i] = + l_bb_before_v(opblocks[opno + 1], + "op.%d.json_item_coercion.%d", + opno, i); + + /* Jump to check first coercion */ + LLVMBuildBr(b, b_coercions[0]); + + /* + * Add conditional branches for individual coercion's + * expressions + */ + for (item_jcstate = &item_jcstates->null, i = 0; + item_jcstate <= &item_jcstates->composite; + item_jcstate++, i++) + { + /* Block for this coercion */ + LLVMPositionBuilderAtEnd(b, b_coercions[i]); + + /* + * Jump to evaluate the coercion's expression if + * the address returned is the same as this + * coercion's jump_eval_expr (that is, if it is + * valid), else check the next coercion's. + */ + LLVMBuildCondBr(b, + LLVMBuildICmp(b, + LLVMIntEQ, + v_ret, + l_int32_const((*item_jcstate)->jump_eval_expr), + ""), + (*item_jcstate)->jump_eval_expr >= 0 ? + opblocks[(*item_jcstate)->jump_eval_expr] : + opblocks[op->d.jsonexpr_coercion.jump_coercion_done], + b_coercions[i + 1]); + } + + /* + * A placeholder block that the last coercion's block + * might jump to, which unconditionally jumps to end + * of coercions. + */ + LLVMPositionBuilderAtEnd(b, b_coercions[i]); + LLVMBuildBr(b, opblocks[op->d.jsonexpr_coercion.jump_coercion_done]); + } + + /* + * Jump to evaluate the result_coercion's expression if + * none of the above coercions matched, that is, if + * there's one. + */ + if (result_jcstate) + { + LLVMBuildCondBr(b, + LLVMBuildICmp(b, + LLVMIntEQ, + v_ret, + l_int32_const(result_jcstate->jump_eval_expr), + ""), + result_jcstate->jump_eval_expr >= 0 ? + opblocks[result_jcstate->jump_eval_expr] : + opblocks[op->d.jsonexpr_coercion.jump_coercion_done], + opblocks[op->d.jsonexpr_coercion.jump_coercion_done]); + } + break; + } + + case EEOP_JSONEXPR_COERCION_FINISH: + { + LLVMValueRef params[2]; + LLVMValueRef v_ret; + + /* + * Call ExecEvalJsonExprCoercionFinish() to check whether + * an coercion error occurred, in which case we must jump + * to whatever step handles the error. This returns the + * step address to jump to. + */ + params[0] = v_state; + params[1] = l_ptr_const(op, l_ptr(StructExprEvalStep)); + v_ret = LLVMBuildCall(b, + llvm_pg_func(mod, "ExecEvalJsonExprCoercionFinish"), + params, lengthof(params), ""); + + /* + * Jump to the step that handles coercion error if the + * returned address is the same as + * jsonexpr_coercion_finish.jump_coercion_error, else to + * jsonexpr_coercion_finish.jump_coercion_done. + */ + LLVMBuildCondBr(b, + LLVMBuildICmp(b, + LLVMIntEQ, + v_ret, + l_int32_const(op->d.jsonexpr_coercion_finish.jump_coercion_error), + ""), + opblocks[op->d.jsonexpr_coercion_finish.jump_coercion_error], + opblocks[op->d.jsonexpr_coercion_finish.jump_coercion_done]); + break; + } + case EEOP_AGGREF: { LLVMValueRef v_aggno; diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c index 41ac4c6f45..cf3ced3427 100644 --- a/src/backend/jit/llvm/llvmjit_types.c +++ b/src/backend/jit/llvm/llvmjit_types.c @@ -135,6 +135,11 @@ void *referenced_functions[] = ExecEvalXmlExpr, ExecEvalJsonConstructor, ExecEvalJsonIsPredicate, + ExecEvalJsonExprSkip, + ExecEvalJsonExprBehavior, + ExecEvalJsonExprCoercion, + ExecEvalJsonExprCoercionFinish, + ExecEvalJsonExpr, MakeExpandedObjectReadOnlyInternal, slot_getmissingattrs, slot_getsomeattrs_int, diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c index 0e7e6e46d9..9c02634355 100644 --- a/src/backend/nodes/makefuncs.c +++ b/src/backend/nodes/makefuncs.c @@ -860,6 +860,21 @@ makeJsonValueExpr(Expr *raw_expr, Expr *formatted_expr, 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 diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index c03f4f23e2..cd5fff2bbe 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -234,6 +234,12 @@ exprType(const Node *expr) case T_JsonIsPredicate: type = BOOLOID; break; + case T_JsonExpr: + type = ((const JsonExpr *) expr)->returning->typid; + break; + case T_JsonCoercion: + type = exprType(((const JsonCoercion *) expr)->expr); + break; case T_NullTest: type = BOOLOID; break; @@ -493,6 +499,10 @@ exprTypmod(const Node *expr) return exprTypmod((Node *) ((const JsonValueExpr *) expr)->formatted_expr); case T_JsonConstructorExpr: return ((const JsonConstructorExpr *) expr)->returning->typmod; + case T_JsonExpr: + return ((JsonExpr *) expr)->returning->typmod; + case T_JsonCoercion: + return exprTypmod(((const JsonCoercion *) expr)->expr); case T_CoerceToDomain: return ((const CoerceToDomain *) expr)->resulttypmod; case T_CoerceToDomainValue: @@ -969,6 +979,22 @@ exprCollation(const Node *expr) /* IS JSON's result is boolean ... */ coll = InvalidOid; /* ... so it has no collation */ 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; + case T_NullTest: /* NullTest's result is boolean ... */ coll = InvalidOid; /* ... so it has no collation */ @@ -1205,6 +1231,21 @@ exprSetCollation(Node *expr, Oid collation) case T_JsonIsPredicate: Assert(!OidIsValid(collation)); /* result is always boolean */ 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; case T_NullTest: /* NullTest's result is boolean ... */ Assert(!OidIsValid(collation)); /* ... so never set a collation */ @@ -1508,6 +1549,15 @@ exprLocation(const Node *expr) case T_JsonIsPredicate: loc = ((const JsonIsPredicate *) expr)->location; break; + case T_JsonExpr: + { + const JsonExpr *jsexpr = (const JsonExpr *) expr; + + /* consider both function name and leftmost arg */ + loc = leftmostLoc(jsexpr->location, + exprLocation(jsexpr->formatted_expr)); + } + break; case T_NullTest: { const NullTest *nexpr = (const NullTest *) expr; @@ -2260,6 +2310,54 @@ expression_tree_walker_impl(Node *node, break; case T_JsonIsPredicate: return WALK(((JsonIsPredicate *) node)->expr); + case T_JsonExpr: + { + JsonExpr *jexpr = (JsonExpr *) node; + + if (WALK(jexpr->formatted_expr)) + return true; + if (WALK(jexpr->result_coercion)) + return true; + if (WALK(jexpr->passing_values)) + return true; + /* we assume walker doesn't care about passing_names */ + if (jexpr->on_empty && + WALK(jexpr->on_empty->default_expr)) + return true; + if (WALK(jexpr->on_error->default_expr)) + return true; + if (WALK(jexpr->coercions)) + return true; + } + break; + case T_JsonCoercion: + return WALK(((JsonCoercion *) node)->expr); + case T_JsonItemCoercions: + { + JsonItemCoercions *coercions = (JsonItemCoercions *) node; + + if (WALK(coercions->null)) + return true; + if (WALK(coercions->string)) + return true; + if (WALK(coercions->numeric)) + return true; + if (WALK(coercions->boolean)) + return true; + if (WALK(coercions->date)) + return true; + if (WALK(coercions->time)) + return true; + if (WALK(coercions->timetz)) + return true; + if (WALK(coercions->timestamp)) + return true; + if (WALK(coercions->timestamptz)) + return true; + if (WALK(coercions->composite)) + return true; + } + break; case T_NullTest: return WALK(((NullTest *) node)->arg); case T_BooleanTest: @@ -3259,6 +3357,54 @@ expression_tree_mutator_impl(Node *node, return (Node *) newnode; } + case T_JsonExpr: + { + JsonExpr *jexpr = (JsonExpr *) node; + JsonExpr *newnode; + + FLATCOPY(newnode, jexpr, JsonExpr); + MUTATE(newnode->path_spec, jexpr->path_spec, 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 */ + if (newnode->on_empty) + 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; case T_NullTest: { NullTest *ntest = (NullTest *) node; @@ -3945,6 +4091,43 @@ raw_expression_tree_walker_impl(Node *node, break; case T_JsonIsPredicate: return WALK(((JsonIsPredicate *) node)->expr); + case T_JsonArgument: + return WALK(((JsonArgument *) node)->val); + case T_JsonCommon: + { + JsonCommon *jc = (JsonCommon *) node; + + if (WALK(jc->expr)) + return true; + if (WALK(jc->pathspec)) + return true; + if (WALK(jc->passing)) + return true; + } + break; + case T_JsonBehavior: + { + JsonBehavior *jb = (JsonBehavior *) node; + + if (jb->btype == JSON_BEHAVIOR_DEFAULT && + WALK(jb->default_expr)) + return true; + } + break; + case T_JsonFuncExpr: + { + JsonFuncExpr *jfe = (JsonFuncExpr *) node; + + if (WALK(jfe->common)) + return true; + if (jfe->output && WALK(jfe->output)) + return true; + if (WALK(jfe->on_empty)) + return true; + if (WALK(jfe->on_error)) + return true; + } + break; case T_NullTest: return WALK(((NullTest *) node)->arg); case T_BooleanTest: diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index ef475d95a1..f58c275b4b 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -4609,7 +4609,8 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context) IsA(node, SQLValueFunction) || IsA(node, XmlExpr) || IsA(node, CoerceToDomain) || - IsA(node, NextValueExpr)) + IsA(node, NextValueExpr) || + IsA(node, JsonExpr)) { /* Treat all these as having cost 1 */ context->total.per_tuple += cpu_operator_cost; diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index da258968b8..e40cfab4b7 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -53,6 +53,7 @@ #include "utils/fmgroids.h" #include "utils/json.h" #include "utils/jsonb.h" +#include "utils/jsonpath.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/syscache.h" @@ -412,6 +413,24 @@ contain_mutable_functions_walker(Node *node, void *context) /* Check all subnodes */ } + if (IsA(node, JsonExpr)) + { + JsonExpr *jexpr = castNode(JsonExpr, node); + Const *cnst; + + if (!IsA(jexpr->path_spec, Const)) + return true; + + cnst = castNode(Const, jexpr->path_spec); + + Assert(cnst->consttype == JSONPATHOID); + if (cnst->constisnull) + return false; + + return jspIsMutable(DatumGetJsonPathP(cnst->constvalue), + jexpr->passing_names, jexpr->passing_values); + } + if (IsA(node, SQLValueFunction)) { /* all variants of SQLValueFunction are stable */ diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 99e05d8548..1736ba35bd 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -278,6 +278,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); MergeWhenClause *mergewhen; struct KeyActions *keyactions; struct KeyAction *keyaction; + JsonBehavior *jsbehavior; + JsonQuotes js_quotes; } %type stmt toplevel_stmt schema_stmt routine_body_stmt @@ -650,14 +652,22 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); json_returning_clause_opt json_name_and_value json_aggregate_func + json_api_common_syntax + json_argument %type json_name_and_value_list json_value_expr_list json_array_aggregate_order_by_clause_opt + json_arguments %type json_encoding_clause_opt json_predicate_type_constraint + json_wrapper_behavior %type json_key_uniqueness_constraint_opt json_object_constructor_null_clause_opt json_array_constructor_null_clause_opt +%type json_value_behavior + json_query_behavior + json_exists_behavior +%type json_quotes_clause_opt /* * Non-keyword token types. These are hard-wired into the "flex" lexer. @@ -694,7 +704,7 @@ 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 COMPRESSION CONCURRENTLY CONFIGURATION CONFLICT + COMMITTED COMPRESSION 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 @@ -705,8 +715,8 @@ 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 EXPRESSION + EACH ELSE EMPTY_P ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ERROR_P ESCAPE + EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXPRESSION EXTENSION EXTERNAL EXTRACT FALSE_P FAMILY FETCH FILTER FINALIZE FIRST_P FLOAT_P FOLLOWING FOR @@ -721,10 +731,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 JSON JSON_ARRAY JSON_ARRAYAGG JSON_OBJECT JSON_OBJECTAGG - JSON_SCALAR JSON_SERIALIZE + JOIN JSON JSON_ARRAY JSON_ARRAYAGG JSON_EXISTS JSON_OBJECT JSON_OBJECTAGG + JSON_QUERY JSON_SCALAR JSON_SERIALIZE JSON_VALUE - KEY KEYS + KEY KEYS KEEP LABEL LANGUAGE LARGE_P LAST_P LATERAL_P LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL @@ -738,7 +748,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 @@ -747,7 +757,7 @@ 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_P REFERENCES REFERENCING REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA @@ -758,7 +768,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); 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 STORED STRICT_P STRIP_P + START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRING_P STRIP_P SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_USER TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN @@ -766,7 +776,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); TREAT TRIGGER TRIM TRUE_P TRUNCATE TRUSTED TYPE_P TYPES_P - UESCAPE UNBOUNDED UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN + UESCAPE UNBOUNDED UNCONDITIONAL UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN UNLISTEN UNLOGGED UNTIL UPDATE USER USING VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING @@ -15671,6 +15681,192 @@ func_expr_common_subexpr: n->location = @1; $$ = (Node *) n; } + | JSON_QUERY '(' + json_api_common_syntax + json_returning_clause_opt + json_wrapper_behavior + json_quotes_clause_opt + ')' + { + JsonFuncExpr *n = makeNode(JsonFuncExpr); + + n->op = JSON_QUERY_OP; + 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 must not be specified when WITH WRAPPER is used"), + parser_errposition(@6))); + n->omit_quotes = $6 == JS_QUOTES_OMIT; + n->location = @1; + $$ = (Node *) n; + } + | JSON_QUERY '(' + json_api_common_syntax + json_returning_clause_opt + json_wrapper_behavior + json_quotes_clause_opt + json_query_behavior ON EMPTY_P + ')' + { + JsonFuncExpr *n = makeNode(JsonFuncExpr); + + n->op = JSON_QUERY_OP; + 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 must not be specified when WITH WRAPPER is used"), + parser_errposition(@6))); + n->omit_quotes = $6 == JS_QUOTES_OMIT; + n->on_empty = $7; + n->location = @1; + $$ = (Node *) n; + } + | JSON_QUERY '(' + json_api_common_syntax + json_returning_clause_opt + json_wrapper_behavior + json_quotes_clause_opt + json_query_behavior ON ERROR_P + ')' + { + JsonFuncExpr *n = makeNode(JsonFuncExpr); + + n->op = JSON_QUERY_OP; + 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 must not be specified when WITH WRAPPER is used"), + parser_errposition(@6))); + n->omit_quotes = $6 == JS_QUOTES_OMIT; + n->on_error = $7; + n->location = @1; + $$ = (Node *) n; + } + | JSON_QUERY '(' + json_api_common_syntax + json_returning_clause_opt + json_wrapper_behavior + json_quotes_clause_opt + json_query_behavior ON EMPTY_P + json_query_behavior ON ERROR_P + ')' + { + JsonFuncExpr *n = makeNode(JsonFuncExpr); + + n->op = JSON_QUERY_OP; + 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 must not be specified when WITH WRAPPER is used"), + parser_errposition(@6))); + n->omit_quotes = $6 == JS_QUOTES_OMIT; + n->on_empty = $7; + n->on_error = $10; + n->location = @1; + $$ = (Node *) n; + } + | JSON_EXISTS '(' + json_api_common_syntax + json_returning_clause_opt + ')' + { + JsonFuncExpr *p = makeNode(JsonFuncExpr); + + p->op = JSON_EXISTS_OP; + p->common = (JsonCommon *) $3; + p->output = (JsonOutput *) $4; + p->location = @1; + $$ = (Node *) p; + } + | JSON_EXISTS '(' + json_api_common_syntax + json_returning_clause_opt + json_exists_behavior ON ERROR_P + ')' + { + JsonFuncExpr *p = makeNode(JsonFuncExpr); + + p->op = JSON_EXISTS_OP; + p->common = (JsonCommon *) $3; + p->output = (JsonOutput *) $4; + p->on_error = $5; + p->location = @1; + $$ = (Node *) p; + } + | JSON_VALUE '(' + json_api_common_syntax + json_returning_clause_opt + ')' + { + JsonFuncExpr *n = makeNode(JsonFuncExpr); + + n->op = JSON_VALUE_OP; + n->common = (JsonCommon *) $3; + n->output = (JsonOutput *) $4; + n->location = @1; + $$ = (Node *) n; + } + + | JSON_VALUE '(' + json_api_common_syntax + json_returning_clause_opt + json_value_behavior ON EMPTY_P + ')' + { + JsonFuncExpr *n = makeNode(JsonFuncExpr); + + n->op = JSON_VALUE_OP; + n->common = (JsonCommon *) $3; + n->output = (JsonOutput *) $4; + n->on_empty = $5; + n->location = @1; + $$ = (Node *) n; + } + | JSON_VALUE '(' + json_api_common_syntax + json_returning_clause_opt + json_value_behavior ON ERROR_P + ')' + { + JsonFuncExpr *n = makeNode(JsonFuncExpr); + + n->op = JSON_VALUE_OP; + n->common = (JsonCommon *) $3; + n->output = (JsonOutput *) $4; + n->on_error = $5; + n->location = @1; + $$ = (Node *) n; + } + + | JSON_VALUE '(' + json_api_common_syntax + json_returning_clause_opt + json_value_behavior ON EMPTY_P + json_value_behavior ON ERROR_P + ')' + { + JsonFuncExpr *n = makeNode(JsonFuncExpr); + + n->op = JSON_VALUE_OP; + n->common = (JsonCommon *) $3; + n->output = (JsonOutput *) $4; + n->on_empty = $5; + n->on_error = $8; + n->location = @1; + $$ = (Node *) n; + } ; @@ -16397,6 +16593,72 @@ opt_asymmetric: ASYMMETRIC ; /* SQL/JSON support */ +json_api_common_syntax: + json_value_expr ',' a_expr /* i.e. a json_path */ + { + JsonCommon *n = makeNode(JsonCommon); + + n->expr = (JsonValueExpr *) $1; + n->pathspec = $3; + n->pathname = NULL; + n->passing = NULL; + n->location = @1; + $$ = (Node *) n; + } + | json_value_expr ',' a_expr AS name + { + JsonCommon *n = makeNode(JsonCommon); + + n->expr = (JsonValueExpr *) $1; + n->pathspec = $3; + n->pathname = $5; + n->passing = NULL; + n->location = @1; + $$ = (Node *) n; + } + + | json_value_expr ',' a_expr /* i.e. a json_path */ + PASSING json_arguments + { + JsonCommon *n = makeNode(JsonCommon); + + n->expr = (JsonValueExpr *) $1; + n->pathspec = $3; + n->pathname = NULL; + n->passing = $5; + n->location = @1; + $$ = (Node *) n; + } + | json_value_expr ',' a_expr AS name + PASSING json_arguments + { + JsonCommon *n = makeNode(JsonCommon); + + n->expr = (JsonValueExpr *) $1; + n->pathspec = $3; + n->pathname = $5; + n->passing = $7; + n->location = @1; + $$ = (Node *) n; + } + ; + +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 { @@ -16422,6 +16684,50 @@ json_encoding_clause_opt: | /* EMPTY */ { $$ = JS_ENC_DEFAULT; } ; +json_query_behavior: + ERROR_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_ERROR, NULL); } + | NULL_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_NULL, NULL); } + | DEFAULT a_expr { $$ = makeJsonBehavior(JSON_BEHAVIOR_DEFAULT, $2); } + | EMPTY_P ARRAY { $$ = makeJsonBehavior(JSON_BEHAVIOR_EMPTY_ARRAY, NULL); } + | EMPTY_P OBJECT_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_EMPTY_OBJECT, NULL); } + /* non-standard, for Oracle compatibility only */ + | EMPTY_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_EMPTY_ARRAY, NULL); } + ; + +json_exists_behavior: + ERROR_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_ERROR, NULL); } + | TRUE_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_TRUE, NULL); } + | FALSE_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_FALSE, NULL); } + | UNKNOWN { $$ = makeJsonBehavior(JSON_BEHAVIOR_UNKNOWN, NULL); } + ; + +json_value_behavior: + NULL_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_NULL, NULL); } + | ERROR_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_ERROR, NULL); } + | DEFAULT a_expr { $$ = makeJsonBehavior(JSON_BEHAVIOR_DEFAULT, $2); } + ; + +/* ARRAY is a noise word */ +json_wrapper_behavior: + WITHOUT WRAPPER { $$ = JSW_NONE; } + | WITHOUT ARRAY WRAPPER { $$ = JSW_NONE; } + | WITH WRAPPER { $$ = JSW_UNCONDITIONAL; } + | WITH ARRAY WRAPPER { $$ = JSW_UNCONDITIONAL; } + | WITH CONDITIONAL ARRAY WRAPPER { $$ = JSW_CONDITIONAL; } + | WITH UNCONDITIONAL ARRAY WRAPPER { $$ = JSW_UNCONDITIONAL; } + | WITH CONDITIONAL WRAPPER { $$ = JSW_CONDITIONAL; } + | WITH UNCONDITIONAL WRAPPER { $$ = JSW_UNCONDITIONAL; } + | /* empty */ { $$ = JSW_NONE; } + ; + +json_quotes_clause_opt: + KEEP QUOTES ON SCALAR STRING_P { $$ = JS_QUOTES_KEEP; } + | KEEP QUOTES { $$ = JS_QUOTES_KEEP; } + | OMIT QUOTES ON SCALAR STRING_P { $$ = JS_QUOTES_OMIT; } + | OMIT QUOTES { $$ = JS_QUOTES_OMIT; } + | /* EMPTY */ { $$ = JS_QUOTES_UNSPEC; } + ; + json_returning_clause_opt: RETURNING Typename json_format_clause_opt { @@ -17024,6 +17330,7 @@ unreserved_keyword: | COMMIT | COMMITTED | COMPRESSION + | CONDITIONAL | CONFIGURATION | CONFLICT | CONNECTION @@ -17060,10 +17367,12 @@ unreserved_keyword: | DOUBLE_P | DROP | EACH + | EMPTY_P | ENABLE_P | ENCODING | ENCRYPTED | ENUM_P + | ERROR_P | ESCAPE | EVENT | EXCLUDE @@ -17113,6 +17422,7 @@ unreserved_keyword: | INSTEAD | INVOKER | ISOLATION + | KEEP | KEY | KEYS | LABEL @@ -17159,6 +17469,7 @@ unreserved_keyword: | OFF | OIDS | OLD + | OMIT | OPERATOR | OPTION | OPTIONS @@ -17189,6 +17500,7 @@ unreserved_keyword: | PROGRAM | PUBLICATION | QUOTE + | QUOTES | RANGE | READ | REASSIGN @@ -17248,6 +17560,7 @@ unreserved_keyword: | STORAGE | STORED | STRICT_P + | STRING_P | STRIP_P | SUBSCRIPTION | SUPPORT @@ -17270,6 +17583,7 @@ unreserved_keyword: | UESCAPE | UNBOUNDED | UNCOMMITTED + | UNCONDITIONAL | UNENCRYPTED | UNKNOWN | UNLISTEN @@ -17330,10 +17644,13 @@ col_name_keyword: | JSON | JSON_ARRAY | JSON_ARRAYAGG + | JSON_EXISTS | JSON_OBJECT | JSON_OBJECTAGG + | JSON_QUERY | JSON_SCALAR | JSON_SERIALIZE + | JSON_VALUE | LEAST | NATIONAL | NCHAR @@ -17566,6 +17883,7 @@ bare_label_keyword: | COMMITTED | COMPRESSION | CONCURRENTLY + | CONDITIONAL | CONFIGURATION | CONFLICT | CONNECTION @@ -17618,11 +17936,13 @@ bare_label_keyword: | DROP | EACH | ELSE + | EMPTY_P | ENABLE_P | ENCODING | ENCRYPTED | END_P | ENUM_P + | ERROR_P | ESCAPE | EVENT | EXCLUDE @@ -17692,10 +18012,14 @@ bare_label_keyword: | JSON | JSON_ARRAY | JSON_ARRAYAGG + | JSON_EXISTS | JSON_OBJECT | JSON_OBJECTAGG + | JSON_QUERY | JSON_SCALAR | JSON_SERIALIZE + | JSON_VALUE + | KEEP | KEY | KEYS | LABEL @@ -17756,6 +18080,7 @@ bare_label_keyword: | OFF | OIDS | OLD + | OMIT | ONLY | OPERATOR | OPTION @@ -17793,6 +18118,7 @@ bare_label_keyword: | PROGRAM | PUBLICATION | QUOTE + | QUOTES | RANGE | READ | REAL @@ -17861,6 +18187,7 @@ bare_label_keyword: | STORAGE | STORED | STRICT_P + | STRING_P | STRIP_P | SUBSCRIPTION | SUBSTRING @@ -17895,6 +18222,7 @@ bare_label_keyword: | UESCAPE | UNBOUNDED | UNCOMMITTED + | UNCONDITIONAL | UNENCRYPTED | UNIQUE | UNKNOWN diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c index 9f6afc351c..692e5d1225 100644 --- a/src/backend/parser/parse_collate.c +++ b/src/backend/parser/parse_collate.c @@ -691,6 +691,13 @@ 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 e7f988dbd2..ca60b5604a 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -90,6 +90,7 @@ static Node *transformJsonParseExpr(ParseState *pstate, JsonParseExpr *expr); static Node *transformJsonScalarExpr(ParseState *pstate, JsonScalarExpr *expr); static Node *transformJsonSerializeExpr(ParseState *pstate, JsonSerializeExpr * expr); +static Node *transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *p); 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, @@ -353,6 +354,10 @@ transformExprRecurse(ParseState *pstate, Node *expr) result = transformJsonSerializeExpr(pstate, (JsonSerializeExpr *) expr); break; + case T_JsonFuncExpr: + result = transformJsonFuncExpr(pstate, (JsonFuncExpr *) expr); + break; + default: /* should not reach here */ elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); @@ -3229,7 +3234,7 @@ makeJsonByteaToTextConversion(Node *expr, JsonFormat *format, int location) static Node * transformJsonValueExpr(ParseState *pstate, const char *constructName, JsonValueExpr *ve, JsonFormatType default_format, - Oid targettype) + Oid targettype, bool isarg) { Node *expr = transformExprRecurse(pstate, (Node *) ve->raw_expr); Node *rawexpr; @@ -3266,6 +3271,35 @@ transformJsonValueExpr(ParseState *pstate, const char *constructName, else format = ve->format->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 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 @@ -3277,7 +3311,8 @@ transformJsonValueExpr(ParseState *pstate, const char *constructName, Node *coerced; bool cast_is_needed = OidIsValid(targettype); - if (!cast_is_needed && + if (!isarg && + !cast_is_needed && exprtype != BYTEAOID && typcategory != TYPCATEGORY_STRING) ereport(ERROR, errcode(ERRCODE_DATATYPE_MISMATCH), @@ -3622,7 +3657,7 @@ transformJsonObjectConstructor(ParseState *pstate, JsonObjectConstructor *ctor) Node *val = transformJsonValueExpr(pstate, "JSON_OBJECT()", kv->value, JS_FORMAT_DEFAULT, - InvalidOid); + InvalidOid, false); args = lappend(args, key); args = lappend(args, val); @@ -3807,7 +3842,7 @@ transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg) key = transformExprRecurse(pstate, (Node *) agg->arg->key); val = transformJsonValueExpr(pstate, "JSON_OBJECTAGG()", agg->arg->value, - JS_FORMAT_DEFAULT, InvalidOid); + JS_FORMAT_DEFAULT, InvalidOid, false); args = list_make2(key, val); returning = transformJsonConstructorOutput(pstate, agg->constructor->output, @@ -3863,9 +3898,8 @@ transformJsonArrayAgg(ParseState *pstate, JsonArrayAgg *agg) Oid aggfnoid; Oid aggtype; - arg = transformJsonValueExpr(pstate, "JSON_ARRAYAGG()", - agg->arg, - JS_FORMAT_DEFAULT, InvalidOid); + arg = transformJsonValueExpr(pstate, "JSON_ARRAYAGG()", agg->arg, + JS_FORMAT_DEFAULT, InvalidOid, false); returning = transformJsonConstructorOutput(pstate, agg->constructor->output, list_make1(arg)); @@ -3912,9 +3946,8 @@ transformJsonArrayConstructor(ParseState *pstate, JsonArrayConstructor *ctor) { JsonValueExpr *jsval = castNode(JsonValueExpr, lfirst(lc)); Node *val = transformJsonValueExpr(pstate, "JSON_ARRAY()", - jsval, - JS_FORMAT_DEFAULT, - InvalidOid); + jsval, JS_FORMAT_DEFAULT, + InvalidOid, false); args = lappend(args, val); } @@ -4070,7 +4103,7 @@ transformJsonParseExpr(ParseState *pstate, JsonParseExpr *jsexpr) * function-like CASTs. */ arg = transformJsonValueExpr(pstate, "JSON()", jsexpr->expr, - JS_FORMAT_JSON, returning->typid); + JS_FORMAT_JSON, returning->typid, false); } return makeJsonConstructorExpr(pstate, JSCTOR_JSON_PARSE, list_make1(arg), NULL, @@ -4104,9 +4137,8 @@ static Node * transformJsonSerializeExpr(ParseState *pstate, JsonSerializeExpr * expr) { Node *arg = transformJsonValueExpr(pstate, "JSON_SERIALIZE()", - expr->expr, - JS_FORMAT_JSON, - InvalidOid); + expr->expr, JS_FORMAT_JSON, + InvalidOid, false); JsonReturning *returning; if (expr->output) @@ -4141,3 +4173,462 @@ transformJsonSerializeExpr(ParseState *pstate, JsonSerializeExpr * expr) return makeJsonConstructorExpr(pstate, JSCTOR_JSON_SERIALIZE, list_make1(arg), NULL, returning, false, false, expr->location); } + +/* + * Transform a JSON PASSING clause. + */ +static void +transformJsonPassingArgs(ParseState *pstate, char *constructName, + JsonFormatType format, List *args, + List **passing_values, List **passing_names) +{ + ListCell *lc; + + *passing_values = NIL; + *passing_names = NIL; + + foreach(lc, args) + { + JsonArgument *arg = castNode(JsonArgument, lfirst(lc)); + Node *expr = transformJsonValueExpr(pstate, constructName, + arg->val, format, + InvalidOid, true); + + 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) +{ + JsonBehaviorType behavior_type = default_behavior; + Node *default_expr = NULL; + + if (behavior) + { + behavior_type = behavior->btype; + if (behavior_type == JSON_BEHAVIOR_DEFAULT) + default_expr = transformExprRecurse(pstate, behavior->default_expr); + } + return makeJsonBehavior(behavior_type, default_expr); +} + +/* + * 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; + char *constructName; + + 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; + switch (jsexpr->op) + { + case JSON_VALUE_OP: + constructName = "JSON_VALUE()"; + break; + case JSON_QUERY_OP: + constructName = "JSON_QUERY()"; + break; + case JSON_EXISTS_OP: + constructName = "JSON_EXISTS()"; + break; + default: + elog(ERROR, "unrecognized SQL/JSON expression op %d", jsexpr->op); + break; + } + jsexpr->formatted_expr = transformJsonValueExpr(pstate, constructName, + func->common->expr, + JS_FORMAT_DEFAULT, + InvalidOid, false); + + 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; + + 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, constructName, format, + func->common->passing, + &jsexpr->passing_values, + &jsexpr->passing_names); + + if (func->op != JSON_EXISTS_OP) + jsexpr->on_empty = transformJsonBehavior(pstate, func->on_empty, + JSON_BEHAVIOR_NULL); + + jsexpr->on_error = transformJsonBehavior(pstate, func->on_error, + func->op == JSON_EXISTS_OP ? + 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 = copyObject(context_format); + + if (ret->format->format_type == JS_FORMAT_DEFAULT) + is_jsonb = exprType(context_item) == JSONBOID; + else + is_jsonb = ret->format->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, const 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 and JSON_QUERY. + */ +static void +transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func, + JsonExpr *jsexpr) +{ + Node *expr = jsexpr->formatted_expr; + + jsexpr->returning = transformJsonOutput(pstate, func->output, false); + + /* JSON_VALUE returns text by default */ + if (func->op == JSON_VALUE_OP && !OidIsValid(jsexpr->returning->typid)) + { + jsexpr->returning->typid = TEXTOID; + jsexpr->returning->typmod = -1; + } + + if (OidIsValid(jsexpr->returning->typid)) + { + JsonReturning ret; + + if (func->op == JSON_VALUE_OP && + 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->formatted_expr, jsexpr->format, &ret); + + if (ret.typid != jsexpr->returning->typid || + ret.typmod != jsexpr->returning->typmod) + { + CaseTestExpr *cte = makeNode(CaseTestExpr); + + cte->typeId = exprType(expr); + cte->typeMod = exprTypmod(expr); + cte->collation = exprCollation(expr); + + Assert(cte->typeId == ret.typid); + Assert(cte->typeMod == ret.typmod); + + jsexpr->result_coercion = coerceJsonExpr(pstate, (Node *) cte, + jsexpr->returning); + } + } + else + assignDefaultJsonReturningType(jsexpr->formatted_expr, jsexpr->format, + jsexpr->returning); +} + +/* + * Coerce an 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_IMPLICIT_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, + const 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, + const 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); + const char *func_name = NULL; + Node *contextItemExpr = jsexpr->formatted_expr; + + /* + * Disallow FORMAT specification in the RETURNING clause of JSON_EXISTS() + * and JSON_VALUE(). + */ + if (func->output && + (func->op == JSON_VALUE_OP || func->op == JSON_EXISTS_OP)) + { + JsonFormat *format = func->output->returning->format; + + if(format->format_type != JS_FORMAT_DEFAULT || + format->encoding != JS_ENC_DEFAULT) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("cannot specify FORMAT in RETURNING clause of %s", + func->op == JSON_VALUE_OP ? "JSON_VALUE()" : + "JSON_EXISTS()"), + parser_errposition(pstate, format->location))); + } + + switch (func->op) + { + case JSON_VALUE_OP: + func_name = "JSON_VALUE"; + + transformJsonFuncExprOutput(pstate, func, jsexpr); + + jsexpr->returning->format->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 JSON_QUERY_OP: + func_name = "JSON_QUERY"; + + transformJsonFuncExprOutput(pstate, func, jsexpr); + + 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->wrapper = func->wrapper; + jsexpr->omit_quotes = func->omit_quotes; + + break; + + case JSON_EXISTS_OP: + func_name = "JSON_EXISTS"; + + jsexpr->returning = transformJsonOutput(pstate, func->output, false); + + jsexpr->returning->format->format_type = JS_FORMAT_DEFAULT; + jsexpr->returning->format->encoding = JS_ENC_DEFAULT; + + if (!OidIsValid(jsexpr->returning->typid)) + { + jsexpr->returning->typid = BOOLOID; + jsexpr->returning->typmod = -1; + } + else if (jsexpr->returning->typid != BOOLOID) + { + CaseTestExpr *placeholder = makeNode(CaseTestExpr); + int location = exprLocation((Node *) jsexpr); + + placeholder->typeId = BOOLOID; + placeholder->typeMod = -1; + placeholder->collation = InvalidOid; + + jsexpr->result_coercion = makeNode(JsonCoercion); + jsexpr->result_coercion->expr = + coerce_to_target_type(pstate, (Node *) placeholder, BOOLOID, + jsexpr->returning->typid, + jsexpr->returning->typmod, + COERCION_EXPLICIT, + COERCE_IMPLICIT_CAST, + location); + + if (!jsexpr->result_coercion->expr) + ereport(ERROR, + (errcode(ERRCODE_CANNOT_COERCE), + errmsg("cannot cast type %s to %s", + format_type_be(BOOLOID), + format_type_be(jsexpr->returning->typid)), + parser_coercion_errposition(pstate, location, (Node *) jsexpr))); + + if (jsexpr->result_coercion->expr == (Node *) placeholder) + jsexpr->result_coercion->expr = NULL; + } + break; + } + + if (exprType(contextItemExpr) != JSONBOID) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("%s() is not yet implemented for the json type", func_name), + errhint("Try casting the argument to jsonb"), + 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 520d4f2a23..4f94fc69d6 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -1979,6 +1979,21 @@ FigureColnameInternal(Node *node, char **name) /* make JSON_ARRAYAGG act like a regular function */ *name = "json_arrayagg"; return 2; + case T_JsonFuncExpr: + /* make SQL/JSON functions act like a regular function */ + switch (((JsonFuncExpr *) node)->op) + { + case JSON_QUERY_OP: + *name = "json_query"; + return 2; + case JSON_VALUE_OP: + *name = "json_value"; + return 2; + case JSON_EXISTS_OP: + *name = "json_exists"; + return 2; + } + break; default: break; } diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c index e27ea8ef97..188b5594e0 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -4426,6 +4426,50 @@ parse_datetime(text *date_txt, text *fmt, Oid collid, bool strict, } } +/* + * Parses the datetime format string in 'fmt_str' and returns true if it + * contains a timezone specifier, false if not. + */ +bool +datetime_format_has_tz(const char *fmt_str) +{ + bool incache; + int fmt_len = strlen(fmt_str); + int result; + FormatNode *format; + + if (fmt_len > DCH_CACHE_SIZE) + { + /* + * Allocate new memory if format picture is bigger than static cache + * and do not use cache (call parser always) + */ + incache = false; + + format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode)); + + parse_format(format, fmt_str, DCH_keywords, + DCH_suff, DCH_index, DCH_FLAG, NULL); + } + else + { + /* + * Use cache buffers + */ + DCHCacheEntry *ent = DCH_cache_fetch(fmt_str, false); + + incache = true; + format = ent->format; + } + + result = DCH_datetime_type(format); + + if (!incache) + pfree(format); + + return result & DCH_ZONED; +} + /* * do_to_timestamp: shared code for to_timestamp and to_date * diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index 06ba409e64..d2b4da8ec8 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -2156,3 +2156,65 @@ jsonb_float8(PG_FUNCTION_ARGS) PG_RETURN_DATUM(retValue); } + +/* + * Construct an empty array jsonb. + */ +Jsonb * +JsonbMakeEmptyArray(void) +{ + JsonbValue jbv; + + jbv.type = jbvArray; + jbv.val.array.elems = NULL; + jbv.val.array.nElems = 0; + jbv.val.array.rawScalar = false; + + return JsonbValueToJsonb(&jbv); +} + +/* + * Construct an empty object jsonb. + */ +Jsonb * +JsonbMakeEmptyObject(void) +{ + JsonbValue jbv; + + jbv.type = jbvObject; + jbv.val.object.pairs = NULL; + jbv.val.object.nPairs = 0; + + return JsonbValueToJsonb(&jbv); +} + +/* + * Convert jsonb to a C-string stripping quotes from scalar strings. + */ +char * +JsonbUnquote(Jsonb *jb) +{ + if (JB_ROOT_IS_SCALAR(jb)) + { + JsonbValue v; + + (void) 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 a4bfa5e404..2d5fa285fc 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -265,6 +265,7 @@ typedef struct PopulateArrayContext int *dims; /* dimensions */ int *sizes; /* current dimension counters */ int ndims; /* number of dimensions */ + Node *escontext; /* For soft-error capture */ } PopulateArrayContext; /* state for populate_array_json() */ @@ -442,12 +443,13 @@ static void JsValueToJsObject(JsValue *jsv, JsObject *jso); static Datum populate_composite(CompositeIOData *io, Oid typid, const char *colname, MemoryContext mcxt, HeapTupleHeader defaultval, JsValue *jsv, bool isnull); -static Datum populate_scalar(ScalarIOData *io, Oid typid, int32 typmod, JsValue *jsv); +static Datum populate_scalar(ScalarIOData *io, Oid typid, int32 typmod, JsValue *jsv, + Node *escontext); static void prepare_column_cache(ColumnIOData *column, Oid typid, int32 typmod, MemoryContext mcxt, bool need_scalar); static Datum populate_record_field(ColumnIOData *col, Oid typid, int32 typmod, const char *colname, MemoryContext mcxt, Datum defaultval, - JsValue *jsv, bool *isnull); + JsValue *jsv, bool *isnull, Node *escontext); static RecordIOData *allocate_record_info(MemoryContext mcxt, int ncolumns); static bool JsObjectGetField(JsObject *obj, char *field, JsValue *jsv); static void populate_recordset_record(PopulateRecordsetState *state, JsObject *obj); @@ -459,7 +461,8 @@ static void populate_array_assign_ndims(PopulateArrayContext *ctx, int ndims); static void populate_array_check_dimension(PopulateArrayContext *ctx, int ndim); static void populate_array_element(PopulateArrayContext *ctx, int ndim, JsValue *jsv); static Datum populate_array(ArrayIOData *aio, const char *colname, - MemoryContext mcxt, JsValue *jsv); + MemoryContext mcxt, JsValue *jsv, Node *escontext, + bool *isnull); static Datum populate_domain(DomainIOData *io, Oid typid, const char *colname, MemoryContext mcxt, JsValue *jsv, bool isnull); @@ -2484,12 +2487,12 @@ populate_array_report_expected_array(PopulateArrayContext *ctx, int ndim) if (ndim <= 0) { if (ctx->colname) - ereport(ERROR, + errsave(ctx->escontext, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("expected JSON array"), errhint("See the value of key \"%s\".", ctx->colname))); else - ereport(ERROR, + errsave(ctx->escontext, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("expected JSON array"))); } @@ -2506,13 +2509,13 @@ populate_array_report_expected_array(PopulateArrayContext *ctx, int ndim) appendStringInfo(&indices, "[%d]", ctx->sizes[i]); if (ctx->colname) - ereport(ERROR, + errsave(ctx->escontext, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("expected JSON array"), errhint("See the array element %s of key \"%s\".", indices.data, ctx->colname))); else - ereport(ERROR, + errsave(ctx->escontext, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("expected JSON array"), errhint("See the array element %s.", @@ -2520,7 +2523,11 @@ populate_array_report_expected_array(PopulateArrayContext *ctx, int ndim) } } -/* set the number of dimensions of the populated array when it becomes known */ +/* + * Set the number of dimensions of the populated array when it becomes known. + * + * Returns without doing anything if the input (ndims) is erratic. + */ static void populate_array_assign_ndims(PopulateArrayContext *ctx, int ndims) { @@ -2531,6 +2538,10 @@ populate_array_assign_ndims(PopulateArrayContext *ctx, int ndims) if (ndims <= 0) populate_array_report_expected_array(ctx, ndims); + /* Nothing to do on an error. */ + if (SOFT_ERROR_OCCURRED(ctx->escontext)) + return; + ctx->ndims = ndims; ctx->dims = palloc(sizeof(int) * ndims); ctx->sizes = palloc0(sizeof(int) * ndims); @@ -2548,12 +2559,16 @@ populate_array_check_dimension(PopulateArrayContext *ctx, int ndim) if (ctx->dims[ndim] == -1) ctx->dims[ndim] = dim; /* assign dimension if not yet known */ else if (ctx->dims[ndim] != dim) - ereport(ERROR, + errsave(ctx->escontext, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed JSON array"), errdetail("Multidimensional arrays must have " "sub-arrays with matching dimensions."))); + /* Nothing to do on an error. */ + if (SOFT_ERROR_OCCURRED(ctx->escontext)) + return; + /* reset the current array dimension size counter */ ctx->sizes[ndim] = 0; @@ -2573,7 +2588,10 @@ populate_array_element(PopulateArrayContext *ctx, int ndim, JsValue *jsv) ctx->aio->element_type, ctx->aio->element_typmod, NULL, ctx->mcxt, PointerGetDatum(NULL), - jsv, &element_isnull); + jsv, &element_isnull, ctx->escontext); + /* Nothing to do on an error. */ + if (SOFT_ERROR_OCCURRED(ctx->escontext)) + return; accumArrayResult(ctx->astate, element, element_isnull, ctx->aio->element_type, ctx->acxt); @@ -2594,6 +2612,10 @@ populate_array_object_start(void *_state) else if (ndim < state->ctx->ndims) populate_array_report_expected_array(state->ctx, ndim); + /* Report that an error occurred. */ + if (SOFT_ERROR_OCCURRED(state->ctx->escontext)) + return JSON_SEM_ACTION_FAILED; + return JSON_SUCCESS; } @@ -2611,6 +2633,10 @@ populate_array_array_end(void *_state) if (ndim < ctx->ndims) populate_array_check_dimension(ctx, ndim); + /* Report that an error occurred. */ + if (SOFT_ERROR_OCCURRED(state->ctx->escontext)) + return JSON_SEM_ACTION_FAILED; + return JSON_SUCCESS; } @@ -2686,6 +2712,10 @@ populate_array_scalar(void *_state, char *token, JsonTokenType tokentype) else if (ndim < ctx->ndims) populate_array_report_expected_array(ctx, ndim); + /* Report that an error occurred. */ + if (SOFT_ERROR_OCCURRED(ctx->escontext)) + return JSON_SEM_ACTION_FAILED; + if (ndim == ctx->ndims) { /* remember the scalar element token */ @@ -2715,7 +2745,13 @@ populate_array_json(PopulateArrayContext *ctx, char *json, int len) sem.array_element_end = populate_array_element_end; sem.scalar = populate_array_scalar; - pg_parse_json_or_ereport(state.lex, &sem); + if (!pg_parse_json_or_errsave(state.lex, &sem, ctx->escontext)) + { + /* Nothing to do on an error. */ + Assert(SOFT_ERROR_OCCURRED(ctx->escontext)); + pfree(state.lex); + return; + } /* number of dimensions should be already known */ Assert(ctx->ndims > 0 && ctx->dims); @@ -2740,10 +2776,15 @@ populate_array_dim_jsonb(PopulateArrayContext *ctx, /* context */ check_stack_depth(); - if (jbv->type != jbvBinary || !JsonContainerIsArray(jbc)) + if (jbv->type != jbvBinary || + !JsonContainerIsArray(jbc) || + JsonContainerIsScalar(jbc)) + { populate_array_report_expected_array(ctx, ndim - 1); - - Assert(!JsonContainerIsScalar(jbc)); + /* Nothing to do on an error. */ + Assert(SOFT_ERROR_OCCURRED(ctx->escontext)); + return; + } it = JsonbIteratorInit(jbc); @@ -2762,7 +2803,12 @@ populate_array_dim_jsonb(PopulateArrayContext *ctx, /* context */ (tok == WJB_ELEM && (val.type != jbvBinary || !JsonContainerIsArray(val.val.binary.data))))) + { populate_array_assign_ndims(ctx, ndim); + /* Nothing to do on an error. */ + if (SOFT_ERROR_OCCURRED(ctx->escontext)) + return; + } jsv.is_json = false; jsv.val.jsonb = &val; @@ -2780,6 +2826,9 @@ populate_array_dim_jsonb(PopulateArrayContext *ctx, /* context */ { /* populate child sub-array */ populate_array_dim_jsonb(ctx, &val, ndim + 1); + /* Nothing to do on an error. */ + if (SOFT_ERROR_OCCURRED(ctx->escontext)) + return; /* number of dimensions should be already known */ Assert(ctx->ndims > 0 && ctx->dims); @@ -2797,12 +2846,18 @@ populate_array_dim_jsonb(PopulateArrayContext *ctx, /* context */ Assert(tok == WJB_DONE && !it); } -/* recursively populate an array from json/jsonb */ +/* + * Recursively populate an array from json/jsonb + * + * *isnull is set to true if an error is reported during parsing. + */ static Datum populate_array(ArrayIOData *aio, const char *colname, MemoryContext mcxt, - JsValue *jsv) + JsValue *jsv, + Node *escontext, + bool *isnull) { PopulateArrayContext ctx; Datum result; @@ -2817,6 +2872,7 @@ populate_array(ArrayIOData *aio, ctx.ndims = 0; /* unknown yet */ ctx.dims = NULL; ctx.sizes = NULL; + ctx.escontext = escontext; if (jsv->is_json) populate_array_json(&ctx, jsv->val.json.str, @@ -2825,7 +2881,16 @@ populate_array(ArrayIOData *aio, else { populate_array_dim_jsonb(&ctx, jsv->val.jsonb, 1); - ctx.dims[0] = ctx.sizes[0]; + /* Nothing to do on an error. */ + if (!SOFT_ERROR_OCCURRED(ctx.escontext)) + ctx.dims[0] = ctx.sizes[0]; + } + + /* Nothing to return if an error occurred. */ + if (SOFT_ERROR_OCCURRED(ctx.escontext)) + { + *isnull = true; + return (Datum) 0; } Assert(ctx.ndims > 0); @@ -2842,6 +2907,7 @@ populate_array(ArrayIOData *aio, pfree(ctx.sizes); pfree(lbs); + *isnull = false; return result; } @@ -2957,7 +3023,8 @@ populate_composite(CompositeIOData *io, /* populate non-null scalar value from json/jsonb value */ static Datum -populate_scalar(ScalarIOData *io, Oid typid, int32 typmod, JsValue *jsv) +populate_scalar(ScalarIOData *io, Oid typid, int32 typmod, JsValue *jsv, + Node *escontext) { Datum res; char *str = NULL; @@ -3028,7 +3095,11 @@ populate_scalar(ScalarIOData *io, Oid typid, int32 typmod, JsValue *jsv) elog(ERROR, "unrecognized jsonb type: %d", (int) jbv->type); } - res = InputFunctionCall(&io->typiofunc, str, io->typioparam, typmod); + if (!InputFunctionCallSafe(&io->typiofunc, str, io->typioparam, typmod, + escontext, &res)) + { + res = (Datum) 0; + } /* free temporary buffer */ if (str != json) @@ -3054,7 +3125,7 @@ populate_domain(DomainIOData *io, res = populate_record_field(io->base_io, io->base_typid, io->base_typmod, colname, mcxt, PointerGetDatum(NULL), - jsv, &isnull); + jsv, &isnull, NULL); Assert(!isnull); } @@ -3159,7 +3230,8 @@ populate_record_field(ColumnIOData *col, MemoryContext mcxt, Datum defaultval, JsValue *jsv, - bool *isnull) + bool *isnull, + Node *escontext) { TypeCat typcat; @@ -3192,10 +3264,12 @@ populate_record_field(ColumnIOData *col, switch (typcat) { case TYPECAT_SCALAR: - return populate_scalar(&col->scalar_io, typid, typmod, jsv); + return populate_scalar(&col->scalar_io, typid, typmod, jsv, + escontext); case TYPECAT_ARRAY: - return populate_array(&col->io.array, colname, mcxt, jsv); + return populate_array(&col->io.array, colname, mcxt, jsv, + escontext, isnull); case TYPECAT_COMPOSITE: case TYPECAT_COMPOSITE_DOMAIN: @@ -3216,6 +3290,53 @@ 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, + Node *escontext) +{ + 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, + escontext); +} + static RecordIOData * allocate_record_info(MemoryContext mcxt, int ncolumns) { @@ -3357,7 +3478,8 @@ populate_record(TupleDesc tupdesc, mcxt, nulls[i] ? (Datum) 0 : values[i], &field, - &nulls[i]); + &nulls[i], + NULL); } res = heap_form_tuple(tupdesc, values, nulls); diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index 7891fde310..5194d0d91f 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -68,7 +68,9 @@ #include "libpq/pqformat.h" #include "nodes/miscnodes.h" #include "miscadmin.h" +#include "nodes/nodeFuncs.h" #include "utils/builtins.h" +#include "utils/formatting.h" #include "utils/json.h" #include "utils/jsonpath.h" @@ -1109,3 +1111,256 @@ jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from, JsonPathItem *to, return true; } + +/* SQL/JSON datatype status: */ +typedef enum JsonPathDatatypeStatus +{ + jpdsNonDateTime, /* null, bool, numeric, string, array, object */ + jpdsUnknownDateTime, /* unknown datetime type */ + jpdsDateTimeZoned, /* timetz, timestamptz */ + jpdsDateTimeNonZoned /* time, timestamp, date */ +} JsonPathDatatypeStatus; + +/* Context for jspIsMutableWalker() */ +typedef struct JsonPathMutableContext +{ + List *varnames; /* list of variable names */ + List *varexprs; /* list of variable expressions */ + JsonPathDatatypeStatus current; /* status of @ item */ + bool lax; /* jsonpath is lax or strict */ + bool mutable; /* resulting mutability status */ +} JsonPathMutableContext; + +/* + * Recursive walker for jspIsMutable() + */ +static JsonPathDatatypeStatus +jspIsMutableWalker(JsonPathItem *jpi, JsonPathMutableContext *cxt) +{ + JsonPathItem next; + JsonPathDatatypeStatus status = jpdsNonDateTime; + + while (!cxt->mutable) + { + JsonPathItem arg; + JsonPathDatatypeStatus leftStatus; + JsonPathDatatypeStatus rightStatus; + + switch (jpi->type) + { + case jpiRoot: + Assert(status == jpdsNonDateTime); + break; + + case jpiCurrent: + Assert(status == jpdsNonDateTime); + status = cxt->current; + break; + + case jpiFilter: + { + JsonPathDatatypeStatus prevStatus = cxt->current; + + cxt->current = status; + jspGetArg(jpi, &arg); + jspIsMutableWalker(&arg, cxt); + + cxt->current = prevStatus; + break; + } + + case jpiVariable: + { + int32 len; + const char *name = jspGetString(jpi, &len); + ListCell *lc1; + ListCell *lc2; + + Assert(status == jpdsNonDateTime); + + forboth(lc1, cxt->varnames, lc2, cxt->varexprs) + { + String *varname = lfirst_node(String, lc1); + Node *varexpr = lfirst(lc2); + + if (strncmp(varname->sval, name, len)) + continue; + + switch (exprType(varexpr)) + { + case DATEOID: + case TIMEOID: + case TIMESTAMPOID: + status = jpdsDateTimeNonZoned; + break; + + case TIMETZOID: + case TIMESTAMPTZOID: + status = jpdsDateTimeZoned; + break; + + default: + status = jpdsNonDateTime; + break; + } + + break; + } + break; + } + + case jpiEqual: + case jpiNotEqual: + case jpiLess: + case jpiGreater: + case jpiLessOrEqual: + case jpiGreaterOrEqual: + Assert(status == jpdsNonDateTime); + jspGetLeftArg(jpi, &arg); + leftStatus = jspIsMutableWalker(&arg, cxt); + + jspGetRightArg(jpi, &arg); + rightStatus = jspIsMutableWalker(&arg, cxt); + + /* + * Comparison of datetime type with different timezone status + * is mutable. + */ + if (leftStatus != jpdsNonDateTime && + rightStatus != jpdsNonDateTime && + (leftStatus == jpdsUnknownDateTime || + rightStatus == jpdsUnknownDateTime || + leftStatus != rightStatus)) + cxt->mutable = true; + break; + + case jpiNot: + case jpiIsUnknown: + case jpiExists: + case jpiPlus: + case jpiMinus: + Assert(status == jpdsNonDateTime); + jspGetArg(jpi, &arg); + jspIsMutableWalker(&arg, cxt); + break; + + case jpiAnd: + case jpiOr: + case jpiAdd: + case jpiSub: + case jpiMul: + case jpiDiv: + case jpiMod: + case jpiStartsWith: + Assert(status == jpdsNonDateTime); + jspGetLeftArg(jpi, &arg); + jspIsMutableWalker(&arg, cxt); + jspGetRightArg(jpi, &arg); + jspIsMutableWalker(&arg, cxt); + break; + + case jpiIndexArray: + for (int i = 0; i < jpi->content.array.nelems; i++) + { + JsonPathItem from; + JsonPathItem to; + + if (jspGetArraySubscript(jpi, &from, &to, i)) + jspIsMutableWalker(&to, cxt); + + jspIsMutableWalker(&from, cxt); + } + /* FALLTHROUGH */ + + case jpiAnyArray: + if (!cxt->lax) + status = jpdsNonDateTime; + break; + + case jpiAny: + if (jpi->content.anybounds.first > 0) + status = jpdsNonDateTime; + break; + + case jpiDatetime: + if (jpi->content.arg) + { + char *template; + + jspGetArg(jpi, &arg); + if (arg.type != jpiString) + { + status = jpdsNonDateTime; + break; /* there will be runtime error */ + } + + template = jspGetString(&arg, NULL); + if (datetime_format_has_tz(template)) + status = jpdsDateTimeZoned; + else + status = jpdsDateTimeNonZoned; + } + else + { + status = jpdsUnknownDateTime; + } + break; + + case jpiLikeRegex: + Assert(status == jpdsNonDateTime); + jspInitByBuffer(&arg, jpi->base, jpi->content.like_regex.expr); + jspIsMutableWalker(&arg, cxt); + break; + + /* literals */ + case jpiNull: + case jpiString: + case jpiNumeric: + case jpiBool: + /* accessors */ + case jpiKey: + case jpiAnyKey: + /* special items */ + case jpiSubscript: + case jpiLast: + /* item methods */ + case jpiType: + case jpiSize: + case jpiAbs: + case jpiFloor: + case jpiCeiling: + case jpiDouble: + case jpiKeyValue: + status = jpdsNonDateTime; + break; + } + + if (!jspGetNext(jpi, &next)) + break; + + jpi = &next; + } + + return status; +} + +/* + * Check whether jsonpath expression is immutable or not. + */ +bool +jspIsMutable(JsonPath *path, List *varnames, List *varexprs) +{ + JsonPathMutableContext cxt; + JsonPathItem jpi; + + cxt.varnames = varnames; + cxt.varexprs = varexprs; + cxt.current = jpdsNonDateTime; + cxt.lax = (path->header & JSONPATH_LAX) != 0; + cxt.mutable = false; + + jspInit(&jpi, path); + jspIsMutableWalker(&jpi, &cxt); + + return cxt.mutable; +} diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 2d0599b4aa..eded22e194 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -87,12 +87,16 @@ typedef struct JsonBaseObjectInfo int id; } JsonBaseObjectInfo; +typedef int (*JsonPathVarCallback) (void *vars, char *varName, int varNameLen, + JsonbValue *val, JsonbValue *baseObject); + /* * Context of jsonpath execution. */ typedef struct JsonPathExecContext { - Jsonb *vars; /* variables to substitute into jsonpath */ + void *vars; /* variables to substitute into jsonpath */ + JsonPathVarCallback getVar; JsonbValue *root; /* for $ evaluation */ JsonbValue *current; /* for @ evaluation */ JsonBaseObjectInfo baseObject; /* "base object" for .keyvalue() @@ -174,7 +178,8 @@ typedef JsonPathBool (*JsonPathPredicateCallback) (JsonPathItem *jsp, void *param); typedef Numeric (*BinaryArithmFunc) (Numeric num1, Numeric num2, bool *error); -static JsonPathExecResult executeJsonPath(JsonPath *path, Jsonb *vars, +static JsonPathExecResult executeJsonPath(JsonPath *path, void *vars, + JsonPathVarCallback getVar, Jsonb *json, bool throwErrors, JsonValueList *result, bool useTz); static JsonPathExecResult executeItem(JsonPathExecContext *cxt, @@ -225,8 +230,13 @@ static JsonPathExecResult appendBoolResult(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonValueList *found, JsonPathBool res); static void getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, JsonbValue *value); +static int GetJsonPathVar(void *vars, char *varName, int varNameLen, + JsonbValue *val, JsonbValue *baseObject); static void getJsonPathVariable(JsonPathExecContext *cxt, - JsonPathItem *variable, Jsonb *vars, JsonbValue *value); + JsonPathItem *variable, JsonbValue *value); +static int getJsonPathVariableFromJsonb(void *varsJsonb, char *varName, + int varNameLen, JsonbValue *val, + JsonbValue *baseObject); static int JsonbArraySize(JsonbValue *jb); static JsonPathBool executeComparison(JsonPathItem *cmp, JsonbValue *lv, JsonbValue *rv, void *p); @@ -284,7 +294,8 @@ jsonb_path_exists_internal(FunctionCallInfo fcinfo, bool tz) silent = PG_GETARG_BOOL(3); } - res = executeJsonPath(jp, vars, jb, !silent, NULL, tz); + res = executeJsonPath(jp, vars, getJsonPathVariableFromJsonb, + jb, !silent, NULL, tz); PG_FREE_IF_COPY(jb, 0); PG_FREE_IF_COPY(jp, 1); @@ -339,7 +350,8 @@ jsonb_path_match_internal(FunctionCallInfo fcinfo, bool tz) silent = PG_GETARG_BOOL(3); } - (void) executeJsonPath(jp, vars, jb, !silent, &found, tz); + (void) executeJsonPath(jp, vars, getJsonPathVariableFromJsonb, + jb, !silent, &found, tz); PG_FREE_IF_COPY(jb, 0); PG_FREE_IF_COPY(jp, 1); @@ -417,7 +429,8 @@ jsonb_path_query_internal(FunctionCallInfo fcinfo, bool tz) vars = PG_GETARG_JSONB_P_COPY(2); silent = PG_GETARG_BOOL(3); - (void) executeJsonPath(jp, vars, jb, !silent, &found, tz); + (void) executeJsonPath(jp, vars, getJsonPathVariableFromJsonb, + jb, !silent, &found, tz); funcctx->user_fctx = JsonValueListGetList(&found); @@ -464,7 +477,8 @@ jsonb_path_query_array_internal(FunctionCallInfo fcinfo, bool tz) Jsonb *vars = PG_GETARG_JSONB_P(2); bool silent = PG_GETARG_BOOL(3); - (void) executeJsonPath(jp, vars, jb, !silent, &found, tz); + (void) executeJsonPath(jp, vars, getJsonPathVariableFromJsonb, + jb, !silent, &found, tz); PG_RETURN_JSONB_P(JsonbValueToJsonb(wrapItemsInArray(&found))); } @@ -495,7 +509,8 @@ jsonb_path_query_first_internal(FunctionCallInfo fcinfo, bool tz) Jsonb *vars = PG_GETARG_JSONB_P(2); bool silent = PG_GETARG_BOOL(3); - (void) executeJsonPath(jp, vars, jb, !silent, &found, tz); + (void) executeJsonPath(jp, vars, getJsonPathVariableFromJsonb, + jb, !silent, &found, tz); if (JsonValueListLength(&found) >= 1) PG_RETURN_JSONB_P(JsonbValueToJsonb(JsonValueListHead(&found))); @@ -537,8 +552,9 @@ jsonb_path_query_first_tz(PG_FUNCTION_ARGS) * In other case it tries to find all the satisfied result items. */ static JsonPathExecResult -executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors, - JsonValueList *result, bool useTz) +executeJsonPath(JsonPath *path, void *vars, JsonPathVarCallback getVar, + Jsonb *json, bool throwErrors, JsonValueList *result, + bool useTz) { JsonPathExecContext cxt; JsonPathExecResult res; @@ -550,22 +566,16 @@ executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors, if (!JsonbExtractScalar(&json->root, &jbv)) JsonbInitBinary(&jbv, json); - if (vars && !JsonContainerIsObject(&vars->root)) - { - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("\"vars\" argument is not an object"), - errdetail("Jsonpath parameters should be encoded as key-value pairs of \"vars\" object."))); - } - cxt.vars = vars; + cxt.getVar = getVar; cxt.laxMode = (path->header & JSONPATH_LAX) != 0; cxt.ignoreStructuralErrors = cxt.laxMode; cxt.root = &jbv; cxt.current = &jbv; cxt.baseObject.jbc = NULL; cxt.baseObject.id = 0; - cxt.lastGeneratedObjectId = vars ? 2 : 1; + /* 1 + number of base objects in vars */ + cxt.lastGeneratedObjectId = 1 + getVar(vars, NULL, 0, NULL, NULL); cxt.innermostArraySize = -1; cxt.throwErrors = throwErrors; cxt.useTz = useTz; @@ -2108,54 +2118,118 @@ getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, &value->val.string.len); break; case jpiVariable: - getJsonPathVariable(cxt, item, cxt->vars, value); + getJsonPathVariable(cxt, item, value); return; default: elog(ERROR, "unexpected jsonpath item type"); } } +/* + * Returns the computed value of a JSON path variable with given name. + */ +static int +GetJsonPathVar(void *cxt, char *varName, int varNameLen, + JsonbValue *val, JsonbValue *baseObject) +{ + JsonPathVariable *var = NULL; + List *vars = cxt; + ListCell *lc; + int id = 1; + + if (!varName) + return list_length(vars); + + foreach(lc, vars) + { + JsonPathVariable *curvar = lfirst(lc); + + if (!strncmp(curvar->name, varName, varNameLen)) + { + var = curvar; + break; + } + + id++; + } + + if (!var) + return -1; + + if (var->isnull) + { + val->type = jbvNull; + return 0; + } + + JsonItemFromDatum(var->value, var->typid, var->typmod, val); + + *baseObject = *val; + return id; +} + /* * Get the value of variable passed to jsonpath executor */ static void getJsonPathVariable(JsonPathExecContext *cxt, JsonPathItem *variable, - Jsonb *vars, JsonbValue *value) + JsonbValue *value) { char *varName; int varNameLength; + JsonbValue baseObject; + int baseObjectId; + + Assert(variable->type == jpiVariable); + varName = jspGetString(variable, &varNameLength); + + if (!cxt->vars || + (baseObjectId = cxt->getVar(cxt->vars, varName, varNameLength, value, + &baseObject)) < 0) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("could not find jsonpath variable \"%s\"", + pnstrdup(varName, varNameLength)))); + + if (baseObjectId > 0) + setBaseObject(cxt, &baseObject, baseObjectId); +} + +static int +getJsonPathVariableFromJsonb(void *varsJsonb, char *varName, int varNameLength, + JsonbValue *value, JsonbValue *baseObject) +{ + Jsonb *vars = varsJsonb; JsonbValue tmp; JsonbValue *v; - if (!vars) + if (!varName) { - value->type = jbvNull; - return; + if (vars && !JsonContainerIsObject(&vars->root)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("\"vars\" argument is not an object"), + errdetail("Jsonpath parameters should be encoded as key-value pairs of \"vars\" object."))); + } + + return vars ? 1 : 0; /* count of base objects */ } - Assert(variable->type == jpiVariable); - varName = jspGetString(variable, &varNameLength); tmp.type = jbvString; tmp.val.string.val = varName; tmp.val.string.len = varNameLength; v = findJsonbValueFromContainer(&vars->root, JB_FOBJECT, &tmp); - if (v) - { - *value = *v; - pfree(v); - } - else - { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("could not find jsonpath variable \"%s\"", - pnstrdup(varName, varNameLength)))); - } + if (!v) + return -1; - JsonbInitBinary(&tmp, vars); - setBaseObject(cxt, &tmp, 1); + *value = *v; + pfree(v); + + JsonbInitBinary(baseObject, vars); + return 1; } /**************** Support functions for JsonPath execution *****************/ @@ -2812,3 +2886,240 @@ compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2, return DatumGetInt32(DirectFunctionCall2(cmpfunc, val1, val2)); } + +/* Executor-callable JSON_EXISTS implementation */ +bool +JsonPathExists(Datum jb, JsonPath *jp, List *vars, bool *error) +{ + JsonPathExecResult res = executeJsonPath(jp, vars, GetJsonPathVar, + DatumGetJsonbP(jb), !error, NULL, + true); + + Assert(error || !jperIsError(res)); + + if (error && jperIsError(res)) + *error = true; + + return res == jperOk; +} + +/* Executor-callable JSON_QUERY implementation */ +Datum +JsonPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper, bool *empty, + bool *error, List *vars) +{ + JsonbValue *first; + bool wrap; + JsonValueList found = {0}; + JsonPathExecResult res PG_USED_FOR_ASSERTS_ONLY; + int count; + + res = executeJsonPath(jp, vars, GetJsonPathVar, DatumGetJsonbP(jb), + !error, &found, true); + + Assert(error || !jperIsError(res)); + + if (error && jperIsError(res)) + { + *error = true; + *empty = false; + return (Datum) 0; + } + + 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) + { + if (error) + { + *error = true; + return (Datum) 0; + } + + ereport(ERROR, + (errcode(ERRCODE_MORE_THAN_ONE_SQL_JSON_ITEM), + errmsg("JSON path expression in JSON_QUERY should return singleton item without wrapper"), + errhint("Use WITH WRAPPER clause to wrap SQL/JSON item sequence into array."))); + } + + if (first) + return JsonbPGetDatum(JsonbValueToJsonb(first)); + + *empty = true; + return PointerGetDatum(NULL); +} + +/* Executor-callable JSON_VALUE implementation */ +JsonbValue * +JsonPathValue(Datum jb, JsonPath *jp, bool *empty, bool *error, List *vars) +{ + JsonbValue *res; + JsonValueList found = {0}; + JsonPathExecResult jper PG_USED_FOR_ASSERTS_ONLY; + int count; + + jper = executeJsonPath(jp, vars, GetJsonPathVar, DatumGetJsonbP(jb), + !error, &found, true); + + Assert(error || !jperIsError(jper)); + + if (error && jperIsError(jper)) + { + *error = true; + *empty = false; + return NULL; + } + + count = JsonValueListLength(&found); + + *empty = !count; + + if (*empty) + return NULL; + + if (count > 1) + { + if (error) + { + *error = true; + return NULL; + } + + ereport(ERROR, + (errcode(ERRCODE_MORE_THAN_ONE_SQL_JSON_ITEM), + errmsg("JSON path expression in JSON_VALUE should return singleton scalar item"))); + } + + res = JsonValueListHead(&found); + + if (res->type == jbvBinary && + JsonContainerIsScalar(res->val.binary.data)) + JsonbExtractScalar(res->val.binary.data, res); + + if (!IsAJsonbScalar(res)) + { + if (error) + { + *error = true; + return NULL; + } + + ereport(ERROR, + (errcode(ERRCODE_SQL_JSON_SCALAR_REQUIRED), + errmsg("JSON path expression in JSON_VALUE should return singleton scalar item"))); + } + + if (res->type == jbvNull) + return NULL; + + return res; +} + +static void +JsonbValueInitNumericDatum(JsonbValue *jbv, Datum num) +{ + jbv->type = jbvNumeric; + jbv->val.numeric = DatumGetNumeric(num); +} + +void +JsonItemFromDatum(Datum val, Oid typid, int32 typmod, JsonbValue *res) +{ + switch (typid) + { + case BOOLOID: + res->type = jbvBool; + res->val.boolean = DatumGetBool(val); + break; + case NUMERICOID: + JsonbValueInitNumericDatum(res, val); + break; + case INT2OID: + JsonbValueInitNumericDatum(res, DirectFunctionCall1(int2_numeric, val)); + break; + case INT4OID: + JsonbValueInitNumericDatum(res, DirectFunctionCall1(int4_numeric, val)); + break; + case INT8OID: + JsonbValueInitNumericDatum(res, DirectFunctionCall1(int8_numeric, val)); + break; + case FLOAT4OID: + JsonbValueInitNumericDatum(res, DirectFunctionCall1(float4_numeric, val)); + break; + case FLOAT8OID: + JsonbValueInitNumericDatum(res, DirectFunctionCall1(float8_numeric, val)); + break; + case TEXTOID: + case VARCHAROID: + res->type = jbvString; + res->val.string.val = VARDATA_ANY(val); + res->val.string.len = VARSIZE_ANY_EXHDR(val); + break; + case DATEOID: + case TIMEOID: + case TIMETZOID: + case TIMESTAMPOID: + case TIMESTAMPTZOID: + res->type = jbvDatetime; + res->val.datetime.value = val; + res->val.datetime.typid = typid; + res->val.datetime.typmod = typmod; + res->val.datetime.tz = 0; + break; + case JSONBOID: + { + JsonbValue *jbv = res; + Jsonb *jb = DatumGetJsonbP(val); + + if (JsonContainerIsScalar(&jb->root)) + { + bool result PG_USED_FOR_ASSERTS_ONLY; + + result = JsonbExtractScalar(&jb->root, jbv); + Assert(result); + } + else + JsonbInitBinary(jbv, jb); + break; + } + case JSONOID: + { + text *txt = DatumGetTextP(val); + char *str = text_to_cstring(txt); + Jsonb *jb; + + jb = DatumGetJsonbP(DirectFunctionCall1(jsonb_in, + CStringGetDatum(str))); + pfree(str); + + JsonItemFromDatum(JsonbPGetDatum(jb), JSONBOID, -1, res); + break; + } + default: + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("only datetime, bool, numeric, and text types can be casted to jsonpath types"))); + } +} diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 4ef9173c0b..a23e1f7af5 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -476,6 +476,8 @@ static void get_const_expr(Const *constval, deparse_context *context, int showtype); static void get_const_collation(Const *constval, deparse_context *context); static void get_json_format(JsonFormat *format, StringInfo buf); +static void get_json_returning(JsonReturning *returning, StringInfo buf, + bool json_format_by_default); static void get_json_constructor(JsonConstructorExpr *ctor, deparse_context *context, bool showimplicit); static void get_json_constructor_options(JsonConstructorExpr *ctor, @@ -518,6 +520,8 @@ static char *generate_qualified_type_name(Oid typid); static text *string_to_text(char *str); static char *flatten_reloptions(Oid relid); static void get_reloptions(StringInfo buf, Datum reloptions); +static void get_json_path_spec(Node *path_spec, deparse_context *context, + bool showimplicit); #define only_marker(rte) ((rte)->inh ? "" : "ONLY ") @@ -8279,6 +8283,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags) case T_WindowFunc: case T_FuncExpr: case T_JsonConstructorExpr: + case T_JsonExpr: /* function-like: name(..) or name[..] */ return true; @@ -8450,6 +8455,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags) case T_GroupingFunc: /* own parentheses */ case T_WindowFunc: /* own parentheses */ case T_CaseExpr: /* other separators */ + case T_JsonExpr: /* own parentheses */ return true; default: return false; @@ -8565,6 +8571,65 @@ get_rule_expr_paren(Node *node, deparse_context *context, appendStringInfoChar(context->buf, ')'); } +static void +get_json_behavior(JsonBehavior *behavior, deparse_context *context, + const char *on) +{ + /* + * The order of array elements must correspond to the order of + * JsonBehaviorType members. + */ + const char *behavior_names[] = + { + " NULL", + " ERROR", + " EMPTY", + " TRUE", + " FALSE", + " UNKNOWN", + " EMPTY ARRAY", + " EMPTY OBJECT", + " DEFAULT " + }; + + if ((int) behavior->btype < 0 || behavior->btype >= lengthof(behavior_names)) + elog(ERROR, "invalid json behavior type: %d", behavior->btype); + + appendStringInfoString(context->buf, behavior_names[behavior->btype]); + + if (behavior->btype == JSON_BEHAVIOR_DEFAULT) + get_rule_expr(behavior->default_expr, context, false); + + appendStringInfo(context->buf, " ON %s", on); +} + +/* + * get_json_expr_options + * + * Parse back common options for JSON_QUERY, JSON_VALUE, JSON_EXISTS. + */ +static void +get_json_expr_options(JsonExpr *jsexpr, deparse_context *context, + JsonBehaviorType default_behavior) +{ + if (jsexpr->op == JSON_QUERY_OP) + { + if (jsexpr->wrapper == JSW_CONDITIONAL) + appendStringInfo(context->buf, " WITH CONDITIONAL WRAPPER"); + else if (jsexpr->wrapper == JSW_UNCONDITIONAL) + appendStringInfo(context->buf, " WITH UNCONDITIONAL WRAPPER"); + + if (jsexpr->omit_quotes) + appendStringInfo(context->buf, " OMIT QUOTES"); + } + + if (jsexpr->op != JSON_EXISTS_OP && + jsexpr->on_empty->btype != default_behavior) + get_json_behavior(jsexpr->on_empty, context, "EMPTY"); + + if (jsexpr->on_error->btype != default_behavior) + get_json_behavior(jsexpr->on_error, context, "ERROR"); +} /* ---------- * get_rule_expr - Parse back an expression @@ -9724,6 +9789,7 @@ get_rule_expr(Node *node, deparse_context *context, } break; + case T_JsonValueExpr: { JsonValueExpr *jve = (JsonValueExpr *) node; @@ -9773,6 +9839,63 @@ get_rule_expr(Node *node, deparse_context *context, } break; + case T_JsonExpr: + { + JsonExpr *jexpr = (JsonExpr *) node; + + switch (jexpr->op) + { + case JSON_QUERY_OP: + appendStringInfoString(buf, "JSON_QUERY("); + break; + case JSON_VALUE_OP: + appendStringInfoString(buf, "JSON_VALUE("); + break; + case JSON_EXISTS_OP: + appendStringInfoString(buf, "JSON_EXISTS("); + break; + } + + get_rule_expr(jexpr->formatted_expr, context, showimplicit); + + 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", + ((String *) lfirst_node(String, lc1))->sval); + } + } + + if (jexpr->op != JSON_EXISTS_OP || + jexpr->returning->typid != BOOLOID) + get_json_returning(jexpr->returning, context->buf, + jexpr->op == JSON_QUERY_OP); + + get_json_expr_options(jexpr, context, + jexpr->op == JSON_EXISTS_OP ? + JSON_BEHAVIOR_FALSE : JSON_BEHAVIOR_NULL); + + appendStringInfoString(buf, ")"); + } + break; + case T_List: { char *sep; @@ -9896,6 +10019,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: @@ -10755,6 +10879,18 @@ get_const_collation(Const *constval, 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 node */ diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index 048573c2bc..69fb577850 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -16,12 +16,15 @@ #include "executor/nodeAgg.h" #include "nodes/execnodes.h" +#include "nodes/miscnodes.h" /* forward references to avoid circularity */ struct ExprEvalStep; struct SubscriptingRefState; struct ScalarArrayOpExprHashTable; struct JsonConstructorExprState; +struct JsonbValue; +struct JsonExprState; /* Bits in ExprState->flags (see also execnodes.h for public flag bits): */ /* expression's interpreter has been initialized */ @@ -238,6 +241,11 @@ typedef enum ExprEvalOp EEOP_XMLEXPR, EEOP_JSON_CONSTRUCTOR, EEOP_IS_JSON, + EEOP_JSONEXPR_SKIP, + EEOP_JSONEXPR_PATH, + EEOP_JSONEXPR_BEHAVIOR, + EEOP_JSONEXPR_COERCION, + EEOP_JSONEXPR_COERCION_FINISH, EEOP_AGGREF, EEOP_GROUPING_FUNC, EEOP_WINDOW_FUNC, @@ -689,6 +697,57 @@ typedef struct ExprEvalStep JsonIsPredicate *pred; /* original expression node */ } is_json; + /* for EEOP_JSONEXPR_PATH */ + struct + { + struct JsonExprState *jsestate; + } jsonexpr; + + /* for EEOP_JSONEXPR_SKIP */ + struct + { + /* Same as jsonexpr.jsestate */ + struct JsonExprState *jsestate; + + /* See ExecEvalJsonExprSkip() */ + int jump_coercion; + int jump_passing_args; + } jsonexpr_skip; + + /* for EEOP_JSONEXPR_BEHAVIOR */ + struct + { + /* Same as jsonexpr.jsestate */ + struct JsonExprState *jsestate; + + /* See ExecEvalJsonExprBehavior() */ + int jump_onerror_expr; + int jump_onempty_expr; + int jump_coercion; + int jump_skip_coercion; + } jsonexpr_behavior; + + /* for EEOP_JSONEXPR_COERCION */ + struct + { + /* Same as jsonexpr.jsestate */ + struct JsonExprState *jsestate; + + /* See ExecEvalJsonExprCoercion() */ + int jump_coercion_error; + int jump_coercion_done; + } jsonexpr_coercion; + + /* for EEOP_JSONEXPR_COERCION_FINISH */ + struct + { + /* Same as jsonexpr.jsestate */ + struct JsonExprState *jsestate; + + /* See ExecEvalJsonExprCoercion() */ + int jump_coercion_error; + int jump_coercion_done; + } jsonexpr_coercion_finish; } d; } ExprEvalStep; @@ -752,6 +811,111 @@ typedef struct JsonConstructorExprState int nargs; } JsonConstructorExprState; +/* + * Information computed before evaluating a JsonExpr expression. + */ +typedef struct JsonExprPreEvalState +{ + /* value/isnull for JsonExpr.formatted_expr */ + NullableDatum formatted_expr; + + /* value/isnull for JsonExpr.pathspec */ + NullableDatum pathspec; + + /* JsonPathVariable entries for JsonExpr.passing_values */ + List *args; +} JsonExprPreEvalState; + +/* + * State for a given member of JsonItemCoercions. + */ +typedef struct JsonCoercionState +{ + /* Expression used to evaluate the coercion */ + JsonCoercion *coercion; + + /* ExprEvalStep to compute this coercion's expression */ + int jump_eval_expr; +} JsonCoercionState; + +/* + * State for evaluating the coercion for a given JSON item using one of + * the following coercions. + * + * Note that while ExecInitExprRec() for JsonItemCoercions will initialize + * ExprEvalSteps for all of the members that need it, only one will get run + * during a given evaluation of the enclosing JsonExpr depending on the type + * of the result JSON item. + */ +typedef struct JsonItemCoercionsState +{ + JsonCoercionState *null; + JsonCoercionState *string; + JsonCoercionState *numeric; + JsonCoercionState *boolean; + JsonCoercionState *date; + JsonCoercionState *time; + JsonCoercionState *timetz; + JsonCoercionState *timestamp; + JsonCoercionState *timestamptz; + JsonCoercionState *composite; +} JsonItemCoercionsState; + +/* + * State for some post-JsonExpr-evaluation processing steps that gets filled + * in JsonExpr evaluation. + */ +typedef struct JsonExprPostEvalState +{ + /* Is JSON item empty? */ + bool empty; + + /* Did JSON item evaluation cause an error? */ + bool error; + + /* Cache for json_populate_type() called for coercion in some cases */ + void *cache; + + /* + * State for evaluating a JSON item coercion. Points to one of those in + * JsonExprState.coercions chosen by ExecPrepareJsonItemCoercion(). + */ + JsonCoercionState *item_jcstate; + bool coercion_error; + bool coercion_done; + + ErrorSaveContext escontext; +} JsonExprPostEvalState; + +/* EEOP_JSONEXPR state, too big to inline */ +typedef struct JsonExprState +{ + /* original expression node */ + JsonExpr *jsexpr; + + /* + * Should errors be thrown or handled softly? Different parts of + * evaluating a given JsonExpr may require different treatment depending + * on the JsonExprOp; see ExecEvalJsonExprBehavior(). + */ + bool throw_error; + + JsonExprPreEvalState pre_eval; + JsonExprPostEvalState post_eval; + + struct + { + FmgrInfo *finfo; /* typinput function for output type */ + Oid typioparam; + } input; /* I/O info for output type */ + + /* + * Either of the following two is used by ExecEvalJsonExprCoercion() to + * apply coercion to the final result if needed. + */ + JsonCoercionState *result_jcstate; + JsonItemCoercionsState *item_jcstates; +} JsonExprState; /* functions in execExpr.c */ extern void ExprEvalPushStep(ExprState *es, const ExprEvalStep *s); @@ -805,6 +969,14 @@ extern void ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op); extern void ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op, ExprContext *econtext); extern void ExecEvalJsonIsPredicate(ExprState *state, ExprEvalStep *op); +extern int ExecEvalJsonExprSkip(ExprState *state, ExprEvalStep *op); +extern int ExecEvalJsonExprBehavior(ExprState *state, ExprEvalStep *op); +extern int ExecEvalJsonExprCoercion(ExprState *state, ExprEvalStep *op, + ExprContext *econtext, + Datum res, bool resnull); +extern int ExecEvalJsonExprCoercionFinish(ExprState *state, ExprEvalStep *op); +extern void ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, + ExprContext *econtext); extern void ExecEvalGroupingFunc(ExprState *state, ExprEvalStep *op); extern void ExecEvalSubPlan(ExprState *state, ExprEvalStep *op, ExprContext *econtext); diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index c677e490d7..709ad967ba 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -17,6 +17,7 @@ #include "executor/execdesc.h" #include "fmgr.h" #include "nodes/lockoptions.h" +#include "nodes/miscnodes.h" #include "nodes/parsenodes.h" #include "utils/memutils.h" diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index cb714f4a19..584bf7001a 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -129,6 +129,9 @@ typedef struct ExprState Datum *innermost_domainval; bool *innermost_domainnull; + + /* ErrorSaveContext for soft-error capture. */ + Node *escontext; } ExprState; diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h index 3180703005..c1d437b44e 100644 --- a/src/include/nodes/makefuncs.h +++ b/src/include/nodes/makefuncs.h @@ -112,6 +112,7 @@ extern JsonFormat *makeJsonFormat(JsonFormatType type, JsonEncoding encoding, int location); extern JsonValueExpr *makeJsonValueExpr(Expr *raw_expr, Expr *formatted_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 item_type, bool unique_keys, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index d926713bd9..7d63a37d5f 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1727,6 +1727,12 @@ typedef enum JsonQuotes 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]) @@ -1738,6 +1744,48 @@ typedef struct JsonOutput JsonReturning *returning; /* RETURNING FORMAT clause and type Oids */ } JsonOutput; +/* + * 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; + /* * JsonKeyValue - * untransformed representation of JSON object key-value pair for diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 4104371bd9..64fdbc57fa 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -1544,6 +1544,17 @@ typedef struct XmlExpr int location; } XmlExpr; +/* + * JsonExprOp - + * enumeration of JSON functions using JSON path + */ +typedef enum JsonExprOp +{ + JSON_VALUE_OP, /* JSON_VALUE() */ + JSON_QUERY_OP, /* JSON_QUERY() */ + JSON_EXISTS_OP /* JSON_EXISTS() */ +} JsonExprOp; + /* * JsonEncoding - * representation of JSON ENCODING clause @@ -1568,6 +1579,37 @@ typedef enum JsonFormatType * jsonb */ } JsonFormatType; +/* + * JsonBehaviorType - + * enumeration of behavior types used in JSON ON ... BEHAVIOR clause + * + * If enum members are reordered, get_json_behavior() from ruleutils.c + * must be updated accordingly. + */ +typedef enum JsonBehaviorType +{ + JSON_BEHAVIOR_NULL = 0, + 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 @@ -1662,6 +1704,73 @@ typedef struct JsonIsPredicate int location; /* token location, or -1 if unknown */ } JsonIsPredicate; +/* + * JsonBehavior - + * representation of JSON ON ... BEHAVIOR clause + */ +typedef struct JsonBehavior +{ + NodeTag type; + JsonBehaviorType btype; /* behavior type */ + Node *default_expr; /* default expression, if any */ +} JsonBehavior; + +/* + * 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 *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 */ + List *passing_names; /* PASSING argument names */ + List *passing_values; /* PASSING argument values */ + 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 5984dcfa4b..0954d9fc7b 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -93,6 +93,7 @@ PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("committed", COMMITTED, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("compression", COMPRESSION, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("concurrently", CONCURRENTLY, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("conditional", CONDITIONAL, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("configuration", CONFIGURATION, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("conflict", CONFLICT, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("connection", CONNECTION, UNRESERVED_KEYWORD, BARE_LABEL) @@ -147,11 +148,13 @@ PG_KEYWORD("double", DOUBLE_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("drop", DROP, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("each", EACH, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("else", ELSE, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("empty", EMPTY_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("enable", ENABLE_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("encoding", ENCODING, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("encrypted", ENCRYPTED, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("end", END_P, RESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("enum", ENUM_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("error", ERROR_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("escape", ESCAPE, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("event", EVENT, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("except", EXCEPT, RESERVED_KEYWORD, AS_LABEL) @@ -233,10 +236,14 @@ PG_KEYWORD("join", JOIN, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("json", JSON, COL_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("json_array", JSON_ARRAY, COL_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("json_arrayagg", JSON_ARRAYAGG, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("json_exists", JSON_EXISTS, COL_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("json_object", JSON_OBJECT, COL_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("json_objectagg", JSON_OBJECTAGG, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("json_query", JSON_QUERY, COL_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("json_scalar", JSON_SCALAR, COL_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("json_serialize", JSON_SERIALIZE, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("json_value", JSON_VALUE, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("keep", KEEP, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("key", KEY, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("keys", KEYS, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("label", LABEL, UNRESERVED_KEYWORD, BARE_LABEL) @@ -302,6 +309,7 @@ PG_KEYWORD("off", OFF, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("offset", OFFSET, RESERVED_KEYWORD, AS_LABEL) PG_KEYWORD("oids", OIDS, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("old", OLD, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("omit", OMIT, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("on", ON, RESERVED_KEYWORD, AS_LABEL) PG_KEYWORD("only", ONLY, RESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("operator", OPERATOR, UNRESERVED_KEYWORD, BARE_LABEL) @@ -344,6 +352,7 @@ PG_KEYWORD("procedures", PROCEDURES, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("program", PROGRAM, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("publication", PUBLICATION, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("quote", QUOTE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("quotes", QUOTES, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("range", RANGE, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("read", READ, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("real", REAL, COL_NAME_KEYWORD, BARE_LABEL) @@ -414,6 +423,7 @@ PG_KEYWORD("stdout", STDOUT, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("storage", STORAGE, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("stored", STORED, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("strict", STRICT_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("string", STRING_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("strip", STRIP_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("subscription", SUBSCRIPTION, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("substring", SUBSTRING, COL_NAME_KEYWORD, BARE_LABEL) @@ -449,6 +459,7 @@ PG_KEYWORD("types", TYPES_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("uescape", UESCAPE, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("unbounded", UNBOUNDED, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("uncommitted", UNCOMMITTED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("unconditional", UNCONDITIONAL, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("unencrypted", UNENCRYPTED, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("union", UNION, RESERVED_KEYWORD, AS_LABEL) PG_KEYWORD("unique", UNIQUE, RESERVED_KEYWORD, BARE_LABEL) diff --git a/src/include/utils/formatting.h b/src/include/utils/formatting.h index 0cad3a2709..d4ca0f42ff 100644 --- a/src/include/utils/formatting.h +++ b/src/include/utils/formatting.h @@ -17,7 +17,6 @@ #ifndef _FORMATTING_H_ #define _FORMATTING_H_ - extern char *str_tolower(const char *buff, size_t nbytes, Oid collid); extern char *str_toupper(const char *buff, size_t nbytes, Oid collid); extern char *str_initcap(const char *buff, size_t nbytes, Oid collid); @@ -29,5 +28,6 @@ extern char *asc_initcap(const char *buff, size_t nbytes); extern Datum parse_datetime(text *date_txt, text *fmt, Oid collid, bool strict, Oid *typid, int32 *typmod, int *tz, struct Node *escontext); +extern bool datetime_format_has_tz(const char *fmt_str); #endif diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h index f928e6142a..e628d4fdd0 100644 --- a/src/include/utils/jsonb.h +++ b/src/include/utils/jsonb.h @@ -422,6 +422,9 @@ extern char *JsonbToCString(StringInfo out, JsonbContainer *in, int estimated_len); extern char *JsonbToCStringIndent(StringInfo out, JsonbContainer *in, int estimated_len); +extern Jsonb *JsonbMakeEmptyArray(void); +extern Jsonb *JsonbMakeEmptyObject(void); +extern char *JsonbUnquote(Jsonb *jb); extern bool JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res); extern const char *JsonbTypeName(JsonbValue *val); diff --git a/src/include/utils/jsonfuncs.h b/src/include/utils/jsonfuncs.h index 5ee1964096..ff5dfffb49 100644 --- a/src/include/utils/jsonfuncs.h +++ b/src/include/utils/jsonfuncs.h @@ -15,6 +15,7 @@ #define JSONFUNCS_H #include "common/jsonapi.h" +#include "nodes/nodes.h" #include "utils/jsonb.h" /* @@ -86,5 +87,9 @@ extern Datum to_json_worker(Datum val, JsonTypeCategory tcategory, Oid outfuncoid); extern Datum to_jsonb_worker(Datum val, JsonTypeCategory tcategory, Oid outfuncoid); +extern Datum json_populate_type(Datum json_val, Oid json_type, + Oid typid, int32 typmod, + void **cache, MemoryContext mcxt, bool *isnull, + Node *escontext); #endif diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index f0181e045f..4dae78a98c 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -16,7 +16,9 @@ #include "fmgr.h" #include "nodes/pg_list.h" +#include "nodes/primnodes.h" #include "utils/jsonb.h" +#include "utils/jsonfuncs.h" typedef struct { @@ -184,6 +186,7 @@ extern bool jspGetBool(JsonPathItem *v); extern char *jspGetString(JsonPathItem *v, int32 *len); extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from, JsonPathItem *to, int i); +extern bool jspIsMutable(JsonPath *path, List *varnames, List *varexprs); extern const char *jspOperationName(JsonPathItemType type); @@ -261,4 +264,28 @@ extern bool jspConvertRegexFlags(uint32 xflags, int *result, struct Node *escontext); +/* + * Evaluation of jsonpath + */ + +/* External variable passed into jsonpath. */ +typedef struct JsonPathVariable +{ + char *name; + Oid typid; + int32 typmod; + Datum value; + bool isnull; +} JsonPathVariable; + +/* SQL/JSON item */ +extern void JsonItemFromDatum(Datum val, Oid typid, int32 typmod, + JsonbValue *res); + +extern bool JsonPathExists(Datum jb, JsonPath *path, List *vars, bool *error); +extern Datum JsonPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper, + bool *empty, bool *error, List *vars); +extern JsonbValue *JsonPathValue(Datum jb, JsonPath *jp, bool *empty, + bool *error, List *vars); + #endif diff --git a/src/interfaces/ecpg/preproc/ecpg.trailer b/src/interfaces/ecpg/preproc/ecpg.trailer index 435c139ec2..b2aa44f36d 100644 --- a/src/interfaces/ecpg/preproc/ecpg.trailer +++ b/src/interfaces/ecpg/preproc/ecpg.trailer @@ -651,6 +651,34 @@ var_type: simple_type $$.type_index = mm_strdup("-1"); $$.type_sizeof = NULL; } + | STRING_P + { + if (INFORMIX_MODE) + { + /* In Informix mode, "string" is automatically a typedef */ + $$.type_enum = ECPGt_string; + $$.type_str = mm_strdup("char"); + $$.type_dimension = mm_strdup("-1"); + $$.type_index = mm_strdup("-1"); + $$.type_sizeof = NULL; + } + else + { + /* Otherwise, legal only if user typedef'ed it */ + struct typedefs *this = get_typedef("string", false); + + $$.type_str = (this->type->type_enum == ECPGt_varchar || this->type->type_enum == ECPGt_bytea) ? EMPTY : mm_strdup(this->name); + $$.type_enum = this->type->type_enum; + $$.type_dimension = this->type->type_dimension; + $$.type_index = this->type->type_index; + if (this->type->type_sizeof && strlen(this->type->type_sizeof) != 0) + $$.type_sizeof = this->type->type_sizeof; + else + $$.type_sizeof = cat_str(3, mm_strdup("sizeof("), mm_strdup(this->name), mm_strdup(")")); + + struct_member_list[struct_level] = ECPGstruct_member_dup(this->struct_member_list); + } + } | INTERVAL ecpg_interval { $$.type_enum = ECPGt_interval; diff --git a/src/test/regress/expected/json_sqljson.out b/src/test/regress/expected/json_sqljson.out new file mode 100644 index 0000000000..8b87580752 --- /dev/null +++ b/src/test/regress/expected/json_sqljson.out @@ -0,0 +1,18 @@ +-- JSON_EXISTS +SELECT JSON_EXISTS(NULL FORMAT JSON, '$'); +ERROR: JSON_EXISTS() is not yet implemented for the json type +LINE 1: SELECT JSON_EXISTS(NULL FORMAT JSON, '$'); + ^ +HINT: Try casting the argument to jsonb +-- JSON_VALUE +SELECT JSON_VALUE(NULL FORMAT JSON, '$'); +ERROR: JSON_VALUE() is not yet implemented for the json type +LINE 1: SELECT JSON_VALUE(NULL FORMAT JSON, '$'); + ^ +HINT: Try casting the argument to jsonb +-- JSON_QUERY +SELECT JSON_QUERY(NULL FORMAT JSON, '$'); +ERROR: JSON_QUERY() is not yet implemented for the json type +LINE 1: SELECT JSON_QUERY(NULL FORMAT JSON, '$'); + ^ +HINT: Try casting the argument to jsonb diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out new file mode 100644 index 0000000000..22f8679917 --- /dev/null +++ b/src/test/regress/expected/jsonb_sqljson.out @@ -0,0 +1,1042 @@ +-- 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: jsonpath member accessor can only be applied to an object +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) + +-- extension: RETURNING clause +SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING bool); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(jsonb '1', '$[1]' RETURNING bool); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING int); + json_exists +------------- + 1 +(1 row) + +SELECT JSON_EXISTS(jsonb '1', '$[1]' RETURNING int); + json_exists +------------- + 0 +(1 row) + +SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING text); + json_exists +------------- + true +(1 row) + +SELECT JSON_EXISTS(jsonb '1', '$[1]' RETURNING text); + json_exists +------------- + false +(1 row) + +SELECT JSON_EXISTS(jsonb '1', 'strict $[1]' RETURNING text FALSE ON ERROR); + json_exists +------------- + false +(1 row) + +SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING jsonb); +ERROR: cannot cast type boolean to jsonb +LINE 1: SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING jsonb); + ^ +SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING float4); +ERROR: cannot cast type boolean to real +LINE 1: SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING float4); + ^ +SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING float4 FORMAT JSON); -- RETURNING FORMAT not allowed +ERROR: cannot specify FORMAT in RETURNING clause of JSON_EXISTS() +LINE 1: ...CT JSON_EXISTS(jsonb '1', '$[0]' RETURNING float4 FORMAT JSO... + ^ +-- JSON_VALUE +SELECT JSON_VALUE(NULL::jsonb, '$'); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(jsonb 'null', '$'); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING int); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(jsonb 'true', '$'); + json_value +------------ + true +(1 row) + +SELECT JSON_VALUE(jsonb 'true', '$' RETURNING bool); + json_value +------------ + t +(1 row) + +SELECT JSON_VALUE(jsonb '123', '$'); + json_value +------------ + 123 +(1 row) + +SELECT JSON_VALUE(jsonb '123', '$' RETURNING int) + 234; + ?column? +---------- + 357 +(1 row) + +SELECT JSON_VALUE(jsonb '123', '$' RETURNING text); + json_value +------------ + 123 +(1 row) + +/* jsonb bytea ??? */ +SELECT JSON_VALUE(jsonb '123', '$' RETURNING bytea ERROR ON ERROR); +ERROR: SQL/JSON item cannot be cast to target type +SELECT JSON_VALUE(jsonb '1.23', '$'); + json_value +------------ + 1.23 +(1 row) + +SELECT JSON_VALUE(jsonb '1.23', '$' RETURNING int); + json_value +------------ + 1 +(1 row) + +SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING numeric); + json_value +------------ + 1.23 +(1 row) + +SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING int ERROR ON ERROR); +ERROR: invalid input syntax for type integer: "1.23" +SELECT JSON_VALUE(jsonb '"aaa"', '$'); + json_value +------------ + aaa +(1 row) + +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING text); + json_value +------------ + aaa +(1 row) + +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING char(5)); + json_value +------------ + aaa +(1 row) + +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING char(2)); + json_value +------------ + aa +(1 row) + +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING json); + json_value +------------ + "aaa" +(1 row) + +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING jsonb); + json_value +------------ + "aaa" +(1 row) + +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING json ERROR ON ERROR); + json_value +------------ + "aaa" +(1 row) + +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING jsonb ERROR ON ERROR); + json_value +------------ + "aaa" +(1 row) + +SELECT JSON_VALUE(jsonb '"\"aaa\""', '$' RETURNING json); + json_value +------------ + "\"aaa\"" +(1 row) + +SELECT JSON_VALUE(jsonb '"\"aaa\""', '$' RETURNING jsonb); + json_value +------------ + "\"aaa\"" +(1 row) + +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int ERROR ON ERROR); +ERROR: invalid input syntax for type integer: "aaa" +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int DEFAULT 111 ON ERROR); + json_value +------------ + 111 +(1 row) + +SELECT JSON_VALUE(jsonb '"123"', '$' RETURNING int) + 234; + ?column? +---------- + 357 +(1 row) + +SELECT JSON_VALUE(jsonb '"2017-02-20"', '$' RETURNING date) + 9; + ?column? +------------ + 03-01-2017 +(1 row) + +-- Test NULL checks execution in domain types +CREATE DOMAIN sqljsonb_int_not_null AS int NOT NULL; +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null); +ERROR: domain sqljsonb_int_not_null does not allow null values +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null NULL ON ERROR); +ERROR: domain sqljsonb_int_not_null does not allow null values +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT NULL ON ERROR); +ERROR: domain sqljsonb_int_not_null does not allow null values +SELECT JSON_VALUE(jsonb '[]', '$'); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(jsonb '[]', '$' ERROR ON ERROR); +ERROR: JSON path expression in JSON_VALUE should return singleton scalar item +SELECT JSON_VALUE(jsonb '{}', '$'); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(jsonb '{}', '$' ERROR ON ERROR); +ERROR: JSON path expression in JSON_VALUE should return singleton scalar item +SELECT JSON_VALUE(jsonb '1', '$.a'); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(jsonb '1', 'strict $.a' ERROR ON ERROR); +ERROR: jsonpath member accessor can only be applied to an object +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: JSON path expression in JSON_VALUE should return singleton scalar item +SELECT JSON_VALUE(jsonb '[1,2]', '$[*]' DEFAULT '0' ON ERROR); + json_value +------------ + 0 +(1 row) + +SELECT JSON_VALUE(jsonb '[" "]', '$[*]' RETURNING int ERROR ON ERROR); +ERROR: invalid input syntax for type integer: " " +SELECT JSON_VALUE(jsonb '[" "]', '$[*]' RETURNING int DEFAULT 2 + 3 ON ERROR); + json_value +------------ + 5 +(1 row) + +SELECT JSON_VALUE(jsonb '["1"]', '$[*]' RETURNING int DEFAULT 2 + 3 ON ERROR); + json_value +------------ + 1 +(1 row) + +SELECT JSON_VALUE(jsonb '["1"]', '$[*]' RETURNING int FORMAT JSON); -- RETURNING FORMAT not allowed +ERROR: cannot specify FORMAT in RETURNING clause of JSON_VALUE() +LINE 1: ...CT JSON_VALUE(jsonb '["1"]', '$[*]' RETURNING int FORMAT JSO... + ^ +SELECT + x, + JSON_VALUE( + jsonb '{"a": 1, "b": 2}', + '$.* ? (@ > $x)' PASSING x AS x + RETURNING int + DEFAULT -1 ON EMPTY + DEFAULT -2 ON ERROR + ) y +FROM + generate_series(0, 2) x; + x | y +---+---- + 0 | -2 + 1 | 2 + 2 | -1 +(3 rows) + +SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )' AS a); + json_value +------------ + (1,2) +(1 row) + +SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )' AS a RETURNING point); + json_value +------------ + (1,2) +(1 row) + +-- Test timestamptz passing and output +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts); + json_value +------------------------------ + Tue Feb 20 18:34:56 2018 PST +(1 row) + +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING timestamptz); + json_value +------------------------------ + Tue Feb 20 18:34:56 2018 PST +(1 row) + +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING timestamp); + json_value +-------------------------- + Tue Feb 20 18:34:56 2018 +(1 row) + +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING json); + json_value +----------------------------- + "2018-02-21T02:34:56+00:00" +(1 row) + +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING jsonb); + json_value +----------------------------- + "2018-02-21T02:34:56+00:00" +(1 row) + +-- JSON_QUERY +SELECT + JSON_QUERY(js, '$'), + JSON_QUERY(js, '$' WITHOUT WRAPPER), + JSON_QUERY(js, '$' WITH CONDITIONAL WRAPPER), + JSON_QUERY(js, '$' WITH UNCONDITIONAL ARRAY WRAPPER), + JSON_QUERY(js, '$' WITH ARRAY WRAPPER) +FROM + (VALUES + (jsonb 'null'), + ('12.3'), + ('true'), + ('"aaa"'), + ('[1, null, "2"]'), + ('{"a": 1, "b": [2]}') + ) foo(js); + json_query | json_query | json_query | json_query | json_query +--------------------+--------------------+--------------------+----------------------+---------------------- + null | null | [null] | [null] | [null] + 12.3 | 12.3 | [12.3] | [12.3] | [12.3] + true | true | [true] | [true] | [true] + "aaa" | "aaa" | ["aaa"] | ["aaa"] | ["aaa"] + [1, null, "2"] | [1, null, "2"] | [1, null, "2"] | [[1, null, "2"]] | [[1, null, "2"]] + {"a": 1, "b": [2]} | {"a": 1, "b": [2]} | {"a": 1, "b": [2]} | [{"a": 1, "b": [2]}] | [{"a": 1, "b": [2]}] +(6 rows) + +SELECT + JSON_QUERY(js, 'strict $[*]') AS "unspec", + JSON_QUERY(js, 'strict $[*]' WITHOUT WRAPPER) AS "without", + JSON_QUERY(js, 'strict $[*]' WITH CONDITIONAL WRAPPER) AS "with cond", + JSON_QUERY(js, 'strict $[*]' WITH UNCONDITIONAL ARRAY WRAPPER) AS "with uncond", + JSON_QUERY(js, 'strict $[*]' WITH ARRAY WRAPPER) AS "with" +FROM + (VALUES + (jsonb '1'), + ('[]'), + ('[null]'), + ('[12.3]'), + ('[true]'), + ('["aaa"]'), + ('[[1, 2, 3]]'), + ('[{"a": 1, "b": [2]}]'), + ('[1, "2", null, [3]]') + ) foo(js); + unspec | without | with cond | with uncond | with +--------------------+--------------------+---------------------+----------------------+---------------------- + | | | | + | | | | + null | null | [null] | [null] | [null] + 12.3 | 12.3 | [12.3] | [12.3] | [12.3] + true | true | [true] | [true] | [true] + "aaa" | "aaa" | ["aaa"] | ["aaa"] | ["aaa"] + [1, 2, 3] | [1, 2, 3] | [1, 2, 3] | [[1, 2, 3]] | [[1, 2, 3]] + {"a": 1, "b": [2]} | {"a": 1, "b": [2]} | {"a": 1, "b": [2]} | [{"a": 1, "b": [2]}] | [{"a": 1, "b": [2]}] + | | [1, "2", null, [3]] | [1, "2", null, [3]] | [1, "2", null, [3]] +(9 rows) + +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text); + json_query +------------ + "aaa" +(1 row) + +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text KEEP QUOTES); + json_query +------------ + "aaa" +(1 row) + +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text KEEP QUOTES ON SCALAR STRING); + json_query +------------ + "aaa" +(1 row) + +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text OMIT QUOTES); + json_query +------------ + aaa +(1 row) + +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text OMIT QUOTES ON SCALAR STRING); + json_query +------------ + aaa +(1 row) + +SELECT JSON_QUERY(jsonb '"aaa"', '$' OMIT QUOTES ERROR ON ERROR); +ERROR: invalid input syntax for type json +DETAIL: Token "aaa" is invalid. +CONTEXT: JSON data, line 1: aaa +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING json OMIT QUOTES ERROR ON ERROR); +ERROR: invalid input syntax for type json +DETAIL: Token "aaa" is invalid. +CONTEXT: JSON data, line 1: aaa +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING bytea FORMAT JSON OMIT QUOTES ERROR ON ERROR); + json_query +------------ + \x616161 +(1 row) + +-- QUOTES behavior should not be specified when WITH WRAPPER used: +-- Should fail +SELECT JSON_QUERY(jsonb '[1]', '$' WITH WRAPPER OMIT QUOTES); +ERROR: SQL/JSON QUOTES behavior must 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 must 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 must 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 must 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 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 '[]', '$[*]' DEFAULT '"empty"' ON EMPTY); + json_query +------------ + "empty" +(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: JSON path expression in JSON_QUERY should return singleton item without wrapper +HINT: Use WITH WRAPPER clause to wrap SQL/JSON item sequence into array. +SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' DEFAULT '"empty"' ON ERROR); + json_query +------------ + "empty" +(1 row) + +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 JSON_QUERY(jsonb '[3,4]', '$[*]' RETURNING bigint[] EMPTY OBJECT ON ERROR); +ERROR: expected JSON array +SELECT + x, y, + JSON_QUERY( + jsonb '[1,2,3,4,5,null]', + '$[*] ? (@ >= $x && @ <= $y)' + PASSING x AS x, y AS y + WITH CONDITIONAL WRAPPER + EMPTY ARRAY ON EMPTY + ) list +FROM + generate_series(0, 4) x, + generate_series(0, 4) y; + x | y | list +---+---+-------------- + 0 | 0 | [] + 0 | 1 | [1] + 0 | 2 | [1, 2] + 0 | 3 | [1, 2, 3] + 0 | 4 | [1, 2, 3, 4] + 1 | 0 | [] + 1 | 1 | [1] + 1 | 2 | [1, 2] + 1 | 3 | [1, 2, 3] + 1 | 4 | [1, 2, 3, 4] + 2 | 0 | [] + 2 | 1 | [] + 2 | 2 | [2] + 2 | 3 | [2, 3] + 2 | 4 | [2, 3, 4] + 3 | 0 | [] + 3 | 1 | [] + 3 | 2 | [] + 3 | 3 | [3] + 3 | 4 | [3, 4] + 4 | 0 | [] + 4 | 1 | [] + 4 | 2 | [] + 4 | 3 | [] + 4 | 4 | [4] +(25 rows) + +-- Extension: record types returning +CREATE TYPE sqljsonb_rec AS (a int, t text, js json, jb jsonb, jsa json[]); +CREATE TYPE sqljsonb_reca AS (reca sqljsonb_rec[]); +SELECT JSON_QUERY(jsonb '[{"a": 1, "b": "foo", "t": "aaa", "js": [1, "2", {}], "jb": {"x": [1, "2", {}]}}, {"a": 2}]', '$[0]' RETURNING sqljsonb_rec); + json_query +----------------------------------------------------- + (1,aaa,"[1, ""2"", {}]","{""x"": [1, ""2"", {}]}",) +(1 row) + +SELECT * FROM unnest((JSON_QUERY(jsonb '{"jsa": [{"a": 1, "b": ["foo"]}, {"a": 2, "c": {}}, 123]}', '$' RETURNING sqljsonb_rec)).jsa); + unnest +------------------------ + {"a": 1, "b": ["foo"]} + {"a": 2, "c": {}} + 123 +(3 rows) + +SELECT * FROM unnest((JSON_QUERY(jsonb '{"reca": [{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]}', '$' RETURNING sqljsonb_reca)).reca); + a | t | js | jb | jsa +---+-------------+----+------------+----- + 1 | ["foo", []] | | | + 2 | | | [{}, true] | +(2 rows) + +-- Extension: array types returning +SELECT JSON_QUERY(jsonb '[1,2,null,"3"]', '$[*]' RETURNING int[] WITH WRAPPER); + json_query +-------------- + {1,2,NULL,3} +(1 row) + +SELECT * FROM unnest(JSON_QUERY(jsonb '[{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]', '$' RETURNING sqljsonb_rec[])); + a | t | js | jb | jsa +---+-------------+----+------------+----- + 1 | ["foo", []] | | | + 2 | | | [{}, true] | +(2 rows) + +-- Extension: domain types returning +SELECT JSON_QUERY(jsonb '{"a": 1}', '$.a' RETURNING sqljsonb_int_not_null); + json_query +------------ + 1 +(1 row) + +SELECT JSON_QUERY(jsonb '{"a": 1}', '$.b' RETURNING sqljsonb_int_not_null); +ERROR: domain sqljsonb_int_not_null does not allow null values +-- Test timestamptz passing and output +SELECT JSON_QUERY(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts); + json_query +----------------------------- + "2018-02-21T02:34:56+00:00" +(1 row) + +SELECT JSON_QUERY(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING json); + json_query +----------------------------- + "2018-02-21T02:34:56+00:00" +(1 row) + +SELECT JSON_QUERY(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING jsonb); + json_query +----------------------------- + "2018-02-21T02:34:56+00:00" +(1 row) + +-- Test constraints +CREATE TABLE test_jsonb_constraints ( + js text, + i int, + x jsonb DEFAULT JSON_QUERY(jsonb '[1,2]', '$[*]' WITH WRAPPER) + CONSTRAINT test_jsonb_constraint1 + CHECK (js IS JSON) + CONSTRAINT test_jsonb_constraint2 + CHECK (JSON_EXISTS(js::jsonb, '$.a' PASSING i + 5 AS int, i::text AS txt, array[1,2,3] as arr)) + CONSTRAINT test_jsonb_constraint3 + CHECK (JSON_VALUE(js::jsonb, '$.a' RETURNING int DEFAULT ('12' || i)::int ON EMPTY ERROR ON ERROR) > i) + CONSTRAINT test_jsonb_constraint4 + CHECK (JSON_QUERY(js::jsonb, '$.a' WITH CONDITIONAL WRAPPER EMPTY OBJECT ON ERROR) < jsonb '[10]') + CONSTRAINT test_jsonb_constraint5 + CHECK (JSON_QUERY(js::jsonb, '$.a' RETURNING char(5) OMIT QUOTES EMPTY ARRAY ON EMPTY) > 'a' COLLATE "C") + CONSTRAINT test_jsonb_constraint6 + CHECK (JSON_EXISTS(js::jsonb, 'strict $.a' RETURNING int TRUE ON ERROR) < 2) +); +\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) +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)) + "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 EMPTY OBJECT ON ERROR) < '[10]'::jsonb) + "test_jsonb_constraint5" CHECK (JSON_QUERY(js::jsonb, '$."a"' RETURNING character(5) OMIT QUOTES EMPTY ARRAY ON EMPTY) > ('a'::bpchar COLLATE "C")) + "test_jsonb_constraint6" CHECK (JSON_EXISTS(js::jsonb, 'strict $."a"' RETURNING integer TRUE ON ERROR) < 2) + +SELECT check_clause +FROM information_schema.check_constraints +WHERE constraint_name LIKE 'test_jsonb_constraint%' +ORDER BY 1; + check_clause +-------------------------------------------------------------------------------------------------------------------------- + ((JSON_EXISTS((js)::jsonb, 'strict $."a"' RETURNING integer TRUE ON ERROR) < 2)) + ((JSON_QUERY((js)::jsonb, '$."a"' RETURNING character(5) OMIT QUOTES EMPTY ARRAY ON EMPTY) > ('a'::bpchar COLLATE "C"))) + ((JSON_QUERY((js)::jsonb, '$."a"' RETURNING jsonb WITH CONDITIONAL WRAPPER EMPTY OBJECT ON ERROR) < '[10]'::jsonb)) + ((JSON_VALUE((js)::jsonb, '$."a"' RETURNING integer DEFAULT (('12'::text || i))::integer ON EMPTY ERROR ON ERROR) > i)) + ((js IS JSON)) + (JSON_EXISTS((js)::jsonb, '$."a"' PASSING (i + 5) AS int, (i)::text AS txt, ARRAY[1, 2, 3] AS arr)) +(6 rows) + +SELECT pg_get_expr(adbin, adrelid) +FROM pg_attrdef +WHERE adrelid = 'test_jsonb_constraints'::regclass +ORDER BY 1; + pg_get_expr +-------------------------------------------------------------------------------- + JSON_QUERY('[1, 2]'::jsonb, '$[*]' RETURNING jsonb WITH UNCONDITIONAL WRAPPER) +(1 row) + +WHERE constraint_name LIKE 'test_jsonb_constraint%'; +ERROR: syntax error at or near "WHERE" +LINE 1: WHERE constraint_name LIKE 'test_jsonb_constraint%'; + ^ +SELECT pg_get_expr(adbin, adrelid) FROM pg_attrdef WHERE adrelid = 'test_jsonb_constraints'::regclass; + pg_get_expr +-------------------------------------------------------------------------------- + JSON_QUERY('[1, 2]'::jsonb, '$[*]' RETURNING jsonb WITH UNCONDITIONAL WRAPPER) +(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; +-- Test mutabilily od query functions +CREATE TABLE test_jsonb_mutability(js jsonb); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$')); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a[0]')); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime()')); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@ < $.datetime())')); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime() < $.datetime())')); +ERROR: functions in index expression must be marked IMMUTABLE +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime() < $.datetime("HH:MI TZH"))')); +ERROR: functions in index expression must be marked IMMUTABLE +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime("HH:MI TZH") < $.datetime("HH:MI TZH"))')); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime("HH:MI") < $.datetime("YY-MM-DD HH:MI"))')); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime("HH:MI TZH") < $.datetime("YY-MM-DD HH:MI"))')); +ERROR: functions in index expression must be marked IMMUTABLE +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime("HH:MI TZH") < $x' PASSING '12:34'::timetz AS x)); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime("HH:MI TZH") < $y' PASSING '12:34'::timetz AS x)); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime() < $x' PASSING '12:34'::timetz AS x)); +ERROR: functions in index expression must be marked IMMUTABLE +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime() < $x' PASSING '1234'::int AS x)); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime() ? (@ == $x)' PASSING '12:34'::time AS x)); +ERROR: functions in index expression must be marked IMMUTABLE +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime("YY-MM-DD") ? (@ == $x)' PASSING '2020-07-14'::date AS x)); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, $.a ? (@.datetime() == $x)]' PASSING '12:34'::time AS x)); +ERROR: functions in index expression must be marked IMMUTABLE +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, 0 to $.a ? (@.datetime() == $x)]' PASSING '12:34'::time AS x)); +ERROR: functions in index expression must be marked IMMUTABLE +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, $.a ? (@.datetime("HH:MI") == $x)]' PASSING '12:34'::time AS x)); +DROP TABLE test_jsonb_mutability; diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 4df9d8503b..9b0ecf049d 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -103,7 +103,7 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo # ---------- # Another group of parallel tests (JSON related) # ---------- -test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath sqljson +test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath sqljson json_sqljson jsonb_sqljson # ---------- # Another group of parallel tests diff --git a/src/test/regress/sql/json_sqljson.sql b/src/test/regress/sql/json_sqljson.sql new file mode 100644 index 0000000000..4f30fa46b9 --- /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 0000000000..b8a6148b7b --- /dev/null +++ b/src/test/regress/sql/jsonb_sqljson.sql @@ -0,0 +1,327 @@ +-- 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); + +-- extension: RETURNING clause +SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING bool); +SELECT JSON_EXISTS(jsonb '1', '$[1]' RETURNING bool); +SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING int); +SELECT JSON_EXISTS(jsonb '1', '$[1]' RETURNING int); +SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING text); +SELECT JSON_EXISTS(jsonb '1', '$[1]' RETURNING text); +SELECT JSON_EXISTS(jsonb '1', 'strict $[1]' RETURNING text FALSE ON ERROR); +SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING jsonb); +SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING float4); +SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING float4 FORMAT JSON); -- RETURNING FORMAT not allowed + + +-- JSON_VALUE + +SELECT JSON_VALUE(NULL::jsonb, '$'); + +SELECT JSON_VALUE(jsonb 'null', '$'); +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING int); + +SELECT JSON_VALUE(jsonb 'true', '$'); +SELECT JSON_VALUE(jsonb 'true', '$' RETURNING bool); + +SELECT JSON_VALUE(jsonb '123', '$'); +SELECT JSON_VALUE(jsonb '123', '$' RETURNING int) + 234; +SELECT JSON_VALUE(jsonb '123', '$' RETURNING text); +/* jsonb bytea ??? */ +SELECT JSON_VALUE(jsonb '123', '$' RETURNING bytea ERROR ON ERROR); + +SELECT JSON_VALUE(jsonb '1.23', '$'); +SELECT JSON_VALUE(jsonb '1.23', '$' RETURNING int); +SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING numeric); +SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING int ERROR ON ERROR); + +SELECT JSON_VALUE(jsonb '"aaa"', '$'); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING text); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING char(5)); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING char(2)); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING json); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING jsonb); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING json ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING jsonb ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '"\"aaa\""', '$' RETURNING json); +SELECT JSON_VALUE(jsonb '"\"aaa\""', '$' RETURNING jsonb); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int DEFAULT 111 ON ERROR); +SELECT JSON_VALUE(jsonb '"123"', '$' RETURNING int) + 234; + +SELECT JSON_VALUE(jsonb '"2017-02-20"', '$' RETURNING date) + 9; + +-- Test NULL checks execution in domain types +CREATE DOMAIN sqljsonb_int_not_null AS int NOT NULL; +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null); +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null NULL ON ERROR); +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT NULL ON ERROR); + +SELECT JSON_VALUE(jsonb '[]', '$'); +SELECT JSON_VALUE(jsonb '[]', '$' ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '{}', '$'); +SELECT JSON_VALUE(jsonb '{}', '$' ERROR ON ERROR); + +SELECT JSON_VALUE(jsonb '1', '$.a'); +SELECT JSON_VALUE(jsonb '1', 'strict $.a' ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'strict $.a' DEFAULT 'error' ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'lax $.a' ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'lax $.a' ERROR ON EMPTY ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'strict $.a' DEFAULT 2 ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT 2 ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT '2' ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'lax $.a' NULL ON EMPTY DEFAULT '2' ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT '2' ON EMPTY DEFAULT '3' ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'lax $.a' ERROR ON EMPTY DEFAULT '3' ON ERROR); + +SELECT JSON_VALUE(jsonb '[1,2]', '$[*]' ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '[1,2]', '$[*]' DEFAULT '0' ON ERROR); +SELECT JSON_VALUE(jsonb '[" "]', '$[*]' RETURNING int ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '[" "]', '$[*]' RETURNING int DEFAULT 2 + 3 ON ERROR); +SELECT JSON_VALUE(jsonb '["1"]', '$[*]' RETURNING int DEFAULT 2 + 3 ON ERROR); +SELECT JSON_VALUE(jsonb '["1"]', '$[*]' RETURNING int FORMAT JSON); -- RETURNING FORMAT not allowed + +SELECT + x, + JSON_VALUE( + jsonb '{"a": 1, "b": 2}', + '$.* ? (@ > $x)' PASSING x AS x + RETURNING int + DEFAULT -1 ON EMPTY + DEFAULT -2 ON ERROR + ) y +FROM + generate_series(0, 2) x; + +SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )' AS a); +SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )' AS a RETURNING point); + +-- Test timestamptz passing and output +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts); +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING timestamptz); +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING timestamp); +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING json); +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING jsonb); + +-- JSON_QUERY + +SELECT + JSON_QUERY(js, '$'), + JSON_QUERY(js, '$' WITHOUT WRAPPER), + JSON_QUERY(js, '$' WITH CONDITIONAL WRAPPER), + JSON_QUERY(js, '$' WITH UNCONDITIONAL ARRAY WRAPPER), + JSON_QUERY(js, '$' WITH ARRAY WRAPPER) +FROM + (VALUES + (jsonb 'null'), + ('12.3'), + ('true'), + ('"aaa"'), + ('[1, null, "2"]'), + ('{"a": 1, "b": [2]}') + ) foo(js); + +SELECT + JSON_QUERY(js, 'strict $[*]') AS "unspec", + JSON_QUERY(js, 'strict $[*]' WITHOUT WRAPPER) AS "without", + JSON_QUERY(js, 'strict $[*]' WITH CONDITIONAL WRAPPER) AS "with cond", + JSON_QUERY(js, 'strict $[*]' WITH UNCONDITIONAL ARRAY WRAPPER) AS "with uncond", + JSON_QUERY(js, 'strict $[*]' WITH ARRAY WRAPPER) AS "with" +FROM + (VALUES + (jsonb '1'), + ('[]'), + ('[null]'), + ('[12.3]'), + ('[true]'), + ('["aaa"]'), + ('[[1, 2, 3]]'), + ('[{"a": 1, "b": [2]}]'), + ('[1, "2", null, [3]]') + ) foo(js); + +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text); +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text KEEP QUOTES); +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text KEEP QUOTES ON SCALAR STRING); +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text OMIT QUOTES); +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text OMIT QUOTES ON SCALAR STRING); +SELECT JSON_QUERY(jsonb '"aaa"', '$' OMIT QUOTES ERROR ON ERROR); +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING json OMIT QUOTES ERROR ON ERROR); +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING bytea FORMAT JSON OMIT QUOTES ERROR ON ERROR); + +-- QUOTES behavior should not be specified when WITH WRAPPER used: +-- Should fail +SELECT JSON_QUERY(jsonb '[1]', '$' WITH WRAPPER OMIT QUOTES); +SELECT JSON_QUERY(jsonb '[1]', '$' WITH WRAPPER KEEP QUOTES); +SELECT JSON_QUERY(jsonb '[1]', '$' WITH CONDITIONAL WRAPPER KEEP QUOTES); +SELECT JSON_QUERY(jsonb '[1]', '$' WITH CONDITIONAL WRAPPER OMIT QUOTES); +-- Should succeed +SELECT JSON_QUERY(jsonb '[1]', '$' WITHOUT WRAPPER OMIT QUOTES); +SELECT JSON_QUERY(jsonb '[1]', '$' WITHOUT WRAPPER KEEP QUOTES); + +SELECT JSON_QUERY(jsonb '[]', '$[*]'); +SELECT JSON_QUERY(jsonb '[]', '$[*]' NULL ON EMPTY); +SELECT JSON_QUERY(jsonb '[]', '$[*]' EMPTY 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 '[]', '$[*]' DEFAULT '"empty"' 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]', '$[*]' DEFAULT '"empty"' 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 JSON_QUERY(jsonb '[3,4]', '$[*]' RETURNING bigint[] EMPTY OBJECT ON ERROR); + +SELECT + x, y, + JSON_QUERY( + jsonb '[1,2,3,4,5,null]', + '$[*] ? (@ >= $x && @ <= $y)' + PASSING x AS x, y AS y + WITH CONDITIONAL WRAPPER + EMPTY ARRAY ON EMPTY + ) list +FROM + generate_series(0, 4) x, + generate_series(0, 4) y; + +-- Extension: record types returning +CREATE TYPE sqljsonb_rec AS (a int, t text, js json, jb jsonb, jsa json[]); +CREATE TYPE sqljsonb_reca AS (reca sqljsonb_rec[]); + +SELECT JSON_QUERY(jsonb '[{"a": 1, "b": "foo", "t": "aaa", "js": [1, "2", {}], "jb": {"x": [1, "2", {}]}}, {"a": 2}]', '$[0]' RETURNING sqljsonb_rec); +SELECT * FROM unnest((JSON_QUERY(jsonb '{"jsa": [{"a": 1, "b": ["foo"]}, {"a": 2, "c": {}}, 123]}', '$' RETURNING sqljsonb_rec)).jsa); +SELECT * FROM unnest((JSON_QUERY(jsonb '{"reca": [{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]}', '$' RETURNING sqljsonb_reca)).reca); + +-- Extension: array types returning +SELECT JSON_QUERY(jsonb '[1,2,null,"3"]', '$[*]' RETURNING int[] WITH WRAPPER); +SELECT * FROM unnest(JSON_QUERY(jsonb '[{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]', '$' RETURNING sqljsonb_rec[])); + +-- Extension: domain types returning +SELECT JSON_QUERY(jsonb '{"a": 1}', '$.a' RETURNING sqljsonb_int_not_null); +SELECT JSON_QUERY(jsonb '{"a": 1}', '$.b' RETURNING sqljsonb_int_not_null); + +-- Test timestamptz passing and output +SELECT JSON_QUERY(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts); +SELECT JSON_QUERY(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING json); +SELECT JSON_QUERY(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING jsonb); + +-- Test constraints + +CREATE TABLE test_jsonb_constraints ( + js text, + i int, + x jsonb DEFAULT JSON_QUERY(jsonb '[1,2]', '$[*]' WITH WRAPPER) + CONSTRAINT test_jsonb_constraint1 + CHECK (js IS JSON) + CONSTRAINT test_jsonb_constraint2 + CHECK (JSON_EXISTS(js::jsonb, '$.a' PASSING i + 5 AS int, i::text AS txt, array[1,2,3] as arr)) + CONSTRAINT test_jsonb_constraint3 + CHECK (JSON_VALUE(js::jsonb, '$.a' RETURNING int DEFAULT ('12' || i)::int ON EMPTY ERROR ON ERROR) > i) + CONSTRAINT test_jsonb_constraint4 + CHECK (JSON_QUERY(js::jsonb, '$.a' WITH CONDITIONAL WRAPPER EMPTY OBJECT ON ERROR) < jsonb '[10]') + CONSTRAINT test_jsonb_constraint5 + CHECK (JSON_QUERY(js::jsonb, '$.a' RETURNING char(5) OMIT QUOTES EMPTY ARRAY ON EMPTY) > 'a' COLLATE "C") + CONSTRAINT test_jsonb_constraint6 + CHECK (JSON_EXISTS(js::jsonb, 'strict $.a' RETURNING int TRUE ON ERROR) < 2) +); + +\d test_jsonb_constraints + +SELECT check_clause +FROM information_schema.check_constraints +WHERE constraint_name LIKE 'test_jsonb_constraint%' +ORDER BY 1; + +SELECT pg_get_expr(adbin, adrelid) +FROM pg_attrdef +WHERE adrelid = 'test_jsonb_constraints'::regclass +ORDER BY 1; +WHERE constraint_name LIKE 'test_jsonb_constraint%'; + +SELECT pg_get_expr(adbin, adrelid) FROM pg_attrdef WHERE adrelid = 'test_jsonb_constraints'::regclass; + +INSERT INTO test_jsonb_constraints VALUES ('', 1); +INSERT INTO test_jsonb_constraints VALUES ('1', 1); +INSERT INTO test_jsonb_constraints VALUES ('[]'); +INSERT INTO test_jsonb_constraints VALUES ('{"b": 1}', 1); +INSERT INTO test_jsonb_constraints VALUES ('{"a": 1}', 1); +INSERT INTO test_jsonb_constraints VALUES ('{"a": 7}', 1); +INSERT INTO test_jsonb_constraints VALUES ('{"a": 10}', 1); + +DROP TABLE test_jsonb_constraints; + +-- Test mutabilily od query functions +CREATE TABLE test_jsonb_mutability(js jsonb); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$')); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a[0]')); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime()')); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@ < $.datetime())')); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime() < $.datetime())')); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime() < $.datetime("HH:MI TZH"))')); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime("HH:MI TZH") < $.datetime("HH:MI TZH"))')); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime("HH:MI") < $.datetime("YY-MM-DD HH:MI"))')); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime("HH:MI TZH") < $.datetime("YY-MM-DD HH:MI"))')); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime("HH:MI TZH") < $x' PASSING '12:34'::timetz AS x)); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime("HH:MI TZH") < $y' PASSING '12:34'::timetz AS x)); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime() < $x' PASSING '12:34'::timetz AS x)); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime() < $x' PASSING '1234'::int AS x)); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime() ? (@ == $x)' PASSING '12:34'::time AS x)); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime("YY-MM-DD") ? (@ == $x)' PASSING '2020-07-14'::date AS x)); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, $.a ? (@.datetime() == $x)]' PASSING '12:34'::time AS x)); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, 0 to $.a ? (@.datetime() == $x)]' PASSING '12:34'::time AS x)); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, $.a ? (@.datetime("HH:MI") == $x)]' PASSING '12:34'::time AS x)); +DROP TABLE test_jsonb_mutability; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8efc61cb55..211a223193 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1254,14 +1254,24 @@ JsonArrayAgg JsonArrayConstructor JsonArrayQueryConstructor JsonBaseObjectInfo +JsonBehavior +JsonBehaviorType +JsonCoercion +JsonCoercionState +JsonCommon JsonConstructorExpr JsonConstructorExprState JsonConstructorType JsonEncoding +JsonExpr +JsonExprOp +JsonExprState JsonFormat JsonFormatType JsonHashEntry JsonIsPredicate +JsonItemCoercions +JsonItemCoercionsState JsonIterateStringValuesAction JsonKeyValue JsonLexContext @@ -1294,6 +1304,10 @@ JsonPathParseItem JsonPathParseResult JsonPathPredicateCallback JsonPathString +JsonPathVarCallback +JsonPathVariable +JsonPathVariableEvalContext +JsonQuotes JsonReturning JsonSemAction JsonSerializeExpr -- 2.35.3