From 54464cbb1c588a364c8e61312ad5b848a4bc9fbe Mon Sep 17 00:00:00 2001 From: Amit Langote Date: Fri, 16 Jun 2023 17:49:18 +0900 Subject: [PATCH v2 4/6] SQL/JSON query functions 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. Nikita Glukhov 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. 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 --- 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 | 350 ++++++- src/backend/parser/parse_collate.c | 7 + src/backend/parser/parse_expr.c | 540 +++++++++- 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 | 6 + 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 | 1034 +++++++++++++++++++ src/test/regress/parallel_schedule | 2 +- src/test/regress/sql/json_sqljson.sql | 11 + src/test/regress/sql/jsonb_sqljson.sql | 325 ++++++ src/tools/pgindent/typedefs.list | 14 + 37 files changed, 5239 insertions(+), 102 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 8de8f527a5..e7a018583a 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -16995,6 +16995,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 95d9fbbc7c..dbf2e692ee 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -50,6 +50,7 @@ #include "utils/datum.h" #include "utils/json.h" #include "utils/jsonb.h" +#include "utils/jsonpath.h" #include "utils/lsyscache.h" #include "utils/typcache.h" @@ -89,6 +90,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); /* @@ -2428,6 +2439,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; @@ -4195,3 +4214,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 b83dd1c666..4b0647153a 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), @@ -4137,6 +4184,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 39e1884cf4..5dc3aa2e9f 100644 --- a/src/backend/nodes/makefuncs.c +++ b/src/backend/nodes/makefuncs.c @@ -859,6 +859,21 @@ makeJsonValueExpr(Expr *expr, JsonFormat *format) return jve; } +/* + * makeJsonBehavior - + * creates a JsonBehavior node + */ +JsonBehavior * +makeJsonBehavior(JsonBehaviorType type, Node *default_expr) +{ + JsonBehavior *behavior = makeNode(JsonBehavior); + + behavior->btype = type; + behavior->default_expr = default_expr; + + return behavior; +} + /* * makeJsonEncoding - * converts JSON encoding name to enum JsonEncoding diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index dda964bd19..d31371bb4d 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -236,6 +236,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; @@ -495,6 +501,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: @@ -971,6 +981,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 */ @@ -1207,6 +1233,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 */ @@ -1510,6 +1551,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; @@ -2262,6 +2312,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: @@ -3261,6 +3359,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; @@ -3947,6 +4093,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 59d8ce789e..2dacb83052 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 8f239de65c..4f5c44e063 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 @@ -652,13 +654,23 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); json_output_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_value_behavior + json_query_behavior + json_exists_behavior + +%type json_quotes_clause_opt %type json_key_uniqueness_constraint_opt json_object_constructor_null_clause_opt @@ -699,7 +711,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 @@ -710,8 +722,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 @@ -726,10 +738,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 @@ -743,7 +755,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 @@ -752,7 +764,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 @@ -763,7 +775,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 @@ -771,7 +783,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 @@ -15668,6 +15680,192 @@ func_expr_common_subexpr: n->location = @1; $$ = (Node *) n; } + | JSON_QUERY '(' + json_api_common_syntax + json_output_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_output_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_output_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_output_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; + } ; @@ -16394,6 +16592,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 { @@ -16417,6 +16681,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 { @@ -17033,6 +17341,7 @@ unreserved_keyword: | COMMIT | COMMITTED | COMPRESSION + | CONDITIONAL | CONFIGURATION | CONFLICT | CONNECTION @@ -17069,10 +17378,12 @@ unreserved_keyword: | DOUBLE_P | DROP | EACH + | EMPTY_P | ENABLE_P | ENCODING | ENCRYPTED | ENUM_P + | ERROR_P | ESCAPE | EVENT | EXCLUDE @@ -17122,6 +17433,7 @@ unreserved_keyword: | INSTEAD | INVOKER | ISOLATION + | KEEP | KEY | KEYS | LABEL @@ -17168,6 +17480,7 @@ unreserved_keyword: | OFF | OIDS | OLD + | OMIT | OPERATOR | OPTION | OPTIONS @@ -17198,6 +17511,7 @@ unreserved_keyword: | PROGRAM | PUBLICATION | QUOTE + | QUOTES | RANGE | READ | REASSIGN @@ -17257,6 +17571,7 @@ unreserved_keyword: | STORAGE | STORED | STRICT_P + | STRING_P | STRIP_P | SUBSCRIPTION | SUPPORT @@ -17279,6 +17594,7 @@ unreserved_keyword: | UESCAPE | UNBOUNDED | UNCOMMITTED + | UNCONDITIONAL | UNENCRYPTED | UNKNOWN | UNLISTEN @@ -17339,10 +17655,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 @@ -17575,6 +17894,7 @@ bare_label_keyword: | COMMITTED | COMPRESSION | CONCURRENTLY + | CONDITIONAL | CONFIGURATION | CONFLICT | CONNECTION @@ -17627,11 +17947,13 @@ bare_label_keyword: | DROP | EACH | ELSE + | EMPTY_P | ENABLE_P | ENCODING | ENCRYPTED | END_P | ENUM_P + | ERROR_P | ESCAPE | EVENT | EXCLUDE @@ -17701,10 +18023,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 @@ -17765,6 +18091,7 @@ bare_label_keyword: | OFF | OIDS | OLD + | OMIT | ONLY | OPERATOR | OPTION @@ -17802,6 +18129,7 @@ bare_label_keyword: | PROGRAM | PUBLICATION | QUOTE + | QUOTES | RANGE | READ | REAL @@ -17870,6 +18198,7 @@ bare_label_keyword: | STORAGE | STORED | STRICT_P + | STRING_P | STRIP_P | SUBSCRIPTION | SUBSTRING @@ -17904,6 +18233,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 b46e06237b..f6a7ba5ef5 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -90,6 +90,8 @@ static Node *transformJsonParseExpr(ParseState *pstate, JsonParseExpr *expr); static Node *transformJsonScalarExpr(ParseState *pstate, JsonScalarExpr *expr); static Node *transformJsonSerializeExpr(ParseState *pstate, JsonSerializeExpr * expr); +static Node *transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve); +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 +355,14 @@ transformExprRecurse(ParseState *pstate, Node *expr) result = transformJsonSerializeExpr(pstate, (JsonSerializeExpr *) expr); break; + case T_JsonFuncExpr: + result = transformJsonFuncExpr(pstate, (JsonFuncExpr *) expr); + break; + + case T_JsonValueExpr: + result = transformJsonValueExpr(pstate, (JsonValueExpr *) expr); + break; + default: /* should not reach here */ elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); @@ -3238,10 +3248,10 @@ makeCaseTestExpr(Node *expr) * default format otherwise. */ static Node * -transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve, - char *constructName, - JsonFormatType default_format, - Oid targettype) +transformJsonValueExprInternal(ParseState *pstate, JsonValueExpr *ve, + char *constructName, + JsonFormatType default_format, + Oid targettype, bool isarg) { Node *expr = transformExprRecurse(pstate, (Node *) ve->raw_expr); Node *rawexpr; @@ -3278,6 +3288,35 @@ transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve, 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 @@ -3289,7 +3328,8 @@ transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve, 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), @@ -3354,6 +3394,20 @@ transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve, return expr; } +/* Wrapper with an interface for use in transformExpr() */ +static Node * +transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve) +{ + /* + * A bit bogus to use "JSON()" here for the parent construct's name but + * there's no good way to know the enclosing JSON expression when called + * through transformExpr(). + */ + return transformJsonValueExprInternal(pstate, ve, "JSON()", + JS_FORMAT_DEFAULT, + InvalidOid, false); +} + /* * Checks specified output format for its applicability to the target type. */ @@ -3613,10 +3667,12 @@ transformJsonObjectConstructor(ParseState *pstate, JsonObjectConstructor *ctor) { JsonKeyValue *kv = castNode(JsonKeyValue, lfirst(lc)); Node *key = transformExprRecurse(pstate, (Node *) kv->key); - Node *val = transformJsonValueExpr(pstate, kv->value, - "JSON_OBJECT()", - JS_FORMAT_DEFAULT, - InvalidOid); + Node *val = transformJsonValueExprInternal(pstate, + kv->value, + "JSON_OBJECT()", + JS_FORMAT_DEFAULT, + InvalidOid, + false); args = lappend(args, key); args = lappend(args, val); @@ -3795,8 +3851,10 @@ transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg) Oid aggtype; key = transformExprRecurse(pstate, (Node *) agg->arg->key); - val = transformJsonValueExpr(pstate, agg->arg->value, "JSON_OBJECTAGG()", - JS_FORMAT_DEFAULT, InvalidOid); + val = transformJsonValueExprInternal(pstate, agg->arg->value, + "JSON_OBJECTAGG()", + JS_FORMAT_DEFAULT, + InvalidOid, false); args = list_make2(key, val); returning = transformJsonConstructorOutput(pstate, agg->constructor->output, @@ -3852,8 +3910,8 @@ transformJsonArrayAgg(ParseState *pstate, JsonArrayAgg *agg) Oid aggfnoid; Oid aggtype; - arg = transformJsonValueExpr(pstate, agg->arg, "JSON_ARRAYAGG()", - JS_FORMAT_DEFAULT, InvalidOid); + arg = transformJsonValueExprInternal(pstate, agg->arg, "JSON_ARRAYAGG()", + JS_FORMAT_DEFAULT, InvalidOid, false); returning = transformJsonConstructorOutput(pstate, agg->constructor->output, list_make1(arg)); @@ -3899,10 +3957,10 @@ transformJsonArrayConstructor(ParseState *pstate, JsonArrayConstructor *ctor) foreach(lc, ctor->exprs) { JsonValueExpr *jsval = castNode(JsonValueExpr, lfirst(lc)); - Node *val = transformJsonValueExpr(pstate, jsval, - "JSON_ARRAY()", - JS_FORMAT_DEFAULT, - InvalidOid); + Node *val = transformJsonValueExprInternal(pstate, jsval, + "JSON_ARRAY()", + JS_FORMAT_DEFAULT, + InvalidOid, false); args = lappend(args, val); } @@ -4057,8 +4115,9 @@ transformJsonParseExpr(ParseState *pstate, JsonParseExpr *jsexpr) * Coerce argument to target type using CAST for compatibility with PG * function-like CASTs. */ - arg = transformJsonValueExpr(pstate, jsexpr->expr, "JSON()", - JS_FORMAT_JSON, returning->typid); + arg = transformJsonValueExprInternal(pstate, jsexpr->expr, "JSON()", + JS_FORMAT_JSON, returning->typid, + false); } return makeJsonConstructorExpr(pstate, JSCTOR_JSON_PARSE, list_make1(arg), NULL, @@ -4089,10 +4148,10 @@ transformJsonScalarExpr(ParseState *pstate, JsonScalarExpr *jsexpr) static Node * transformJsonSerializeExpr(ParseState *pstate, JsonSerializeExpr * expr) { - Node *arg = transformJsonValueExpr(pstate, expr->expr, - "JSON_SERIALIZE()", - JS_FORMAT_JSON, - InvalidOid); + Node *arg = transformJsonValueExprInternal(pstate, expr->expr, + "JSON_SERIALIZE()", + JS_FORMAT_JSON, + InvalidOid, false); JsonReturning *returning; if (expr->output) @@ -4127,3 +4186,438 @@ 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, 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 = transformJsonValueExprInternal(pstate, arg->val, + "JSON PASSING()", + 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 = transformJsonValueExprInternal(pstate, + func->common->expr, + constructName, + 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, 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) + { + Node *placeholder = makeCaseTestExpr(expr); + + Assert(((CaseTestExpr *) placeholder)->typeId == ret.typid); + Assert(((CaseTestExpr *) placeholder)->typeMod == ret.typmod); + + jsexpr->result_coercion = coerceJsonExpr(pstate, placeholder, + jsexpr->returning); + } + } + else + assignDefaultJsonReturningType(jsexpr->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; + + 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 e6246dc44b..f6015debbb 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -4444,6 +4444,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 7a91177a55..100dfa9c3a 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -2265,3 +2265,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 70cb922e6b..cbc6b94fc2 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -264,6 +264,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() */ @@ -441,12 +442,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); @@ -458,7 +460,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); @@ -2483,12 +2486,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"))); } @@ -2505,13 +2508,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.", @@ -2519,7 +2522,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) { @@ -2530,6 +2537,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); @@ -2547,12 +2558,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; @@ -2572,7 +2587,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); @@ -2593,6 +2611,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; } @@ -2610,6 +2632,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; } @@ -2685,6 +2711,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 */ @@ -2714,7 +2744,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); @@ -2739,10 +2775,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); @@ -2761,7 +2802,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; @@ -2779,6 +2825,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); @@ -2796,12 +2845,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; @@ -2816,6 +2871,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, @@ -2824,7 +2880,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); @@ -2841,6 +2906,7 @@ populate_array(ArrayIOData *aio, pfree(ctx.sizes); pfree(lbs); + *isnull = false; return result; } @@ -2956,7 +3022,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; @@ -3027,7 +3094,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) @@ -3053,7 +3124,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); } @@ -3158,7 +3229,8 @@ populate_record_field(ColumnIOData *col, MemoryContext mcxt, Datum defaultval, JsValue *jsv, - bool *isnull) + bool *isnull, + Node *escontext) { TypeCat typcat; @@ -3191,10 +3263,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: @@ -3215,6 +3289,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) { @@ -3356,7 +3477,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 d1b03d6cb2..d1ec418ccf 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 ac02247947..089d1c5750 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 06d991b725..5e149b0266 100644 --- a/src/include/nodes/makefuncs.h +++ b/src/include/nodes/makefuncs.h @@ -111,6 +111,7 @@ extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_ extern JsonFormat *makeJsonFormat(JsonFormatType type, JsonEncoding encoding, int location); extern JsonValueExpr *makeJsonValueExpr(Expr *expr, JsonFormat *format); +extern 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 06a124a86c..bba2920aba 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1728,6 +1728,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]) @@ -1739,6 +1745,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 6c6a1810af..0e5a160c90 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -1542,6 +1542,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 @@ -1566,6 +1577,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 @@ -1656,6 +1698,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 8417a74298..d9e28d14ce 100644 --- a/src/include/utils/jsonb.h +++ b/src/include/utils/jsonb.h @@ -439,6 +439,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 a85203d4a4..cd9cc1480e 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" /* @@ -63,4 +64,9 @@ extern Jsonb *transform_jsonb_string_values(Jsonb *jsonb, void *action_state, extern text *transform_json_string_values(text *json, void *action_state, JsonTransformStringValuesAction transform_action); +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..7eb8faf487 --- /dev/null +++ b/src/test/regress/expected/jsonb_sqljson.out @@ -0,0 +1,1034 @@ +-- 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); + ^ +-- 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 + 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 cf46fa3359..cb3230f4dd 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..963129699b --- /dev/null +++ b/src/test/regress/sql/jsonb_sqljson.sql @@ -0,0 +1,325 @@ +-- 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); + + +-- JSON_VALUE + +SELECT JSON_VALUE(NULL::jsonb, '$'); + +SELECT JSON_VALUE(jsonb 'null', '$'); +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING int); + +SELECT JSON_VALUE(jsonb 'true', '$'); +SELECT JSON_VALUE(jsonb 'true', '$' RETURNING bool); + +SELECT JSON_VALUE(jsonb '123', '$'); +SELECT JSON_VALUE(jsonb '123', '$' RETURNING int) + 234; +SELECT JSON_VALUE(jsonb '123', '$' RETURNING text); +/* jsonb bytea ??? */ +SELECT JSON_VALUE(jsonb '123', '$' RETURNING bytea ERROR ON ERROR); + +SELECT JSON_VALUE(jsonb '1.23', '$'); +SELECT JSON_VALUE(jsonb '1.23', '$' RETURNING int); +SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING numeric); +SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING int ERROR ON ERROR); + +SELECT JSON_VALUE(jsonb '"aaa"', '$'); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING text); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING char(5)); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING char(2)); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING json); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING jsonb); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING json ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING jsonb ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '"\"aaa\""', '$' RETURNING json); +SELECT JSON_VALUE(jsonb '"\"aaa\""', '$' RETURNING jsonb); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int DEFAULT 111 ON ERROR); +SELECT JSON_VALUE(jsonb '"123"', '$' RETURNING int) + 234; + +SELECT JSON_VALUE(jsonb '"2017-02-20"', '$' RETURNING date) + 9; + +-- Test NULL checks execution in domain types +CREATE DOMAIN sqljsonb_int_not_null AS int NOT NULL; +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null); +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null NULL ON ERROR); +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT NULL ON ERROR); + +SELECT JSON_VALUE(jsonb '[]', '$'); +SELECT JSON_VALUE(jsonb '[]', '$' ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '{}', '$'); +SELECT JSON_VALUE(jsonb '{}', '$' ERROR ON ERROR); + +SELECT JSON_VALUE(jsonb '1', '$.a'); +SELECT JSON_VALUE(jsonb '1', 'strict $.a' ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'strict $.a' DEFAULT 'error' ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'lax $.a' ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'lax $.a' ERROR ON EMPTY ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'strict $.a' DEFAULT 2 ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT 2 ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT '2' ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'lax $.a' NULL ON EMPTY DEFAULT '2' ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT '2' ON EMPTY DEFAULT '3' ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'lax $.a' ERROR ON EMPTY DEFAULT '3' ON ERROR); + +SELECT JSON_VALUE(jsonb '[1,2]', '$[*]' ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '[1,2]', '$[*]' DEFAULT '0' ON ERROR); +SELECT JSON_VALUE(jsonb '[" "]', '$[*]' RETURNING int ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '[" "]', '$[*]' RETURNING int DEFAULT 2 + 3 ON ERROR); +SELECT JSON_VALUE(jsonb '["1"]', '$[*]' RETURNING int DEFAULT 2 + 3 ON ERROR); + +SELECT + x, + JSON_VALUE( + jsonb '{"a": 1, "b": 2}', + '$.* ? (@ > $x)' PASSING x AS x + RETURNING int + DEFAULT -1 ON EMPTY + DEFAULT -2 ON ERROR + ) y +FROM + generate_series(0, 2) x; + +SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )' AS a); +SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )' AS a RETURNING point); + +-- Test timestamptz passing and output +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts); +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING timestamptz); +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING timestamp); +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING json); +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING jsonb); + +-- JSON_QUERY + +SELECT + JSON_QUERY(js, '$'), + JSON_QUERY(js, '$' WITHOUT WRAPPER), + JSON_QUERY(js, '$' WITH CONDITIONAL WRAPPER), + JSON_QUERY(js, '$' WITH UNCONDITIONAL ARRAY WRAPPER), + JSON_QUERY(js, '$' WITH ARRAY WRAPPER) +FROM + (VALUES + (jsonb 'null'), + ('12.3'), + ('true'), + ('"aaa"'), + ('[1, null, "2"]'), + ('{"a": 1, "b": [2]}') + ) foo(js); + +SELECT + JSON_QUERY(js, 'strict $[*]') AS "unspec", + JSON_QUERY(js, 'strict $[*]' WITHOUT WRAPPER) AS "without", + JSON_QUERY(js, 'strict $[*]' WITH CONDITIONAL WRAPPER) AS "with cond", + JSON_QUERY(js, 'strict $[*]' WITH UNCONDITIONAL ARRAY WRAPPER) AS "with uncond", + JSON_QUERY(js, 'strict $[*]' WITH ARRAY WRAPPER) AS "with" +FROM + (VALUES + (jsonb '1'), + ('[]'), + ('[null]'), + ('[12.3]'), + ('[true]'), + ('["aaa"]'), + ('[[1, 2, 3]]'), + ('[{"a": 1, "b": [2]}]'), + ('[1, "2", null, [3]]') + ) foo(js); + +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text); +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text KEEP QUOTES); +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text KEEP QUOTES ON SCALAR STRING); +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text OMIT QUOTES); +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text OMIT QUOTES ON SCALAR STRING); +SELECT JSON_QUERY(jsonb '"aaa"', '$' OMIT QUOTES ERROR ON ERROR); +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING json OMIT QUOTES ERROR ON ERROR); +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING bytea FORMAT JSON OMIT QUOTES ERROR ON ERROR); + +-- QUOTES behavior should not be specified when WITH WRAPPER used: +-- Should fail +SELECT JSON_QUERY(jsonb '[1]', '$' WITH WRAPPER OMIT QUOTES); +SELECT JSON_QUERY(jsonb '[1]', '$' WITH WRAPPER KEEP QUOTES); +SELECT JSON_QUERY(jsonb '[1]', '$' WITH CONDITIONAL WRAPPER KEEP QUOTES); +SELECT JSON_QUERY(jsonb '[1]', '$' WITH CONDITIONAL WRAPPER OMIT QUOTES); +-- Should succeed +SELECT JSON_QUERY(jsonb '[1]', '$' WITHOUT WRAPPER OMIT QUOTES); +SELECT JSON_QUERY(jsonb '[1]', '$' WITHOUT WRAPPER KEEP QUOTES); + +SELECT JSON_QUERY(jsonb '[]', '$[*]'); +SELECT JSON_QUERY(jsonb '[]', '$[*]' NULL ON EMPTY); +SELECT JSON_QUERY(jsonb '[]', '$[*]' EMPTY 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 7d60511e9e..336b7faa36 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