From 93a0c1e15bf4956c76e8ba1ef20cb79f71073dc2 Mon Sep 17 00:00:00 2001 From: Amit Langote Date: Tue, 12 Mar 2024 13:20:34 +0900 Subject: [PATCH v43 2/3] Many fixes and refactoring --- doc/src/sgml/func.sgml | 14 +- src/backend/executor/execExpr.c | 259 +++++------- src/backend/executor/execExprInterp.c | 255 +++++------- src/backend/jit/llvm/llvmjit_expr.c | 82 +--- src/backend/nodes/makefuncs.c | 6 +- src/backend/nodes/nodeFuncs.c | 119 +----- src/backend/parser/gram.y | 4 +- src/backend/parser/parse_expr.c | 394 +++++++----------- src/backend/parser/parse_target.c | 3 + src/backend/utils/adt/ruleutils.c | 3 + src/include/executor/execExpr.h | 5 +- src/include/nodes/execnodes.h | 22 +- src/include/nodes/makefuncs.h | 4 +- src/include/nodes/primnodes.h | 91 +--- .../regress/expected/sqljson_queryfuncs.out | 62 +-- src/test/regress/sql/sqljson_queryfuncs.sql | 23 +- src/tools/pgindent/typedefs.list | 2 - 17 files changed, 481 insertions(+), 867 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 70fa1f6c69..754a52fb48 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -18605,16 +18605,6 @@ $.* ? (@ like_regex "^\\d+$") path_expression can contain. - - - SQL/JSON query functions currently only accept values of the - jsonb type, because the SQL/JSON path language only - supports those, so it might be necessary to cast the - context_item argument of these functions to - jsonb. - - - SQL/JSON Query Functions @@ -18684,7 +18674,7 @@ ERROR: jsonpath array subscript is out of bounds { ERROR | NULL | EMPTY { ARRAY | OBJECT } | DEFAULT expression } ON ERROR ) - Returns the result of applying the + Returns the result of applying the SQL/JSON path_expression to the context_item using the PASSING values. @@ -18762,7 +18752,7 @@ DETAIL: Missing "]" after array dimensions. { ERROR | NULL | DEFAULT expression } ON ERROR ) - Returns the result of applying the + Returns the result of applying the SQL/JSON path_expression to the context_item using the PASSING values. diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index e5c92efd61..2269642934 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -88,12 +88,12 @@ 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, +static void ExecInitJsonExpr(JsonExpr *jsexpr, ExprState *state, Datum *resv, bool *resnull, ExprEvalStep *scratch); -static int ExecInitJsonExprCoercion(ExprState *state, Node *coercion, - ErrorSaveContext *escontext, - Datum *resv, bool *resnull); +static void ExecInitJsonCoercion(ExprState *state, JsonReturning *returning, + ErrorSaveContext *escontext, + Datum *resv, bool *resnull); /* @@ -2434,9 +2434,9 @@ ExecInitExprRec(Expr *node, ExprState *state, case T_JsonExpr: { - JsonExpr *jexpr = castNode(JsonExpr, node); + JsonExpr *jsexpr = castNode(JsonExpr, node); - ExecInitJsonExpr(jexpr, state, resv, resnull, &scratch); + ExecInitJsonExpr(jsexpr, state, resv, resnull, &scratch); break; } @@ -4213,26 +4213,28 @@ ExecBuildParamSetEqual(TupleDesc desc, * Push steps to evaluate a JsonExpr and its various subsidiary expressions. */ static void -ExecInitJsonExpr(JsonExpr *jexpr, ExprState *state, +ExecInitJsonExpr(JsonExpr *jsexpr, ExprState *state, Datum *resv, bool *resnull, ExprEvalStep *scratch) { JsonExprState *jsestate = palloc0(sizeof(JsonExprState)); + ErrorSaveContext *escontext = + jsexpr->on_error->btype != JSON_BEHAVIOR_ERROR ? + &jsestate->escontext : NULL; ListCell *argexprlc; ListCell *argnamelc; List *jumps_if_skip = NIL; - List *jumps_to_coerce_finish = NIL; List *jumps_to_end = NIL; ListCell *lc; ExprEvalStep *as; - jsestate->jsexpr = jexpr; + jsestate->jsexpr = jsexpr; /* * Evaluate formatted_expr storing the result into * jsestate->formatted_expr. */ - ExecInitExprRec((Expr *) jexpr->formatted_expr, state, + ExecInitExprRec((Expr *) jsexpr->formatted_expr, state, &jsestate->formatted_expr.value, &jsestate->formatted_expr.isnull); @@ -4247,7 +4249,7 @@ ExecInitJsonExpr(JsonExpr *jexpr, ExprState *state, * Evaluate pathspec expression storing the result into * jsestate->pathspec. */ - ExecInitExprRec((Expr *) jexpr->path_spec, state, + ExecInitExprRec((Expr *) jsexpr->path_spec, state, &jsestate->pathspec.value, &jsestate->pathspec.isnull); @@ -4260,8 +4262,8 @@ ExecInitJsonExpr(JsonExpr *jexpr, ExprState *state, /* Steps to compute PASSING args. */ jsestate->args = NIL; - forboth(argexprlc, jexpr->passing_values, - argnamelc, jexpr->passing_names) + forboth(argexprlc, jsexpr->passing_values, + argnamelc, jsexpr->passing_names) { Expr *argexpr = (Expr *) lfirst(argexprlc); String *argname = lfirst_node(String, argnamelc); @@ -4300,8 +4302,8 @@ ExecInitJsonExpr(JsonExpr *jexpr, ExprState *state, scratch->d.constval.isnull = true; ExprEvalPushStep(state, scratch); - /* Jump to coerce the NULL using result_coercion if present. */ - if (jexpr->result_coercion) + /* Jump to coerce the NULL using coercion_expr if present. */ + if (jsexpr->coercion_expr) { scratch->opcode = EEOP_JUMP; scratch->d.jump.jumpdone = state->steps_len + 1; @@ -4309,81 +4311,80 @@ ExecInitJsonExpr(JsonExpr *jexpr, ExprState *state, } /* - * Steps to coerce the result value computed by EEOP_JSONEXPR_PATH. To - * handle coercion errors softly, use the following ErrorSaveContext when - * initializing the coercion expressions, including any JsonCoercion - * nodes. + * To handle coercion errors softly, use the following ErrorSaveContext + * to pass to ExecInitExprRec() when initializing the coercion expressions + * and in the EEOP_JSONEXPR_COERCION step. */ jsestate->escontext.type = T_ErrorSaveContext; - if (jexpr->result_coercion) + + /* Steps to coerce the result value computed by EEOP_JSONEXPR_PATH. */ + jsestate->jump_eval_coercion_expr = -1; + if (jsexpr->coercion_expr) { - jsestate->jump_eval_result_coercion = - ExecInitJsonExprCoercion(state, jexpr->result_coercion, - jexpr->on_error->btype != JSON_BEHAVIOR_ERROR ? - &jsestate->escontext : NULL, - resv, resnull); - /* Jump to COERCION_FINISH. */ - scratch->opcode = EEOP_JUMP; - scratch->d.jump.jumpdone = -1; /* set below */ - jumps_to_coerce_finish = lappend_int(jumps_to_coerce_finish, - state->steps_len); - ExprEvalPushStep(state, scratch); - } - else - jsestate->jump_eval_result_coercion = -1; + Datum *save_innermost_caseval; + bool *save_innermost_casenull; + ErrorSaveContext *save_escontext; - /* Steps for coercing JsonItemType values returned by JsonPathValue(). */ - if (jexpr->item_coercions) + jsestate->jump_eval_coercion_expr = state->steps_len; + save_innermost_caseval = state->innermost_caseval; + save_innermost_casenull = state->innermost_casenull; + save_escontext = state->escontext; + + state->innermost_caseval = resv; + state->innermost_casenull = resnull; + state->escontext = escontext; + + ExecInitExprRec((Expr *) jsexpr->coercion_expr, state, resv, resnull); + + state->innermost_caseval = save_innermost_caseval; + state->innermost_casenull = save_innermost_casenull; + state->escontext = save_escontext; + } + else if (jsexpr->use_json_coercion) + { + jsestate->jump_eval_coercion_expr = state->steps_len; + ExecInitJsonCoercion(state, jsexpr->returning, escontext, resv, resnull); + } + else if (jsexpr->use_io_coercion) { /* - * Here we create the steps for each JsonItemType type's coercion - * expression and also store a flag whether the coercion is a cast - * expression. ExecPrepareJsonItemCoercion() called by - * ExecEvalJsonExprPath() will map a given JsonbValue returned by - * JsonPathValue() to its JsonItemType's expression's step address - * and the flag by indexing the following arrays with JsonItemType - * enum value. + * Only initialize the FunctionCallInfo for the target type's input + * function, which is called by ExecEvalJsonExprPath() itself, so no + * additional step is necessary. */ - jsestate->num_item_coercions = list_length(jexpr->item_coercions); - jsestate->eval_item_coercion_jumps = (int *) - palloc(jsestate->num_item_coercions * sizeof(int)); - jsestate->item_coercion_is_cast = (bool *) - palloc0(jsestate->num_item_coercions * sizeof(bool)); - foreach(lc, jexpr->item_coercions) - { - JsonItemCoercion *item_coercion = lfirst(lc); - Node *coercion = item_coercion->coercion; - - jsestate->item_coercion_is_cast[item_coercion->item_type] = - (coercion != NULL && !IsA(coercion, JsonCoercion)); - jsestate->eval_item_coercion_jumps[item_coercion->item_type] = - ExecInitJsonExprCoercion(state, coercion, - jexpr->on_error->btype != JSON_BEHAVIOR_ERROR ? - &jsestate->escontext : NULL, - resv, resnull); - - /* Jump to COERCION_FINISH. */ - scratch->opcode = EEOP_JUMP; - scratch->d.jump.jumpdone = -1; /* set below */ - jumps_to_coerce_finish = lappend_int(jumps_to_coerce_finish, - state->steps_len); - ExprEvalPushStep(state, scratch); - } + Oid typinput; + Oid typioparam; + FmgrInfo *finfo; + FunctionCallInfo fcinfo; + + getTypeInputInfo(jsexpr->returning->typid, &typinput, &typioparam); + finfo = palloc0(sizeof(FmgrInfo)); + fcinfo = palloc0(SizeForFunctionCallInfo(3)); + fmgr_info(typinput, finfo); + fmgr_info_set_expr((Node *) jsexpr->returning, finfo); + InitFunctionCallInfoData(*fcinfo, finfo, 3, InvalidOid, NULL, NULL); + + /* + * We can preload the second and third arguments for the input + * function, since they're constants. + */ + fcinfo->args[1].value = ObjectIdGetDatum(typioparam); + fcinfo->args[1].isnull = false; + fcinfo->args[2].value = Int32GetDatum(jsexpr->returning->typmod); + fcinfo->args[2].isnull = false; + fcinfo->context = (Node *) escontext; + + jsestate->input_finfo = finfo; + jsestate->input_fcinfo = fcinfo; } /* - * Add step to reset the ErrorSaveContext and set JsonExprState.error if - * the coercion evaluation ran into an error but was not thrown because of - * the ON ERROR behavior. + * Add a special step to check if the coercion evaluation ran into an + * error but was not thrown because of the ON ERROR behavior and set + * JsonExprState.error if so. */ - if (jexpr->result_coercion || jexpr->item_coercions) + if (jsestate->jump_eval_coercion_expr >= 0) { - foreach(lc, jumps_to_coerce_finish) - { - as = &state->steps[lfirst_int(lc)]; - as->d.jump.jumpdone = state->steps_len; - } - scratch->opcode = EEOP_JSONEXPR_COERCION_FINISH; scratch->d.jsonexpr.jsestate = jsestate; ExprEvalPushStep(state, scratch); @@ -4396,8 +4397,8 @@ ExecInitJsonExpr(JsonExpr *jexpr, ExprState *state, * occur during EEOP_JSONEXPR_PATH evaluation and subsequent coercion * evaluation. */ - if (jexpr->on_error && - jexpr->on_error->btype != JSON_BEHAVIOR_ERROR) + if (jsexpr->on_error && + jsexpr->on_error->btype != JSON_BEHAVIOR_ERROR) { jsestate->jump_error = state->steps_len; scratch->opcode = EEOP_JUMP_IF_NOT_TRUE; @@ -4413,14 +4414,13 @@ ExecInitJsonExpr(JsonExpr *jexpr, ExprState *state, ExprEvalPushStep(state, scratch); /* Steps to evaluate the ON ERROR expression */ - ExecInitExprRec((Expr *) jexpr->on_error->expr, + ExecInitExprRec((Expr *) jsexpr->on_error->expr, state, resv, resnull); /* Steps to coerce the ON ERROR expression if needed */ - (void) ExecInitJsonExprCoercion(state, - (Node *) jexpr->on_error->coercion, - &jsestate->escontext, - resv, resnull); + if (jsexpr->on_error->coerce) + ExecInitJsonCoercion(state, jsexpr->returning, escontext, resv, + resnull); jumps_to_end = lappend_int(jumps_to_end, state->steps_len); scratch->opcode = EEOP_JUMP; @@ -4429,8 +4429,8 @@ ExecInitJsonExpr(JsonExpr *jexpr, ExprState *state, } /* Step to handle ON EMPTY behaviors. */ - if (jexpr->on_empty != NULL && - jexpr->on_empty->btype != JSON_BEHAVIOR_ERROR) + if (jsexpr->on_empty != NULL && + jsexpr->on_empty->btype != JSON_BEHAVIOR_ERROR) { jsestate->jump_empty = state->steps_len; @@ -4442,14 +4442,13 @@ ExecInitJsonExpr(JsonExpr *jexpr, ExprState *state, ExprEvalPushStep(state, scratch); /* Steps to evaluate the ON EMPTY expression */ - ExecInitExprRec((Expr *) jexpr->on_empty->expr, + ExecInitExprRec((Expr *) jsexpr->on_empty->expr, state, resv, resnull); /* Steps to coerce the ON EMPTY expression if needed */ - (void) ExecInitJsonExprCoercion(state, - (Node *) jexpr->on_empty->coercion, - &jsestate->escontext, - resv, resnull); + if (jsexpr->on_empty->coerce) + ExecInitJsonCoercion(state, jsexpr->returning, escontext, resv, + resnull); scratch->opcode = EEOP_JUMP; scratch->d.jump.jumpdone = -1; /* set below */ @@ -4466,64 +4465,24 @@ ExecInitJsonExpr(JsonExpr *jexpr, ExprState *state, jsestate->jump_end = state->steps_len; } -/* Initialize one JsonCoercion for execution. */ -static int -ExecInitJsonExprCoercion(ExprState *state, Node *coercion_expr, - ErrorSaveContext *escontext, - Datum *resv, bool *resnull) +/* + * Initialize a EEOP_JSONEXPR_COERCION step to coerce the value given in resv + * to the given RETURNING type. + */ +static void +ExecInitJsonCoercion(ExprState *state, JsonReturning *returning, + ErrorSaveContext *escontext, + Datum *resv, bool *resnull) { - int jump_eval_coercion; - Datum *save_innermost_caseval; - bool *save_innermost_casenull; - ErrorSaveContext *save_escontext; - - if (coercion_expr == NULL) - return -1; - - jump_eval_coercion = state->steps_len; - if (IsA(coercion_expr, JsonCoercion)) - { - JsonCoercion *coercion = (JsonCoercion *) coercion_expr; - ExprEvalStep scratch = {0}; - Oid typinput; - FmgrInfo *finfo; - Oid typioparam; - - getTypeInputInfo(((JsonCoercion *) coercion)->targettype, - &typinput, &typioparam); - finfo = palloc0(sizeof(FmgrInfo)); - fmgr_info(typinput, finfo); - - scratch.opcode = EEOP_JSONEXPR_COERCION; - scratch.resvalue = resv; - scratch.resnull = resnull; - scratch.d.jsonexpr_coercion.coercion = coercion; - scratch.d.jsonexpr_coercion.input_finfo = finfo; - scratch.d.jsonexpr_coercion.typioparam = typioparam; - scratch.d.jsonexpr_coercion.json_populate_type_cache = NULL; - scratch.d.jsonexpr_coercion.escontext = escontext; - ExprEvalPushStep(state, &scratch); - /* Initialize the cast expression below, if any. */ - if (coercion->cast_expr != NULL) - coercion_expr = coercion->cast_expr; - else - return jump_eval_coercion; - } - - /* Push step(s) to compute cstate->coercion. */ - save_innermost_caseval = state->innermost_caseval; - save_innermost_casenull = state->innermost_casenull; - save_escontext = state->escontext; - - state->innermost_caseval = resv; - state->innermost_casenull = resnull; - state->escontext = escontext; - - ExecInitExprRec((Expr *) coercion_expr, state, resv, resnull); - - state->innermost_caseval = save_innermost_caseval; - state->innermost_casenull = save_innermost_casenull; - state->escontext = save_escontext; + ExprEvalStep scratch = {0}; - return jump_eval_coercion; + /* For json_populate_type() */ + scratch.opcode = EEOP_JSONEXPR_COERCION; + scratch.resvalue = resv; + scratch.resnull = resnull; + scratch.d.jsonexpr_coercion.targettype = returning->typid; + scratch.d.jsonexpr_coercion.targettypmod = returning->typmod; + scratch.d.jsonexpr_coercion.json_populate_type_cache = NULL; + scratch.d.jsonexpr_coercion.escontext = escontext; + ExprEvalPushStep(state, &scratch); } diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 2d6b11bb34..dea3006dec 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -180,10 +180,7 @@ static pg_attribute_always_inline void ExecAggPlainTransByRef(AggState *aggstate AggStatePerGroup pergroup, ExprContext *aggcontext, int setno); -static void ExecPrepareJsonItemCoercion(JsonbValue *item, JsonExprState *jsestate, - bool throw_error, - int *jump_eval_item_coercion, - Datum *resvalue, bool *resnull); +static char *ExecGetJsonValueItemString(JsonbValue *item, bool *resnull); /* * ScalarArrayOpExprHashEntry @@ -4269,15 +4266,14 @@ ExecEvalJsonExprPath(ExprState *state, ExprEvalStep *op, ExprContext *econtext) { JsonExprState *jsestate = op->d.jsonexpr.jsestate; - JsonExpr *jexpr = jsestate->jsexpr; + JsonExpr *jsexpr = jsestate->jsexpr; Datum item; JsonPath *path; - bool throw_error = jexpr->on_error->btype == JSON_BEHAVIOR_ERROR; + bool throw_error = jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR; bool error = false, empty = false; - - /* Might get overridden for JSON_VALUE_OP by an per-item coercion. */ - int jump_eval_coercion = jsestate->jump_eval_result_coercion; + int jump_eval_coercion = jsestate->jump_eval_coercion_expr; + char *val_string = NULL; item = jsestate->formatted_expr.value; path = DatumGetJsonPathP(jsestate->pathspec.value); @@ -4285,7 +4281,8 @@ ExecEvalJsonExprPath(ExprState *state, ExprEvalStep *op, /* Set error/empty to false. */ memset(&jsestate->error, 0, sizeof(NullableDatum)); memset(&jsestate->empty, 0, sizeof(NullableDatum)); - switch (jexpr->op) + jsestate->escontext.error_occurred = false; + switch (jsexpr->op) { case JSON_EXISTS_OP: { @@ -4302,12 +4299,27 @@ ExecEvalJsonExprPath(ExprState *state, ExprEvalStep *op, break; case JSON_QUERY_OP: - *op->resvalue = JsonPathQuery(item, path, jexpr->wrapper, &empty, + *op->resvalue = JsonPathQuery(item, path, jsexpr->wrapper, &empty, !throw_error ? &error : NULL, jsestate->args); - if (!error && !empty) - *op->resnull = (DatumGetPointer(*op->resvalue) == NULL); + *op->resnull = (DatumGetPointer(*op->resvalue) == NULL); + + /* Handle OMIT QUOTES. */ + if (!*op->resnull && jsexpr->omit_quotes) + { + val_string = JsonbUnquote(DatumGetJsonbP(*op->resvalue)); + + /* + * Pass the string as a text value to the cast expression + * if any. If not, use the input function call below to + * do the coercion. + */ + if (jump_eval_coercion >= 0) + *op->resvalue = + DirectFunctionCall1(textin, + PointerGetDatum(val_string)); + } break; case JSON_VALUE_OP: @@ -4318,47 +4330,69 @@ ExecEvalJsonExprPath(ExprState *state, ExprEvalStep *op, if (jbv == NULL) { - /* Will be coerced with result_coercion. */ + /* Will be coerced with coercion_expr. */ *op->resvalue = (Datum) 0; *op->resnull = true; } else if (!error && !empty) { - /* - * If the requested output type is json(b), use - * result_coercion to do the coercion. - */ - if (jexpr->returning->typid == JSONOID || - jexpr->returning->typid == JSONBOID) + if (jsexpr->returning->typid == JSONOID || + jsexpr->returning->typid == JSONBOID) { - *op->resvalue = JsonbPGetDatum(JsonbValueToJsonb(jbv)); - *op->resnull = false; + val_string = DatumGetCString(DirectFunctionCall1(jsonb_out, + JsonbPGetDatum(JsonbValueToJsonb(jbv)))); } else { + val_string = ExecGetJsonValueItemString(jbv, op->resnull); + /* - * Else, use one of the item_coercions. - * - * Error out if no cast expression exists. + * Pass the string as a text value to the cast expression + * if any. If not, use the input function call below to + * do the coercion. */ - ExecPrepareJsonItemCoercion(jbv, jsestate, throw_error, - &jump_eval_coercion, - op->resvalue, op->resnull); + *op->resvalue = PointerGetDatum(val_string); + if (jump_eval_coercion >= 0) + *op->resvalue = DirectFunctionCall1(textin, *op->resvalue); } } break; } default: - elog(ERROR, "unrecognized SQL/JSON expression op %d", jexpr->op); + elog(ERROR, "unrecognized SQL/JSON expression op %d", + (int) jsexpr->op); return false; } + /* + * Coerce the result value by calling the input function coercion. + * *op->resvalue must point to C string in this case. + */ + if (!*op->resnull && jsexpr->use_io_coercion) + { + FunctionCallInfo fcinfo; + + fcinfo = jsestate->input_fcinfo; + Assert(fcinfo != NULL); + Assert(val_string != NULL); + fcinfo->args[0].value = PointerGetDatum(val_string); + fcinfo->args[0].isnull = *op->resnull; + /* second and third arguments are already set up */ + + fcinfo->isnull = false; + *op->resvalue = FunctionCallInvoke(fcinfo); + if (SOFT_ERROR_OCCURRED(&jsestate->escontext)) + error = true; + + jump_eval_coercion = -1; + } + if (empty) { - if (jexpr->on_empty) + if (jsexpr->on_empty) { - if (jexpr->on_empty->btype == JSON_BEHAVIOR_ERROR) + if (jsexpr->on_empty->btype == JSON_BEHAVIOR_ERROR) ereport(ERROR, errcode(ERRCODE_NO_SQL_JSON_ITEM), errmsg("no SQL/JSON item")); @@ -4368,7 +4402,7 @@ ExecEvalJsonExprPath(ExprState *state, ExprEvalStep *op, Assert(jsestate->jump_empty >= 0); return jsestate->jump_empty; } - else if (jexpr->on_error->btype == JSON_BEHAVIOR_ERROR) + else if (jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR) ereport(ERROR, errcode(ERRCODE_NO_SQL_JSON_ITEM), errmsg("no SQL/JSON item")); @@ -4396,31 +4430,18 @@ ExecEvalJsonExprPath(ExprState *state, ExprEvalStep *op, } /* - * Selects a coercion for a given JsonbValue based on its type. - * - * On return, *resvalue and *resnull are set to the value extracted from the - * JsonbValue and *jump_eval_item_coercion is set to the step address of the - * coercion expression. + * Convert the a given JsonbValue to its C string representation * - * If the found expression is a JsonCoercion node that means the parser - * didnt' find a cast to do the coercion, so throw an error if the - * ON ERROR behavior says to do so. + * Returns the string as a Datum setting *resnull if the JsonbValue is a + * a jbvNull. */ -static void -ExecPrepareJsonItemCoercion(JsonbValue *item, JsonExprState *jsestate, - bool throw_error, - int *jump_eval_item_coercion, - Datum *resvalue, bool *resnull) +static char * +ExecGetJsonValueItemString(JsonbValue *item, bool *resnull) { - int *eval_item_coercion_jumps = jsestate->eval_item_coercion_jumps; - bool *item_coercion_is_cast = jsestate->item_coercion_is_cast; - bool is_cast; - int jump_to; - JsonbValue buf; - if (item->type == jbvBinary && JsonContainerIsScalar(item->val.binary.data)) { bool is_scalar PG_USED_FOR_ASSERTS_ONLY; + JsonbValue buf; is_scalar = JsonbExtractScalar(item->val.binary.data, &buf); item = &buf; @@ -4433,56 +4454,44 @@ ExecPrepareJsonItemCoercion(JsonbValue *item, JsonExprState *jsestate, switch (item->type) { case jbvNull: - is_cast = item_coercion_is_cast[JsonItemTypeNull]; - jump_to = eval_item_coercion_jumps[JsonItemTypeNull]; - *resvalue = (Datum) 0; *resnull = true; - break; + return NULL; case jbvString: - is_cast = item_coercion_is_cast[JsonItemTypeString]; - jump_to = eval_item_coercion_jumps[JsonItemTypeString]; - *resvalue = - PointerGetDatum(cstring_to_text_with_len(item->val.string.val, - item->val.string.len)); - break; + { + char *str = palloc(item->val.string.len + 1); + + memcpy(str, item->val.string.val, item->val.string.len); + str[item->val.string.len] = '\0'; + return str; + } case jbvNumeric: - is_cast = item_coercion_is_cast[JsonItemTypeNumeric]; - jump_to = eval_item_coercion_jumps[JsonItemTypeNumeric]; - *resvalue = NumericGetDatum(item->val.numeric); - break; + return DatumGetCString(DirectFunctionCall1(numeric_out, + NumericGetDatum(item->val.numeric))); case jbvBool: - is_cast = item_coercion_is_cast[JsonItemTypeBoolean]; - jump_to = eval_item_coercion_jumps[JsonItemTypeBoolean]; - *resvalue = BoolGetDatum(item->val.boolean); - break; + return DatumGetCString(DirectFunctionCall1(boolout, + BoolGetDatum(item->val.boolean))); case jbvDatetime: - *resvalue = item->val.datetime.value; switch (item->val.datetime.typid) { case DATEOID: - is_cast = item_coercion_is_cast[JsonItemTypeDate]; - jump_to = eval_item_coercion_jumps[JsonItemTypeDate]; - break; + return DatumGetCString(DirectFunctionCall1(date_out, + item->val.datetime.value)); case TIMEOID: - is_cast = item_coercion_is_cast[JsonItemTypeTime]; - jump_to = eval_item_coercion_jumps[JsonItemTypeTime]; - break; + return DatumGetCString(DirectFunctionCall1(time_out, + item->val.datetime.value)); case TIMETZOID: - is_cast = item_coercion_is_cast[JsonItemTypeTimetz]; - jump_to = eval_item_coercion_jumps[JsonItemTypeTimetz]; - break; + return DatumGetCString(DirectFunctionCall1(timetz_out, + item->val.datetime.value)); case TIMESTAMPOID: - is_cast = item_coercion_is_cast[JsonItemTypeTimestamp]; - jump_to = eval_item_coercion_jumps[JsonItemTypeTimestamp]; - break; + return DatumGetCString(DirectFunctionCall1(timestamp_out, + item->val.datetime.value)); case TIMESTAMPTZOID: - is_cast = item_coercion_is_cast[JsonItemTypeTimestamptz]; - jump_to = eval_item_coercion_jumps[JsonItemTypeTimestamptz]; - break; + return DatumGetCString(DirectFunctionCall1(timestamptz_out, + item->val.datetime.value)); default: elog(ERROR, "unexpected jsonb datetime type oid %u", item->val.datetime.typid); @@ -4492,37 +4501,21 @@ ExecPrepareJsonItemCoercion(JsonbValue *item, JsonExprState *jsestate, case jbvArray: case jbvObject: case jbvBinary: - is_cast = item_coercion_is_cast[JsonItemTypeComposite]; - jump_to = eval_item_coercion_jumps[JsonItemTypeComposite]; - *resvalue = JsonbPGetDatum(JsonbValueToJsonb(item)); - break; + return DatumGetCString(DirectFunctionCall1(jsonb_out, + JsonbPGetDatum(JsonbValueToJsonb(item)))); default: elog(ERROR, "unexpected jsonb value type %d", item->type); } - /* If the expression is not a cast expression, throw an error. */ - if (jump_to >= 0 && !is_cast) - { - if (throw_error) - ereport(ERROR, - errcode(ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE), - errmsg("SQL/JSON item cannot be cast to target type")); - - *resvalue = (Datum) 0; - *resnull = true; - } - - *jump_eval_item_coercion = jump_to; + Assert(false); + *resnull = true; + return NULL; } /* - * Coerce a jsonb or a scalar string value produced by ExecEvalJsonExprPath() - * or an ON ERROR / EMPTY behavior expression to the target type. - * - * This is also responsible for removing any quotes present in the source - * value if JsonCoercion.omit_quotes is true before coercing to the target - * type. + * Coerce a jsonb value produced by ExecEvalJsonExprPath() or an ON ERROR / + * ON EMPTY behavior expression to the target type. * * Any soft errors that occur here will be checked by * EEOP_JSONEXPR_COERCION_FINISH that will run after this. @@ -4531,48 +4524,14 @@ void ExecEvalJsonCoercion(ExprState *state, ExprEvalStep *op, ExprContext *econtext) { - JsonCoercion *coercion = op->d.jsonexpr_coercion.coercion; ErrorSaveContext *escontext = op->d.jsonexpr_coercion.escontext; - Datum res = *op->resvalue; - /* - * Handle OMIT QUOTES. - * - * Normally, json_populate_type() is the place to coerce jsonb values to - * the requested target type, including those that are scalar strings, but - * it doesn't have the support for removing quotes to implement the - * OMIT QUOTES clause, so we handle it here. - */ - if (coercion->omit_quotes) - { - FmgrInfo *input_finfo = op->d.jsonexpr_coercion.input_finfo; - Oid typioparam = op->d.jsonexpr_coercion.typioparam; - char *val = !*op->resnull ? - JsonbUnquote(DatumGetJsonbP(res)) : NULL; - - /* - * If the coercion must be done using a cast expression, pass to it - * the text version of the quote-stripped string. If not, finish the - * coercion by calling the input function. - */ - if (coercion->cast_expr) - *op->resvalue = DirectFunctionCall1(textin, - CStringGetDatum(val)); - else - (void) InputFunctionCallSafe(input_finfo, val, typioparam, - coercion->targettypmod, - (Node *) escontext, - op->resvalue); - } - else - { - *op->resvalue = json_populate_type(res, JSONBOID, - coercion->targettype, - coercion->targettypmod, - &op->d.jsonexpr_coercion.json_populate_type_cache, - econtext->ecxt_per_query_memory, - op->resnull, (Node *) escontext); - } + *op->resvalue = json_populate_type(*op->resvalue, JSONBOID, + op->d.jsonexpr_coercion.targettype, + op->d.jsonexpr_coercion.targettypmod, + &op->d.jsonexpr_coercion.json_populate_type_cache, + econtext->ecxt_per_query_memory, + op->resnull, (Node *) escontext); } /* diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c index 0c4d5953dc..2c49100259 100644 --- a/src/backend/jit/llvm/llvmjit_expr.c +++ b/src/backend/jit/llvm/llvmjit_expr.c @@ -1950,10 +1950,8 @@ llvm_compile_expr(ExprState *state) */ if (jsestate->jump_empty >= 0 || jsestate->jump_error >= 0 || - jsestate->jump_eval_result_coercion >= 0 || - jsestate->num_item_coercions > 0) + jsestate->jump_eval_coercion_expr >= 0) { - int i; LLVMValueRef v_jump_empty; LLVMValueRef v_jump_error; LLVMValueRef v_jump_coercion; @@ -1961,8 +1959,7 @@ llvm_compile_expr(ExprState *state) LLVMBasicBlockRef b_done, b_empty, b_error, - b_result_coercion, - *b_item_coercions = NULL; + b_coercion_expr; b_empty = l_bb_before_v(opblocks[opno + 1], @@ -1970,21 +1967,9 @@ llvm_compile_expr(ExprState *state) b_error = l_bb_before_v(opblocks[opno + 1], "op.%d.jsonexpr_error", opno); - b_result_coercion = + b_coercion_expr = l_bb_before_v(opblocks[opno + 1], - "op.%d.jsonexpr_result_coercion", opno); - if (jsestate->num_item_coercions > 0) - { - b_item_coercions = palloc(sizeof(LLVMBasicBlockRef) * - jsestate->num_item_coercions); - for (i = 0; i < jsestate->num_item_coercions; i++) - { - b_item_coercions[i] = - l_bb_before_v(opblocks[opno + 1], - "op.%d.jsonexpr_item_coercion.%d", - opno, i); - } - } + "op.%d.jsonexpr_coercion_expr", opno); b_done = l_bb_before_v(opblocks[opno + 1], "op.%d.jsonexpr_done", opno); @@ -1992,66 +1977,45 @@ llvm_compile_expr(ExprState *state) v_switch = LLVMBuildSwitch(b, v_ret, b_done, - jsestate->num_item_coercions + 3); + 3); /* Returned jsestate->jump_empty? */ if (jsestate->jump_empty >= 0) { v_jump_empty = l_int32_const(lc, jsestate->jump_empty); LLVMAddCase(v_switch, v_jump_empty, b_empty); } - /* Returned jsestate->jump_error? */ - if (jsestate->jump_error >= 0) - { - v_jump_error = l_int32_const(lc, jsestate->jump_error); - LLVMAddCase(v_switch, v_jump_error, b_error); - } - /* Returned jsestate->jump_eval_result_coercion? */ - if (jsestate->jump_eval_result_coercion >= 0) - { - v_jump_coercion = l_int32_const(lc, jsestate->jump_eval_result_coercion); - LLVMAddCase(v_switch, v_jump_coercion, b_result_coercion); - } - - /* - * Returned one of - * jsestate->eval_item_coercion_jumps[]? - */ - for (i = 0; i < jsestate->num_item_coercions; i++) - { - if (jsestate->eval_item_coercion_jumps[i] >= 0) - { - v_jump_coercion = l_int32_const(lc, jsestate->eval_item_coercion_jumps[i]); - LLVMAddCase(v_switch, v_jump_coercion, b_item_coercions[i]); - } - } - /* ON EMPTY code */ LLVMPositionBuilderAtEnd(b, b_empty); if (jsestate->jump_empty >= 0) LLVMBuildBr(b, opblocks[jsestate->jump_empty]); else LLVMBuildUnreachable(b); + + /* Returned jsestate->jump_error? */ + if (jsestate->jump_error >= 0) + { + v_jump_error = l_int32_const(lc, jsestate->jump_error); + LLVMAddCase(v_switch, v_jump_error, b_error); + } /* ON ERROR code */ LLVMPositionBuilderAtEnd(b, b_error); if (jsestate->jump_error >= 0) LLVMBuildBr(b, opblocks[jsestate->jump_error]); else LLVMBuildUnreachable(b); - /* result_coercion code */ - LLVMPositionBuilderAtEnd(b, b_result_coercion); - if (jsestate->jump_eval_result_coercion >= 0) - LLVMBuildBr(b, opblocks[jsestate->jump_eval_result_coercion]); - else - LLVMBuildUnreachable(b); - /* item coercion code blocks */ - for (i = 0; i < jsestate->num_item_coercions; i++) + + /* Returned jsestate->jump_eval_coercion_expr? */ + if (jsestate->jump_eval_coercion_expr >= 0) { - LLVMPositionBuilderAtEnd(b, b_item_coercions[i]); - if (jsestate->eval_item_coercion_jumps[i] >= 0) - LLVMBuildBr(b, opblocks[jsestate->eval_item_coercion_jumps[i]]); - else - LLVMBuildUnreachable(b); + v_jump_coercion = l_int32_const(lc, jsestate->jump_eval_coercion_expr); + LLVMAddCase(v_switch, v_jump_coercion, b_coercion_expr); } + /* coercion_expr code */ + LLVMPositionBuilderAtEnd(b, b_coercion_expr); + if (jsestate->jump_eval_coercion_expr >= 0) + LLVMBuildBr(b, opblocks[jsestate->jump_eval_coercion_expr]); + else + LLVMBuildUnreachable(b); LLVMPositionBuilderAtEnd(b, b_done); } diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c index 538ccb30aa..b13cfa4201 100644 --- a/src/backend/nodes/makefuncs.c +++ b/src/backend/nodes/makefuncs.c @@ -861,14 +861,12 @@ makeJsonValueExpr(Expr *raw_expr, Expr *formatted_expr, * creates a JsonBehavior node */ JsonBehavior * -makeJsonBehavior(JsonBehaviorType type, Node *expr, JsonCoercion *coercion, - int location) +makeJsonBehavior(JsonBehaviorType btype, Node *expr, int location) { JsonBehavior *behavior = makeNode(JsonBehavior); - behavior->btype = type; + behavior->btype = btype; behavior->expr = expr; - behavior->coercion = coercion; behavior->location = location; return behavior; diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index d794192fae..aefdcdb9ec 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -243,24 +243,11 @@ exprType(const Node *expr) type = jexpr->returning->typid; break; } - case T_JsonCoercion: - { - const JsonCoercion *coercion = (const JsonCoercion *) expr; - - type = coercion->targettype; - break; - } - case T_JsonItemCoercion: - type = exprType(((JsonItemCoercion *) expr)->coercion); - break; case T_JsonBehavior: { const JsonBehavior *behavior = (const JsonBehavior *) expr; - if (behavior->coercion) - type = exprType((Node *) behavior->coercion); - else - type = exprType(behavior->expr); + type = exprType(behavior->expr); break; } case T_NullTest: @@ -527,23 +514,11 @@ exprTypmod(const Node *expr) return jexpr->returning->typmod; } break; - case T_JsonCoercion: - { - const JsonCoercion *coercion = (const JsonCoercion *) expr; - - return coercion->targettypmod; - } - break; - case T_JsonItemCoercion: - return exprTypmod(((JsonItemCoercion *) expr)->coercion); case T_JsonBehavior: { const JsonBehavior *behavior = (const JsonBehavior *) expr; - if (behavior->coercion) - return exprTypmod((Node *) behavior->coercion); - else - return exprTypmod(behavior->expr); + return exprTypmod(behavior->expr); } break; case T_CoerceToDomain: @@ -1026,21 +1001,16 @@ exprCollation(const Node *expr) coll = InvalidOid; /* ... so it has no collation */ break; case T_JsonExpr: - coll = exprCollation(((JsonExpr *) expr)->result_coercion); - break; - case T_JsonCoercion: - coll = ((const JsonCoercion *) expr)->collation; - break; - case T_JsonItemCoercion: - coll = exprCollation(((JsonItemCoercion *) expr)->coercion); + if (((JsonExpr *) expr)->coercion_expr) + coll = exprCollation(((JsonExpr *) expr)->coercion_expr); + else + coll = ((JsonExpr *) expr)->collation; break; case T_JsonBehavior: { JsonBehavior *behavior = (JsonBehavior *) expr; - if (behavior->coercion) - coll = exprCollation((Node *) behavior->coercion); - else if (behavior->expr) + if (behavior->expr) coll = exprCollation(behavior->expr); else coll = InvalidOid; @@ -1289,28 +1259,10 @@ exprSetCollation(Node *expr, Oid collation) { JsonExpr *jexpr = (JsonExpr *) expr; - if (jexpr->result_coercion) - exprSetCollation((Node *) jexpr->result_coercion, collation); + if (jexpr->coercion_expr) + exprSetCollation((Node *) jexpr->coercion_expr, collation); else - Assert(!OidIsValid(collation)); /* result is always a - * json[b] type */ - } - break; - case T_JsonItemCoercion: - { - JsonItemCoercion *item_coercion = (JsonItemCoercion *) expr; - - if (item_coercion->coercion) - exprSetCollation(item_coercion->coercion, collation); - } - break; - case T_JsonCoercion: - { - JsonCoercion *coercion = (JsonCoercion *) expr; - - if (coercion->cast_expr) - exprSetCollation(coercion->cast_expr, collation); - coercion->collation = collation; + jexpr->collation = collation; } break; case T_JsonBehavior: @@ -1319,8 +1271,6 @@ exprSetCollation(Node *expr, Oid collation) if (behavior->expr) exprSetCollation(behavior->expr, collation); - if (behavior->coercion) - exprSetCollation((Node *) behavior->coercion, collation); } break; case T_NullTest: @@ -2400,9 +2350,9 @@ expression_tree_walker_impl(Node *node, if (WALK(jexpr->formatted_expr)) return true; - if (WALK(jexpr->result_coercion)) + if (WALK(jexpr->path_spec)) return true; - if (WALK(jexpr->item_coercions)) + if (WALK(jexpr->coercion_expr)) return true; if (WALK(jexpr->passing_values)) return true; @@ -2413,30 +2363,12 @@ expression_tree_walker_impl(Node *node, return true; } break; - case T_JsonCoercion: - { - JsonCoercion *coercion = (JsonCoercion *) node; - - if (WALK(coercion->cast_expr)) - return true; - } - break; - case T_JsonItemCoercion: - { - JsonItemCoercion *item_coercion = (JsonItemCoercion *) node; - - if (WALK(item_coercion->coercion)) - return true; - } - break; case T_JsonBehavior: { JsonBehavior *behavior = (JsonBehavior *) node; if (WALK(behavior->expr)) return true; - if (WALK(behavior->coercion)) - return true; } break; case T_NullTest: @@ -3449,10 +3381,9 @@ expression_tree_mutator_impl(Node *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, Node *); - MUTATE(newnode->item_coercions, jexpr->item_coercions, List *); + MUTATE(newnode->path_spec, jexpr->path_spec, Node *); + MUTATE(newnode->coercion_expr, jexpr->coercion_expr, Node *); MUTATE(newnode->passing_values, jexpr->passing_values, List *); /* assume mutator does not care about passing_names */ MUTATE(newnode->on_empty, jexpr->on_empty, JsonBehavior *); @@ -3460,25 +3391,6 @@ expression_tree_mutator_impl(Node *node, return (Node *) newnode; } break; - case T_JsonCoercion: - { - JsonCoercion *coercion = (JsonCoercion *) node; - JsonCoercion *newnode; - - FLATCOPY(newnode, coercion, JsonCoercion); - MUTATE(newnode->cast_expr, coercion->cast_expr, Node *); - return (Node *) newnode; - } - case T_JsonItemCoercion: - { - JsonItemCoercion *item_coercion = (JsonItemCoercion *) node; - JsonItemCoercion *newnode; - - FLATCOPY(newnode, item_coercion, JsonItemCoercion); - MUTATE(newnode->coercion, item_coercion->coercion, Node *); - return (Node *) newnode; - } - break; case T_JsonBehavior: { JsonBehavior *behavior = (JsonBehavior *) node; @@ -3486,7 +3398,6 @@ expression_tree_mutator_impl(Node *node, FLATCOPY(newnode, behavior, JsonBehavior); MUTATE(newnode->expr, behavior->expr, Node *); - MUTATE(newnode->coercion, behavior->coercion, JsonCoercion *); return (Node *) newnode; } break; @@ -4205,8 +4116,6 @@ raw_expression_tree_walker_impl(Node *node, if (WALK(jb->expr)) return true; - if (WALK(jb->coercion)) - return true; } break; case T_NullTest: diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 91354b8178..c247eefb0c 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -16632,9 +16632,9 @@ json_wrapper_behavior: json_behavior: DEFAULT a_expr - { $$ = (Node *) makeJsonBehavior(JSON_BEHAVIOR_DEFAULT, $2, NULL, @1); } + { $$ = (Node *) makeJsonBehavior(JSON_BEHAVIOR_DEFAULT, $2, @1); } | json_behavior_type - { $$ = (Node *) makeJsonBehavior($1, NULL, NULL, @1); } + { $$ = (Node *) makeJsonBehavior($1, NULL, @1); } ; json_behavior_type: diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 5a5130b160..a898449ed4 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -93,19 +93,10 @@ static Node *transformJsonScalarExpr(ParseState *pstate, JsonScalarExpr *expr); static Node *transformJsonSerializeExpr(ParseState *pstate, JsonSerializeExpr *expr); static Node *transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *p); -static JsonExpr *transformJsonExprCommon(ParseState *pstate, JsonFuncExpr *func, - const char *constructName); static void transformJsonPassingArgs(ParseState *pstate, const char *constructName, JsonFormatType format, List *args, List **passing_values, List **passing_names); -static Node *coerceJsonFuncExprOutput(ParseState *pstate, JsonExpr *jsexpr); -static Oid JsonFuncExprDefaultReturnType(JsonExpr *jsexpr); -static Node *coerceJsonExpr(ParseState *pstate, Node *expr, - const JsonReturning *returning, bool omit_quotes); -static JsonCoercion *makeJsonCoercion(const JsonReturning *returning, - bool omit_quotes, Node *cast_expr); -static List *InitJsonItemCoercions(ParseState *pstate, const JsonReturning *returning, - Oid contextItemTypeId); +static void coerceJsonExprOutput(ParseState *pstate, JsonExpr *jsexpr); static JsonBehavior *transformJsonBehavior(ParseState *pstate, JsonBehavior *behavior, JsonBehaviorType default_behavior, JsonReturning *returning); @@ -4258,22 +4249,27 @@ transformJsonSerializeExpr(ParseState *pstate, JsonSerializeExpr *expr) static Node * transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func) { - JsonExpr *jsexpr = NULL; + JsonExpr *jsexpr; + Node *path_spec; const char *func_name = NULL; + JsonFormatType default_format; switch (func->op) { case JSON_EXISTS_OP: func_name = "JSON_EXISTS"; + default_format = JS_FORMAT_DEFAULT; break; case JSON_QUERY_OP: func_name = "JSON_QUERY"; + default_format = JS_FORMAT_JSONB; break; case JSON_VALUE_OP: func_name = "JSON_VALUE"; + default_format = JS_FORMAT_DEFAULT; break; default: - elog(ERROR, "invalid JsonFuncExpr op"); + elog(ERROR, "invalid JsonFuncExpr op %d", (int) func->op); break; } @@ -4289,7 +4285,7 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func) format->encoding != JS_ENC_DEFAULT) ereport(ERROR, errcode(ERRCODE_SYNTAX_ERROR), - errmsg("cannot specify FORMAT in RETURNING clause of %s()", + errmsg("cannot specify FORMAT JSON in RETURNING clause of %s()", func_name), parser_errposition(pstate, format->location)); } @@ -4304,7 +4300,49 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func) errmsg("SQL/JSON QUOTES behavior must not be specified when WITH WRAPPER is used"), parser_errposition(pstate, func->location)); - jsexpr = transformJsonExprCommon(pstate, func, func_name); + jsexpr = makeNode(JsonExpr); + jsexpr->location = func->location; + jsexpr->op = func->op; + jsexpr->formatted_expr = transformJsonValueExpr(pstate, func_name, + func->context_item, + default_format, + JSONBOID, false); + + /* + * jsonpath machinery can only handle jsonb documents, so coerce the input + * if not already of jsonb type. + */ + if (exprType(jsexpr->formatted_expr) != JSONBOID) + jsexpr->formatted_expr = coerce_to_specific_type(pstate, + jsexpr->formatted_expr, JSONBOID, + func_name); + jsexpr->format = func->context_item->format; + + path_spec = transformExprRecurse(pstate, func->pathspec); + path_spec = coerce_to_target_type(pstate, path_spec, exprType(path_spec), + JSONPATHOID, -1, + COERCION_EXPLICIT, COERCE_IMPLICIT_CAST, + exprLocation(path_spec)); + if (path_spec == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("JSON path expression must be of type %s, not of type %s", + "jsonpath", format_type_be(exprType(path_spec))), + parser_errposition(pstate, exprLocation(path_spec)))); + jsexpr->path_spec = path_spec; + + /* + * Transform and coerce to json[b] passing arguments, whose format is + * determined by context item type. + */ + transformJsonPassingArgs(pstate, func_name, + exprType(jsexpr->formatted_expr) == JSONBOID ? + JS_FORMAT_JSONB : JS_FORMAT_JSON, + func->passing, + &jsexpr->passing_values, + &jsexpr->passing_names); + + jsexpr->returning = transformJsonOutput(pstate, func->output, false); switch (func->op) { @@ -4335,11 +4373,11 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func) { JsonReturning *ret = jsexpr->returning; - ret->typid = JsonFuncExprDefaultReturnType(jsexpr); + ret->typid = JSONBOID; ret->typmod = -1; } - jsexpr->result_coercion = coerceJsonFuncExprOutput(pstate, jsexpr); + coerceJsonExprOutput(pstate, jsexpr); if (func->on_empty) jsexpr->on_empty = transformJsonBehavior(pstate, @@ -4353,7 +4391,7 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func) case JSON_VALUE_OP: /* Always omit quotes from scalar strings. */ - jsexpr->omit_quotes = (func->quotes == JS_QUOTES_OMIT); + jsexpr->omit_quotes = true; /* JSON_VALUE returns text by default. */ if (!OidIsValid(jsexpr->returning->typid)) @@ -4369,16 +4407,7 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func) jsexpr->returning->format->format_type = JS_FORMAT_DEFAULT; jsexpr->returning->format->encoding = JS_ENC_DEFAULT; - jsexpr->result_coercion = coerceJsonFuncExprOutput(pstate, jsexpr); - - /* - * Initialize expressions to coerce the scalar value returned by - * JsonPathValue() to the "returning" type. - */ - if (jsexpr->result_coercion) - jsexpr->item_coercions = - InitJsonItemCoercions(pstate, jsexpr->returning, - exprType(jsexpr->formatted_expr)); + coerceJsonExprOutput(pstate, jsexpr); if (func->on_empty) jsexpr->on_empty = transformJsonBehavior(pstate, @@ -4391,70 +4420,13 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func) break; default: - elog(ERROR, "invalid JsonFuncExpr op"); + elog(ERROR, "invalid JsonFuncExpr op %d", (int) func->op); break; } return (Node *) jsexpr; } -/* - * Common code for JSON_VALUE, JSON_QUERY, JSON_EXISTS transformation - * into a JsonExpr node. - */ -static JsonExpr * -transformJsonExprCommon(ParseState *pstate, JsonFuncExpr *func, - const char *constructName) -{ - JsonExpr *jsexpr = makeNode(JsonExpr); - Node *path_spec; - - jsexpr->location = func->location; - jsexpr->op = func->op; - jsexpr->formatted_expr = transformJsonValueExpr(pstate, constructName, - func->context_item, - JS_FORMAT_JSON, - InvalidOid, false); - - if (exprType(jsexpr->formatted_expr) != JSONBOID) - ereport(ERROR, - errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("%s() is not yet implemented for the json type", - constructName), - errhint("Try casting the argument to jsonb"), - parser_errposition(pstate, exprLocation(jsexpr->formatted_expr))); - - jsexpr->format = func->context_item->format; - - path_spec = transformExprRecurse(pstate, func->pathspec); - jsexpr->path_spec = - coerce_to_target_type(pstate, path_spec, exprType(path_spec), - JSONPATHOID, -1, - COERCION_EXPLICIT, COERCE_IMPLICIT_CAST, - exprLocation(path_spec)); - if (!jsexpr->path_spec) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("JSON path expression must be of type %s, not of type %s", - "jsonpath", format_type_be(exprType(path_spec))), - parser_errposition(pstate, exprLocation(path_spec)))); - - /* - * Transform and coerce to json[b] passing arguments, whose format is - * determined by context item type. - */ - transformJsonPassingArgs(pstate, constructName, - exprType(jsexpr->formatted_expr) == JSONBOID ? - JS_FORMAT_JSONB : JS_FORMAT_JSON, - func->passing, - &jsexpr->passing_values, - &jsexpr->passing_names); - - jsexpr->returning = transformJsonOutput(pstate, func->output, false); - - return jsexpr; -} - /* * Transform a JSON PASSING clause. */ @@ -4481,29 +4453,47 @@ transformJsonPassingArgs(ParseState *pstate, const char *constructName, } /* - * Create an expression to coerce the output of JSON_VALUE() / JSON_QUERY() - * to the output type, if needed. + * Set up to coerce the result value of JSON_VALUE() / JSON_QUERY() to the + * RETURNING type (default or user-specified), if needed. */ -static Node * -coerceJsonFuncExprOutput(ParseState *pstate, JsonExpr *jsexpr) +static void +coerceJsonExprOutput(ParseState *pstate, JsonExpr *jsexpr) { JsonReturning *returning = jsexpr->returning; Node *context_item = jsexpr->formatted_expr; - Node *coercion_expr = NULL; int default_typmod; Oid default_typid; bool omit_quotes = jsexpr->op == JSON_QUERY_OP && jsexpr->omit_quotes; + Node *coercion_expr = NULL; Assert(returning); /* - * Cast functions from jsonb to the following types (jsonb_bool() et al) - * don't handle errors softly, so force to use json_populate_type() using - * a JsonCoercion node so that any errors are handled appropriately. + * Check for cases where the coercion should be handled at runtime, that + * is, without using a cast expression. */ - if (jsexpr->op == JSON_QUERY_OP) + if (jsexpr->op == JSON_VALUE_OP) { + /* + * Use cast expressions for types with typmod and domain types. + */ + if (returning->typmod == -1 && + get_typtype(returning->typid) != TYPTYPE_DOMAIN) + { + jsexpr->use_io_coercion = true; + return; + } + } + else if (jsexpr->op == JSON_QUERY_OP) + { + /* + * Cast functions from jsonb to the following types (jsonb_bool() et + * al) don't handle errors softly, so coerce either by calling + * json_populate_type() or the type's input function so that any + * errors are handled appropriately. The latter only if OMIT QUOTES + * is true. + */ switch (returning->typid) { case BOOLOID: @@ -4513,173 +4503,68 @@ coerceJsonFuncExprOutput(ParseState *pstate, JsonExpr *jsexpr) case INT8OID: case FLOAT4OID: case FLOAT8OID: - return (Node *) makeJsonCoercion(returning, omit_quotes, NULL); + if (jsexpr->omit_quotes) + jsexpr->use_io_coercion = true; + else + jsexpr->use_json_coercion = true; + return; default: break; } } - default_typid = JsonFuncExprDefaultReturnType(jsexpr); - default_typmod = -1; + /* Look up a cast expression. */ + + /* + * For JSON_VALUE() and for JSON_QUERY() when OMIT QUOTES is true, + * ExecEvalJsonExprPath() will convert a quote-stripped source value to + * its text representation, so use TEXTOID as the source type. + */ + if (omit_quotes || jsexpr->op == JSON_VALUE_OP) + { + default_typid = TEXTOID; + default_typmod = -1; + } + else + { + default_typid = exprType(context_item); + default_typmod = exprTypmod(context_item); + } + if (returning->typid != default_typid || - returning->typmod != default_typmod || - omit_quotes) + returning->typmod != default_typmod) { /* * We abuse CaseTestExpr here as placeholder to pass the result of - * evaluating the JSON_VALUE/QUERY jsonpath expression as input to the - * coercion expression. + * jsonpath evaluation as input to the coercion expression. */ CaseTestExpr *placeholder = makeNode(CaseTestExpr); - /* - * When OMIT QUOTES is true, ExecEvalJsonCoercion() will convert a - * quote-stripped source value to its text representation, so use - * TEXTOID as the source type. - */ - placeholder->typeId = omit_quotes ? TEXTOID : exprType(context_item); - placeholder->typeMod = omit_quotes ? -1 : exprTypmod(context_item); - - Assert(placeholder->typeId == default_typid || - placeholder->typeId == TEXTOID); - Assert(placeholder->typeMod == default_typmod || - placeholder->typeMod == -1); + placeholder->typeId = default_typid; + placeholder->typeMod = default_typmod; - coercion_expr = coerceJsonExpr(pstate, (Node *) placeholder, - returning, omit_quotes); + coercion_expr = coerceJsonFuncExpr(pstate, (Node *) placeholder, + returning, false); + if (coercion_expr == (Node *) placeholder) + coercion_expr = NULL; } - return coercion_expr; -} - -/* Returns the default type for a given JsonExpr for a given JsonFormat. */ -static Oid -JsonFuncExprDefaultReturnType(JsonExpr *jsexpr) -{ - JsonFormat *format = jsexpr->format; - Node *context_item = jsexpr->formatted_expr; - - Assert(format); - if (format->format_type == JS_FORMAT_JSONB) - return JSONBOID; - else if (format->format_type == JS_FORMAT_DEFAULT && - exprType(context_item) == JSONBOID) - return JSONBOID; - - return JSONOID; -} - -/* - * Returns a JsonCoercion node to coerce a jsonb-valued expression to the - * target type given by 'returning' using either json_populate_type() or - * by using the target type's input function. - */ -static JsonCoercion * -makeJsonCoercion(const JsonReturning *returning, bool omit_quotes, - Node *cast_expr) -{ - JsonCoercion *coercion = makeNode(JsonCoercion); + jsexpr->coercion_expr = coercion_expr; - coercion->targettype = returning->typid; - coercion->targettypmod = returning->typmod; - coercion->omit_quotes = omit_quotes; - coercion->cast_expr = cast_expr; - - return coercion; -} - -/* - * Coerce the result of JSON_VALUE / JSON_QUERY () (or a behavior expression) - * to the output type - * - * Returns NULL if no coercion needed (the input expresssion is already of the - * desired type) and OMIT QUOTES is false. - * - * Returns a JsonCoercion node if the cast was not found or if OMIT QUOTES is - * true. - */ -static Node * -coerceJsonExpr(ParseState *pstate, Node *expr, const JsonReturning *returning, - bool omit_quotes) -{ - Node *coerced_expr; - - Assert(expr != NULL); - coerced_expr = coerceJsonFuncExpr(pstate, expr, returning, false); - - if (coerced_expr == expr && !omit_quotes) - return NULL; - - /* Use coerced_expr for JsonCoercion.cast_expr iff coercion is needed. */ - if (coerced_expr == NULL || omit_quotes) - return (Node *) makeJsonCoercion(returning, omit_quotes, - coerced_expr == expr ? - NULL : coerced_expr); - - return coerced_expr; -} - -/* - * Initialize JsonCoercion nodes for coercing a given JSON item value produced - * by JSON_VALUE to the target "returning" type; also see - * ExecPrepareJsonItemCoercion(). - */ -static List * -InitJsonItemCoercions(ParseState *pstate, const JsonReturning *returning, - Oid contextItemTypeId) -{ - List *item_coercions = NIL; - int i; - Oid typeoid; - struct - { - JsonItemType item_type; - Oid typeoid; - } item_types[] = - { - {JsonItemTypeNull, UNKNOWNOID}, - {JsonItemTypeString, TEXTOID}, - {JsonItemTypeNumeric, NUMERICOID}, - {JsonItemTypeBoolean, BOOLOID}, - {JsonItemTypeDate, DATEOID}, - {JsonItemTypeTime, TIMEOID}, - {JsonItemTypeTimetz, TIMETZOID}, - {JsonItemTypeTimestamp, TIMESTAMPOID}, - {JsonItemTypeTimestamptz, TIMESTAMPTZOID}, - {JsonItemTypeComposite, contextItemTypeId}, - {JsonItemTypeInvalid, InvalidOid} - }; - - for (i = 0; OidIsValid(typeoid = item_types[i].typeoid); i++) + if (coercion_expr == NULL) { - Node *expr; - JsonItemCoercion *item_coercion = makeNode(JsonItemCoercion); - - if (typeoid == UNKNOWNOID) - { - expr = (Node *) makeNullConst(UNKNOWNOID, -1, InvalidOid); - } + /* + * Either no cast was found or coercion is unnecessary but still must + * convert the string value to the output type. + */ + if (omit_quotes || jsexpr->op == JSON_VALUE_OP) + jsexpr->use_io_coercion = true; else - { - CaseTestExpr *placeholder = makeNode(CaseTestExpr); - - /* - * We abuse CaseTestExpr here as placeholder to pass the result of - * JSON_VALUE jsonpath expression to the coercion function. - */ - placeholder->typeId = item_types[i].typeoid; - placeholder->typeMod = -1; - placeholder->collation = InvalidOid; - - expr = (Node *) placeholder; - } - - item_coercion->item_type = item_types[i].item_type; - item_coercion->coercion = coerceJsonExpr(pstate, expr, returning, false); - item_coercions = lappend(item_coercions, item_coercion); + jsexpr->use_json_coercion = true; } - return item_coercions; + Assert(jsexpr->coercion_expr != NULL || + (jsexpr->use_io_coercion != jsexpr->use_json_coercion)); } /* @@ -4758,16 +4643,16 @@ transformJsonBehavior(ParseState *pstate, JsonBehavior *behavior, JsonBehaviorType default_behavior, JsonReturning *returning) { - JsonBehaviorType behavior_type = default_behavior; + JsonBehaviorType btype = default_behavior; Node *expr = NULL; - JsonCoercion *coercion = NULL; + bool coerce = false; int location = -1; if (behavior) { - behavior_type = behavior->btype; + btype = behavior->btype; location = behavior->location; - if (behavior_type == JSON_BEHAVIOR_DEFAULT) + if (btype == JSON_BEHAVIOR_DEFAULT) { expr = transformExprRecurse(pstate, behavior->expr); if (!IsA(expr, Const) && !IsA(expr, FuncExpr) && @@ -4791,8 +4676,8 @@ transformJsonBehavior(ParseState *pstate, JsonBehavior *behavior, } } - if (expr == NULL && behavior_type != JSON_BEHAVIOR_ERROR) - expr = GetJsonBehaviorConstExpr(behavior_type, location); + if (expr == NULL && btype != JSON_BEHAVIOR_ERROR) + expr = GetJsonBehaviorConstExpr(btype, location); if (expr) { @@ -4801,15 +4686,15 @@ transformJsonBehavior(ParseState *pstate, JsonBehavior *behavior, /* * Coerce NULLs and "internal" (that is, not specified by the user) - * jsonb-valued expressions with a JsonCoercion node. + * jsonb-valued expressions at runtime using json_populate_type(). * * For other (user-specified) non-NULL values, try to find a cast and * error out if one is not found. */ if (isnull || (exprType(expr) == JSONBOID && - behavior_type == default_behavior)) - coercion = makeJsonCoercion(returning, false, NULL); + btype == default_behavior)) + coerce = true; else coerced_expr = coerce_to_target_type(pstate, expr, exprType(expr), @@ -4828,5 +4713,12 @@ transformJsonBehavior(ParseState *pstate, JsonBehavior *behavior, expr = coerced_expr; } - return makeJsonBehavior(behavior_type, expr, coercion, location); + if (behavior) + behavior->expr = expr; + else + behavior = makeJsonBehavior(btype, expr, location); + + behavior->coerce = coerce; + + return behavior; } diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 383efe5b6c..1276f33604 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -2019,6 +2019,9 @@ FigureColnameInternal(Node *node, char **name) case JSON_VALUE_OP: *name = "json_value"; return 2; + default: + elog(ERROR, "unrecognized JsonExpr op: %d", + (int) ((JsonFuncExpr *) node)->op); } break; default: diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 0539c2424f..155274d7e8 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -9886,6 +9886,9 @@ get_rule_expr(Node *node, deparse_context *context, case JSON_VALUE_OP: appendStringInfoString(buf, "JSON_VALUE("); break; + default: + elog(ERROR, "unrecognized JsonExpr op: %d", + (int) jexpr->op); } get_rule_expr(jexpr->formatted_expr, context, showimplicit); diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index 4bbf4acdf6..64698202a5 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -705,9 +705,8 @@ typedef struct ExprEvalStep /* for EEOP_JSONEXPR_COERCION */ struct { - JsonCoercion *coercion; - FmgrInfo *input_finfo; - Oid typioparam; + Oid targettype; + int32 targettypmod; void *json_populate_type_cache; ErrorSaveContext *escontext; } jsonexpr_coercion; diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index cd2ce63fa1..b17fa52d82 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1028,6 +1028,10 @@ typedef struct JsonExprState /* JsonPathVariable entries for passing_values */ List *args; + /* Type input function info for JSON_VALUE() result coercion. */ + FmgrInfo *input_finfo; + FunctionCallInfo input_fcinfo; + /* * Addresses of steps that implement the non-ERROR variant of ON EMPTY * and ON ERROR behaviors, respectively. @@ -1039,22 +1043,10 @@ typedef struct JsonExprState * Addresses of steps to coerce the result value of jsonpath evaluation to * the RETURNING type. * - * jump_eval_result_coercion points to the step to evaluate the coercion - * given in JsonExpr.result_coercion. -1 if no coercion is necessary. - * - * Only valid for JSON_VALUE, eval_item_coercion_jumps is an array of - * num_item_coercions elements each containing a step address to coerce - * a value of given JsonItemType returned by JsonPathValue() to the - * RETURNING type, or -1 if no coercion is necessary. - * item_coercion_is_cast is an array of boolean flags of the same length - * that indicates whether each valid step address in the - * eval_item_coercion_jumps array corresponds to a cast expression or a - * JsonCoercion node. + * jump_eval_coercion_expr points to the step to evaluate the coercion + * given in JsonExpr.coercion_expr. -1 if no coercion is necessary. */ - int jump_eval_result_coercion; - int num_item_coercions; - int *eval_item_coercion_jumps; - bool *item_coercion_is_cast; + int jump_eval_coercion_expr; /* * Address to jump to when skipping all the steps after performing diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h index 91d95fc52b..fdc78270e5 100644 --- a/src/include/nodes/makefuncs.h +++ b/src/include/nodes/makefuncs.h @@ -112,11 +112,11 @@ extern JsonFormat *makeJsonFormat(JsonFormatType type, JsonEncoding encoding, int location); extern JsonValueExpr *makeJsonValueExpr(Expr *raw_expr, Expr *formatted_expr, JsonFormat *format); -extern JsonBehavior *makeJsonBehavior(JsonBehaviorType type, Node *expr, - JsonCoercion *coercion, int location); extern Node *makeJsonKeyValue(Node *key, Node *value); extern Node *makeJsonIsPredicate(Node *expr, JsonFormat *format, JsonValueType item_type, bool unique_keys, int location); +extern JsonBehavior *makeJsonBehavior(JsonBehaviorType btype, Node *expr, + int location); #endif /* MAKEFUNC_H */ diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 377982f8fa..6da09bcb5f 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -1725,77 +1725,13 @@ typedef enum JsonBehaviorType JSON_BEHAVIOR_DEFAULT, } JsonBehaviorType; -/* - * JsonCoercion - * Information about coercing a SQL/JSON value to the specified - * type at runtime - * - * A node of this type is created if the parser cannot find a cast expression - * using coerce_type() or if OMIT QUOTES is specified for JSON_QUERY; see - * coerceJsonFuncExprOutput(). - * - * If it's the latter, 'cast_expr' may contain the cast expression, evaluated - * separately from this node, that will do the actual coercion of the - * quote-stripped string. If no cast expression is given, the string will be - * coerced by calling the target type's input function. See - * ExecEvalJsonCoercion(). - */ -typedef struct JsonCoercion -{ - NodeTag type; - - Oid targettype; - int32 targettypmod; - bool omit_quotes; /* OMIT QUOTES specified for JSON_QUERY? */ - Node *cast_expr; /* coercion cast expression or NULL */ - Oid collation; -} JsonCoercion; - -/* - * JsonItemType - * Possible types for scalar values returned by JSON_VALUE() - * - * The comment next to each item type mentions the corresponding - * JsonbValue.jbvType. - */ -typedef enum JsonItemType -{ - JsonItemTypeNull, /* jbvNull */ - JsonItemTypeString, /* jbvString */ - JsonItemTypeNumeric, /* jbvNumeric */ - JsonItemTypeBoolean, /* jbvBool */ - JsonItemTypeDate, /* jbvDatetime: DATEOID */ - JsonItemTypeTime, /* jbvDatetime: TIMEOID */ - JsonItemTypeTimetz, /* jbvDatetime: TIMETZOID */ - JsonItemTypeTimestamp, /* jbvDatetime: TIMESTAMPOID */ - JsonItemTypeTimestamptz, /* jbvDatetime: TIMESTAMPTZOID */ - JsonItemTypeComposite, /* jbvArray, jbvObject, jbvBinary */ - JsonItemTypeInvalid, -} JsonItemType; - -/* - * JsonItemCoercion - * Coercion expression for the given JsonItemType - * - * If not NULL, 'coercion' given the expression node to convert a scalar value - * extracted from a JsonbValue of the given type to the target type given by - * JsonExpr.returning. NULL means the coercion is unnecessary. - */ -typedef struct JsonItemCoercion -{ - NodeTag type; - - JsonItemType item_type; - Node *coercion; -} JsonItemCoercion; - /* * JsonBehavior * Information about ON ERROR / ON EMPTY behaviors of JSON_VALUE(), * JSON_QUERY(), and JSON_EXISTS() * * 'expr' is the expression to emit when a given behavior (EMPTY or ERROR) - * occurs on evaluating the SQL/JSON query function. 'coercion' is set + * occurs on evaluating the SQL/JSON query function. 'coerce' is set to true * if 'expr' isn't already of the expected target type given by * JsonExpr.returning. */ @@ -1805,8 +1741,7 @@ typedef struct JsonBehavior JsonBehaviorType btype; Node *expr; - JsonCoercion *coercion; /* to coerce behavior expression when there is - * no cast to the target type */ + bool coerce; int location; /* token location, or -1 if unknown */ } JsonBehavior; @@ -1853,16 +1788,17 @@ typedef struct JsonExpr JsonBehavior *on_error; /* - * Expression to convert the result of jsonpath functions to the RETURNING - * type - */ - Node *result_coercion; - - /* - * List of expressions for coercing JSON_VALUE() result values, containing - * one element for every JsonItemType. + * Information about converting the result of jsonpath functions + * JsonPathQuery() and JsonPathValue() to the RETURNING type. + * + * coercion_expr is a cast expression if the parser can find it for the + * source and the target type. If not, either use_io_coercion or + * use_json_coercion is set to determine the coercion method to use at + * runtime; see coerceJsonExprOutput() and ExecInitJsonExpr(). */ - List *item_coercions; + Node *coercion_expr; + bool use_io_coercion; + bool use_json_coercion; /* WRAPPER specification for JSON_QUERY */ JsonWrapper wrapper; @@ -1870,6 +1806,9 @@ typedef struct JsonExpr /* KEEP or OMIT QUOTES for singleton scalars returned by JSON_QUERY() */ bool omit_quotes; + /* JsonExpr's collation, if coercion_expr is NULL. */ + Oid collation; + /* Original JsonFuncExpr's location */ int location; } JsonExpr; diff --git a/src/test/regress/expected/sqljson_queryfuncs.out b/src/test/regress/expected/sqljson_queryfuncs.out index f5b57465d6..5a537d0655 100644 --- a/src/test/regress/expected/sqljson_queryfuncs.out +++ b/src/test/regress/expected/sqljson_queryfuncs.out @@ -1,10 +1,4 @@ -- JSON_EXISTS --- json arguments currently not supported -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 SELECT JSON_EXISTS(NULL::jsonb, '$'); json_exists ------------- @@ -147,12 +141,6 @@ SELECT JSON_EXISTS(jsonb '1', '$.a > 2' ERROR ON ERROR); (1 row) -- JSON_VALUE --- json arguments currently not supported -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 SELECT JSON_VALUE(NULL::jsonb, '$'); json_value ------------ @@ -174,7 +162,7 @@ SELECT JSON_VALUE(jsonb 'null', '$' RETURNING int); SELECT JSON_VALUE(jsonb 'true', '$'); json_value ------------ - true + t (1 row) SELECT JSON_VALUE(jsonb 'true', '$' RETURNING bool); @@ -203,7 +191,11 @@ SELECT JSON_VALUE(jsonb '123', '$' RETURNING text); /* jsonb bytea ??? */ SELECT JSON_VALUE(jsonb '123', '$' RETURNING bytea ERROR ON ERROR); -ERROR: SQL/JSON item cannot be cast to target type + json_value +------------ + \x313233 +(1 row) + SELECT JSON_VALUE(jsonb '1.23', '$'); json_value ------------ @@ -213,7 +205,7 @@ SELECT JSON_VALUE(jsonb '1.23', '$'); SELECT JSON_VALUE(jsonb '1.23', '$' RETURNING int); json_value ------------ - 1 + (1 row) SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING numeric); @@ -329,11 +321,7 @@ SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT 2 ON (1 row) SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT NULL ON EMPTY ERROR ON ERROR); - json_value ------------- - -(1 row) - +ERROR: domain sqljsonb_int_not_null does not allow null values CREATE TYPE rainbow AS ENUM ('red', 'orange', 'yellow', 'green', 'blue', 'purple'); CREATE DOMAIN rgb AS rainbow CHECK (VALUE IN ('red', 'green', 'blue')); SELECT JSON_VALUE('"purple"'::jsonb, 'lax $[*]' RETURNING rgb); @@ -433,7 +421,7 @@ SELECT JSON_VALUE(jsonb '["1"]', '$[*]' RETURNING int DEFAULT 2 + 3 ON ERROR); (1 row) SELECT JSON_VALUE(jsonb '["1"]', '$[*]' RETURNING int FORMAT JSON); -- RETURNING FORMAT not allowed -ERROR: cannot specify FORMAT in RETURNING clause of JSON_VALUE() +ERROR: cannot specify FORMAT JSON in RETURNING clause of JSON_VALUE() LINE 1: ...CT JSON_VALUE(jsonb '["1"]', '$[*]' RETURNING int FORMAT JSO... ^ -- RETUGNING pseudo-types not allowed @@ -531,13 +519,12 @@ SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 + "2018-02-21T02:34:56+00:00" (1 row) +-- Test that numeric JSON values are coerced uniformly +select json_value('{"a": 1.234}', '$.a' returning int error on error); +ERROR: invalid input syntax for type integer: "1.234" +select json_value('{"a": "1.234"}', '$.a' returning int error on error); +ERROR: invalid input syntax for type integer: "1.234" -- JSON_QUERY --- json arguments currently not supported -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 SELECT JSON_VALUE(NULL::jsonb, '$'); json_value ------------ @@ -1259,3 +1246,24 @@ SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a' WITH WRAPPER); -- Should fail (invalid path) SELECT JSON_QUERY(jsonb '{"a": 123}', 'error' || ' ' || 'error'); ERROR: syntax error at or near " " of jsonpath input +-- Non-jsonb inputs automatically coerced to jsonb +SELECT JSON_EXISTS(json '{"a": 123}', '$' || '.' || 'a'); + json_exists +------------- + t +(1 row) + +SELECT JSON_QUERY(NULL FORMAT JSON, '$'); + json_query +------------ + +(1 row) + +-- Test non-const jsonpath +CREATE TEMP TABLE jsonpaths (path) AS SELECT '$'; +SELECT json_value('"aaa"', path RETURNING json) FROM jsonpaths; + json_value +------------ + "aaa" +(1 row) + diff --git a/src/test/regress/sql/sqljson_queryfuncs.sql b/src/test/regress/sql/sqljson_queryfuncs.sql index 5be2d8e3f8..d01b172376 100644 --- a/src/test/regress/sql/sqljson_queryfuncs.sql +++ b/src/test/regress/sql/sqljson_queryfuncs.sql @@ -1,8 +1,4 @@ -- JSON_EXISTS - --- json arguments currently not supported -SELECT JSON_EXISTS(NULL FORMAT JSON, '$'); - SELECT JSON_EXISTS(NULL::jsonb, '$'); SELECT JSON_EXISTS(jsonb '[]', '$'); SELECT JSON_EXISTS(JSON_OBJECT(RETURNING jsonb), '$'); @@ -35,10 +31,6 @@ SELECT JSON_EXISTS(jsonb '1', '$ > 2'); SELECT JSON_EXISTS(jsonb '1', '$.a > 2' ERROR ON ERROR); -- JSON_VALUE - --- json arguments currently not supported -SELECT JSON_VALUE(NULL FORMAT JSON, '$'); - SELECT JSON_VALUE(NULL::jsonb, '$'); SELECT JSON_VALUE(jsonb 'null', '$'); @@ -143,10 +135,11 @@ SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamp '2018-02-21 12:34:56 +10 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 +-- Test that numeric JSON values are coerced uniformly +select json_value('{"a": 1.234}', '$.a' returning int error on error); +select json_value('{"a": "1.234"}', '$.a' returning int error on error); --- json arguments currently not supported -SELECT JSON_QUERY(NULL FORMAT JSON, '$'); +-- JSON_QUERY SELECT JSON_VALUE(NULL::jsonb, '$'); @@ -425,3 +418,11 @@ SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a'); SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a' WITH WRAPPER); -- Should fail (invalid path) SELECT JSON_QUERY(jsonb '{"a": 123}', 'error' || ' ' || 'error'); + +-- Non-jsonb inputs automatically coerced to jsonb +SELECT JSON_EXISTS(json '{"a": 123}', '$' || '.' || 'a'); +SELECT JSON_QUERY(NULL FORMAT JSON, '$'); + +-- Test non-const jsonpath +CREATE TEMP TABLE jsonpaths (path) AS SELECT '$'; +SELECT json_value('"aaa"', path RETURNING json) FROM jsonpaths; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 616e315ec4..7b0395510d 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1280,7 +1280,6 @@ JsonArrayQueryConstructor JsonBaseObjectInfo JsonBehavior JsonBehaviorType -JsonCoercion JsonConstructorExpr JsonConstructorExprState JsonConstructorType @@ -1293,7 +1292,6 @@ JsonFormat JsonFormatType JsonHashEntry JsonIsPredicate -JsonItemCoercion JsonItemType JsonIterateStringValuesAction JsonKeyValue -- 2.43.0