From 8d69709a04d32bbed8505efff4b2bb40c9334b84 Mon Sep 17 00:00:00 2001 From: Dmitrii Dolgov <9erthalion6@gmail.com> Date: Thu, 19 Dec 2019 14:08:59 +0100 Subject: [PATCH v30 5/6] Polymorphic subscripting To improve performance and compliance with the SQL standard (in regards how float indexes are considered), interpret each subscript expression variant depending on the result of previous subscripting step. There are two variants of jsonb subscript expressions - the first is casted to text and the second is casted to int4. Executor at each subscripting step selects which variant to execute by calling callback jsonb_subscript_selectexpr(). To manage the subscripting state, another callback jsonb_subscript_step() was introduced along with the new field SubscriptingRefState.privatedata. Author: Nikita Glukhov --- src/backend/executor/execExpr.c | 226 ++++++-- src/backend/executor/execExprInterp.c | 87 ++- src/backend/utils/adt/arrayfuncs.c | 4 +- src/backend/utils/adt/jsonb_util.c | 6 +- src/backend/utils/adt/jsonfuncs.c | 785 ++++++++++++++++++++------ src/backend/utils/adt/jsonpath_exec.c | 2 +- src/include/executor/execExpr.h | 26 +- src/include/nodes/subscripting.h | 14 +- src/include/utils/jsonb.h | 2 +- src/test/regress/expected/jsonb.out | 233 +++++++- src/test/regress/sql/jsonb.sql | 80 +++ 11 files changed, 1211 insertions(+), 254 deletions(-) diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index a756f4ba8d..96b2edf2e1 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -2534,6 +2534,144 @@ ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable, ExprState *state) } } +static void +ExecInitSubscriptExpr(ExprEvalStep *scratch, SubscriptingRefState *sbsrefstate, + ExprState *state, Node *expr, int off, bool isupper, + List **adjust_jumps) +{ + /* Each subscript is evaluated into subscriptvalue/subscriptnull */ + ExecInitExprRec((Expr *) expr, state, + &sbsrefstate->subscriptvalue, + &sbsrefstate->subscriptnull); + + /* ... and then SBSREF_SUBSCRIPT saves it into step's workspace */ + scratch->opcode = EEOP_SBSREF_SUBSCRIPT; + scratch->d.sbsref_subscript.state = sbsrefstate; + scratch->d.sbsref_subscript.typid = exprType(expr); + scratch->d.sbsref_subscript.off = off; + scratch->d.sbsref_subscript.isupper = isupper; + scratch->d.sbsref_subscript.jumpdone = -1; /* adjust later */ + ExprEvalPushStep(state, scratch); + + *adjust_jumps = lappend_int(*adjust_jumps, state->steps_len - 1); +} + +/* Init subscript expressions. */ +static void +ExecInitSubscript(ExprEvalStep *scratch, SubscriptingRefState *sbsrefstate, + ExprState *state, Node *expr, int i, bool isupper, + List **adjust_jumps, bool *isprovided, Oid *exprtype) +{ + List *exprs = NULL; + int nexprs = 0; + int select_step; + + /* When slicing, individual subscript bounds can be omitted */ + *isprovided = expr != NULL; + if (!*isprovided) + return; + + /* + * Node can be a list of expression variants. The first variant is + * an unmodified expression, other variants can be NULL, so we need + * to check if there are any non-NULL and emit SELECTEXPR if any. + */ + if (IsA(expr, List)) + { + exprs = (List *) expr; + expr = linitial(exprs); + + if (list_length(exprs) > 1) + { + ListCell *lc = list_head(exprs); + + while ((lc = lnext(exprs, lc))) + { + if (lfirst(lc)) + { + nexprs = list_length(exprs) - 1; + break; + } + } + } + } + + *exprtype = exprType(expr); + + /* Emit SELECTEXPR step if there are expression variants */ + if (nexprs) + { + scratch->opcode = EEOP_SBSREF_SELECTEXPR; + scratch->d.sbsref_selectexpr.state = sbsrefstate; + scratch->d.sbsref_selectexpr.off = i; + scratch->d.sbsref_selectexpr.isupper = isupper; + scratch->d.sbsref_selectexpr.nexprs = nexprs; + scratch->d.sbsref_selectexpr.exprtypes = palloc(sizeof(Oid) * nexprs); + scratch->d.sbsref_selectexpr.jumpdones = palloc(sizeof(int) * nexprs); + ExprEvalPushStep(state, scratch); + select_step = state->steps_len - 1; + } + + /* Emit main expression */ + ExecInitSubscriptExpr(scratch, sbsrefstate, state, expr, i, isupper, + adjust_jumps); + + /* Emit additional expression variants, if any */ + if (nexprs) + { + ListCell *lc = list_head(exprs); + List *adjust_subexpr_jumps = NIL; + int j = 0; + + /* Skip first expression which is already emitted */ + while ((lc = lnext(exprs, lc))) + { + int jumpdone; + Oid exprtype; + ExprEvalStep *step; + + expr = lfirst(lc); + + if (expr) + { + /* Emit JUMP to the end for previous expression */ + scratch->opcode = EEOP_JUMP; + scratch->d.jump.jumpdone = -1; /* adjust later */ + ExprEvalPushStep(state, scratch); + + adjust_subexpr_jumps = lappend_int(adjust_subexpr_jumps, + state->steps_len - 1); + + exprtype = exprType((Node *) expr); + jumpdone = state->steps_len; + + ExecInitSubscriptExpr(scratch, sbsrefstate, state, expr, + i, isupper, adjust_jumps); + } + else + { + exprtype = InvalidOid; + jumpdone = -1; + } + + step = &state->steps[select_step]; + step->d.sbsref_selectexpr.exprtypes[j] = exprtype; + step->d.sbsref_selectexpr.jumpdones[j] = jumpdone; + + j++; + } + + /* Adjust JUMPs for expression variants */ + foreach(lc, adjust_subexpr_jumps) + { + ExprEvalStep *step = &state->steps[lfirst_int(lc)]; + + Assert(step->opcode == EEOP_JUMP); + step->d.jump.jumpdone = state->steps_len; + } + } +} + /* * Prepare evaluation of a SubscriptingRef expression. */ @@ -2541,12 +2679,14 @@ static void ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref, ExprState *state, Datum *resv, bool *resnull) { - bool isAssignment = (sbsref->refassgnexpr != NULL); SubscriptingRefState *sbsrefstate = palloc0(sizeof(SubscriptingRefState)); - List *adjust_jumps = NIL; - ListCell *lc; - int i; - RegProcedure typsubshandler = get_typsubsprocs(sbsref->refcontainertype); + List *adjust_jumps = NIL; + ListCell *lc; + ListCell *ulc; + ListCell *llc; + int i; + RegProcedure typsubshandler = get_typsubsprocs(sbsref->refcontainertype); + bool isAssignment = (sbsref->refassgnexpr != NULL); /* Fill constant fields of SubscriptingRefState */ sbsrefstate->isassignment = isAssignment; @@ -2576,71 +2716,43 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref, state->steps_len - 1); } - /* Evaluate upper subscripts */ - i = 0; - foreach(lc, sbsref->refupperindexpr) + /* Emit INIT step if needed. */ + if (sbsrefstate->sbsroutines->init) { - Expr *e = (Expr *) lfirst(lc); - - /* When slicing, individual subscript bounds can be omitted */ - if (!e) - { - sbsrefstate->upperprovided[i] = false; - i++; - continue; - } - - sbsrefstate->upperprovided[i] = true; - - /* Each subscript is evaluated into subscriptvalue/subscriptnull */ - ExecInitExprRec(e, state, - &sbsrefstate->subscriptvalue, &sbsrefstate->subscriptnull); - - /* ... and then SBSREF_SUBSCRIPT saves it into step's workspace */ - scratch->opcode = EEOP_SBSREF_SUBSCRIPT; - scratch->d.sbsref_subscript.state = sbsrefstate; - scratch->d.sbsref_subscript.off = i; - scratch->d.sbsref_subscript.isupper = true; - scratch->d.sbsref_subscript.jumpdone = -1; /* adjust later */ + scratch->opcode = EEOP_SBSREF_INIT; + scratch->d.sbsref.state = sbsrefstate; ExprEvalPushStep(state, scratch); - adjust_jumps = lappend_int(adjust_jumps, - state->steps_len - 1); - i++; } - sbsrefstate->numupper = i; - /* Evaluate lower subscripts similarly */ + /* Evaluate upper and lower subscripts */ i = 0; - foreach(lc, sbsref->reflowerindexpr) + llc = list_head(sbsref->reflowerindexpr); + + sbsrefstate->numlower = 0; + + foreach(ulc, sbsref->refupperindexpr) { - Expr *e = (Expr *) lfirst(lc); + ExecInitSubscript(scratch, sbsrefstate, state, lfirst(ulc), i, + true, &adjust_jumps, + &sbsrefstate->upperprovided[i], + &sbsrefstate->uppertypid[i]); - /* When slicing, individual subscript bounds can be omitted */ - if (!e) + if (llc) { - sbsrefstate->lowerprovided[i] = false; - i++; - continue; - } + ExecInitSubscript(scratch, sbsrefstate, state, lfirst(llc), + i, false, &adjust_jumps, + &sbsrefstate->lowerprovided[i], + &sbsrefstate->lowertypid[i]); - sbsrefstate->lowerprovided[i] = true; + llc = lnext(sbsref->reflowerindexpr, llc); - /* Each subscript is evaluated into subscriptvalue/subscriptnull */ - ExecInitExprRec(e, state, - &sbsrefstate->subscriptvalue, &sbsrefstate->subscriptnull); + sbsrefstate->numlower++; + } - /* ... and then SBSREF_SUBSCRIPT saves it into step's workspace */ - scratch->opcode = EEOP_SBSREF_SUBSCRIPT; - scratch->d.sbsref_subscript.state = sbsrefstate; - scratch->d.sbsref_subscript.off = i; - scratch->d.sbsref_subscript.isupper = false; - scratch->d.sbsref_subscript.jumpdone = -1; /* adjust later */ - ExprEvalPushStep(state, scratch); - adjust_jumps = lappend_int(adjust_jumps, - state->steps_len - 1); i++; } - sbsrefstate->numlower = i; + + sbsrefstate->numupper = i; /* Should be impossible if parser is sane, but check anyway: */ if (sbsrefstate->numlower != 0 && diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 0a103bb403..841a15e4a8 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -416,6 +416,8 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) &&CASE_EEOP_FIELDSELECT, &&CASE_EEOP_FIELDSTORE_DEFORM, &&CASE_EEOP_FIELDSTORE_FORM, + &&CASE_EEOP_SBSREF_INIT, + &&CASE_EEOP_SBSREF_SELECTEXPR, &&CASE_EEOP_SBSREF_SUBSCRIPT, &&CASE_EEOP_SBSREF_OLD, &&CASE_EEOP_SBSREF_ASSIGN, @@ -1395,6 +1397,29 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } + EEO_CASE(EEOP_SBSREF_INIT) + { + /* too complex for an inline implementation */ + ExecEvalSubscriptingRefInit(state, op); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_SBSREF_SELECTEXPR) + { + /* too complex for an inline implementation */ + int selectedExpr = ExecEvalSubscriptingRefSelect(state, op); + + /* + * Jump to selected expression variant or simply continue + * to the first (0th) expression + */ + if (selectedExpr > 0) + EEO_JUMP(op->d.sbsref_selectexpr.jumpdones[selectedExpr - 1]); + else + EEO_NEXT(); + } + EEO_CASE(EEOP_SBSREF_SUBSCRIPT) { /* Process an array subscript */ @@ -3119,6 +3144,46 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext *op->resnull = false; } +/* + * Initialize subscripting state. + */ +void +ExecEvalSubscriptingRefInit(ExprState *state, ExprEvalStep *op) +{ + SubscriptingRefState *sbsrefstate = op->d.sbsref_selectexpr.state; + SubscriptRoutines *sbsroutines = sbsrefstate->sbsroutines; + + /* init private subsripting state */ + sbsroutines->init(sbsrefstate, *op->resvalue, *op->resnull); +} + +/* + * Select expression variant for subscript evaluation + */ +int +ExecEvalSubscriptingRefSelect(ExprState *state, ExprEvalStep *op) +{ + SubscriptingRefState *sbsrefstate = op->d.sbsref_selectexpr.state; + SubscriptRoutines *sbsroutines = sbsrefstate->sbsroutines; + Oid *typids = op->d.sbsref_selectexpr.isupper ? + sbsrefstate->uppertypid : sbsrefstate->lowertypid; + int off = op->d.sbsref_selectexpr.off; + Oid *exprtypes = op->d.sbsref_selectexpr.exprtypes; + Oid typid = typids[off]; + int selected; + + selected = sbsroutines->selectexpr(sbsrefstate, off, typid, exprtypes, + op->d.sbsref_selectexpr.nexprs); + + if (selected) + { + Assert(OidIsValid(exprtypes[selected])); + typids[off] = exprtypes[selected - 1]; + } + + return selected; +} + /* * Process a subscript in a SubscriptingRef expression. * @@ -3134,8 +3199,10 @@ bool ExecEvalSubscriptingRef(ExprState *state, ExprEvalStep *op) { SubscriptingRefState *sbsrefstate = op->d.sbsref_subscript.state; - Datum *indexes; - int off; + SubscriptRoutines *sbsroutines = sbsrefstate->sbsroutines; + Datum *indexes; + int off; + bool isupper; /* If any index expr yields NULL, result is NULL or error */ if (sbsrefstate->subscriptnull) @@ -3148,15 +3215,24 @@ ExecEvalSubscriptingRef(ExprState *state, ExprEvalStep *op) return false; } - /* Convert datum to int, save in appropriate place */ - if (op->d.sbsref_subscript.isupper) + off = op->d.sbsref_subscript.off; + isupper = op->d.sbsref_subscript.isupper; + + /* Save converted datum in appropriate place */ + if (isupper) indexes = sbsrefstate->upperindex; else indexes = sbsrefstate->lowerindex; - off = op->d.sbsref_subscript.off; indexes[off] = sbsrefstate->subscriptvalue; + if (sbsroutines->step && + !sbsroutines->step(sbsrefstate, off, isupper)) + { + *op->resnull = true; + return false; + } + return true; } @@ -3174,7 +3250,6 @@ ExecEvalSubscriptingRefFetch(ExprState *state, ExprEvalStep *op) /* Should not get here if source container (or any subscript) is null */ Assert(!(*op->resnull)); - *op->resvalue = sbsroutines->fetch(*op->resvalue, sbsrefstate); *op->resnull = sbsrefstate->resnull; } diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 01bc3e8ac5..7dcef967cd 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -6729,7 +6729,7 @@ Datum array_subscript_handler(PG_FUNCTION_ARGS) { SubscriptRoutines *sbsroutines = (SubscriptRoutines *) - palloc(sizeof(SubscriptRoutines)); + palloc0(sizeof(SubscriptRoutines)); sbsroutines->prepare = array_subscript_prepare; sbsroutines->validate = array_subscript_validate; @@ -6863,7 +6863,7 @@ array_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("array assignment requires type %s" " but expression is of type %s", - format_type_be(sbsref->refelemtype), + format_type_be(typeneeded), format_type_be(typesource)), errhint("You will need to rewrite or cast the expression."), parser_errposition(pstate, exprLocation(assignExpr)))); diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c index adcf16acf1..78664ed451 100644 --- a/src/backend/utils/adt/jsonb_util.c +++ b/src/backend/utils/adt/jsonb_util.c @@ -469,9 +469,8 @@ getKeyJsonValueFromContainer(JsonbContainer *container, * Returns palloc()'d copy of the value, or NULL if it does not exist. */ JsonbValue * -getIthJsonbValueFromContainer(JsonbContainer *container, uint32 i) +getIthJsonbValueFromContainer(JsonbContainer *container, uint32 i, JsonbValue *result) { - JsonbValue *result; char *base_addr; uint32 nelements; @@ -484,7 +483,8 @@ getIthJsonbValueFromContainer(JsonbContainer *container, uint32 i) if (i >= nelements) return NULL; - result = palloc(sizeof(JsonbValue)); + if (!result) + result = palloc(sizeof(JsonbValue)); fillJsonbValue(container, i, base_addr, getJsonbOffset(container, i), diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c index 538740aec9..82d4a98e7e 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -335,6 +335,23 @@ typedef struct JsObject static int report_json_context(JsonLexContext *lex); +/* state for assignment of a single subscript */ +typedef struct JsonbSubscriptState +{ + bool exists; /* does this element exist? */ + bool is_array; /* is it array or object? */ + int array_size; /* size of array */ + int array_index; /* index in array (negative means prepending) */ +} JsonbSubscriptState; + +/* state for subscript assignment */ +typedef struct JsonbAssignState +{ + JsonbParseState *ps; /* jsonb building state */ + JsonbIterator *iter; /* source jsonb iterator */ + JsonbSubscriptState subscripts[MAX_SUBSCRIPT_DEPTH + 1]; /* per-subscript states */ +} JsonbAssignState; + /* semantic action functions for json_object_keys */ static void okeys_object_field_start(void *state, char *fname, bool isnull); static void okeys_array_start(void *state); @@ -464,8 +481,6 @@ static Datum populate_domain(DomainIOData *io, Oid typid, const char *colname, /* functions supporting jsonb_delete, jsonb_set and jsonb_concat */ static JsonbValue *IteratorConcat(JsonbIterator **it1, JsonbIterator **it2, JsonbParseState **state); -static Datum jsonb_set_element(Datum datum, Datum *path, int path_len, - Datum sourceData, Oid source_type, bool is_null); static Datum jsonb_get_element(Jsonb *jb, Datum *path, int npath, bool *isnull, bool as_text); static JsonbValue *setPath(JsonbIterator **it, Datum *path_elems, @@ -474,12 +489,13 @@ static JsonbValue *setPath(JsonbIterator **it, Datum *path_elems, int op_type); static void setPathObject(JsonbIterator **it, Datum *path_elems, bool *path_nulls, int path_len, JsonbParseState **st, - int level, - JsonbValue *newval, uint32 npairs, int op_type); + int level, JsonbValue *newval, int op_type); static void setPathArray(JsonbIterator **it, Datum *path_elems, bool *path_nulls, int path_len, JsonbParseState **st, int level, JsonbValue *newval, uint32 nelems, int op_type); +static bool copyJsonbObject(JsonbParseState **st, JsonbIterator **it, + const JsonbValue *key); /* function supporting iterate_json_values */ static void iterate_values_scalar(void *state, char *token, JsonTokenType tokentype); @@ -932,6 +948,7 @@ jsonb_array_element(PG_FUNCTION_ARGS) Jsonb *jb = PG_GETARG_JSONB_P(0); int element = PG_GETARG_INT32(1); JsonbValue *v; + JsonbValue vbuf; if (!JB_ROOT_IS_ARRAY(jb)) PG_RETURN_NULL(); @@ -947,7 +964,7 @@ jsonb_array_element(PG_FUNCTION_ARGS) element += nelements; } - v = getIthJsonbValueFromContainer(&jb->root, element); + v = getIthJsonbValueFromContainer(&jb->root, element, &vbuf); if (v != NULL) PG_RETURN_JSONB_P(JsonbValueToJsonb(v)); @@ -975,6 +992,7 @@ jsonb_array_element_text(PG_FUNCTION_ARGS) Jsonb *jb = PG_GETARG_JSONB_P(0); int element = PG_GETARG_INT32(1); JsonbValue *v; + JsonbValue vbuf; if (!JB_ROOT_IS_ARRAY(jb)) PG_RETURN_NULL(); @@ -990,7 +1008,7 @@ jsonb_array_element_text(PG_FUNCTION_ARGS) element += nelements; } - v = getIthJsonbValueFromContainer(&jb->root, element); + v = getIthJsonbValueFromContainer(&jb->root, element, &vbuf); if (v != NULL && v->type != jbvNull) PG_RETURN_TEXT_P(JsonbValueAsText(v)); @@ -1010,6 +1028,26 @@ json_extract_path_text(PG_FUNCTION_ARGS) return get_path_all(fcinfo, true); } +static inline bool +jsonb_get_array_index_from_cstring(char *indexstr, long *index) +{ + char *endptr; + + errno = 0; + *index = strtol(indexstr, &endptr, 10); + if (endptr == indexstr || *endptr != '\0' || errno != 0 || + *index > INT_MAX || *index < INT_MIN) + return false; + + return true; +} + +static inline bool +jsonb_get_array_index_from_text(Datum indextext, long *index) +{ + return jsonb_get_array_index_from_cstring(TextDatumGetCString(indextext), index); +} + /* * common routine for extract_path functions */ @@ -1055,11 +1093,8 @@ get_path_all(FunctionCallInfo fcinfo, bool as_text) if (*tpath[i] != '\0') { long ind; - char *endptr; - errno = 0; - ind = strtol(tpath[i], &endptr, 10); - if (*endptr == '\0' && errno == 0 && ind <= INT_MAX && ind >= INT_MIN) + if (jsonb_get_array_index_from_cstring(tpath[i], &ind)) ipath[i] = (int) ind; else ipath[i] = INT_MIN; @@ -1503,13 +1538,362 @@ get_jsonb_path_all(FunctionCallInfo fcinfo, bool as_text) PG_RETURN_DATUM(res); } +/* Initialize private jsonb subscripting state. */ +static void +jsonb_subscript_init(SubscriptingRefState *sbstate, Datum container, bool isnull) +{ + Jsonb *jb = isnull ? NULL : DatumGetJsonbP(container); + + if (sbstate->isassignment) + { + JsonbAssignState *astate = palloc0(sizeof(*astate)); + JsonbSubscriptState *subscript = &astate->subscripts[0]; + + astate->ps = NULL; + + if (jb) + { + JsonbValue jbv; + JsonbIteratorToken tok; + + astate->iter = JsonbIteratorInit(&jb->root); + + tok = JsonbIteratorNext(&astate->iter, &jbv, false); + + if (tok == WJB_BEGIN_ARRAY) + { + if (jbv.val.array.rawScalar) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot assign to subscript of scalar jsonb"))); + + subscript->is_array = true; + subscript->array_size = jbv.val.array.nElems; + } + else + subscript->is_array = false; + + subscript->exists = true; + } + else + { + astate->iter = NULL; + subscript->exists = false; + } + + sbstate->privatedata = astate; + } + else + { + JsonbValue *jbv; + + /* Initialize a binary JsonbValue and use it as a private state */ + if (jb) + { + jbv = palloc(sizeof(*jbv)); + + jbv->type = jbvBinary; + jbv->val.binary.data = &jb->root; + jbv->val.binary.len = VARSIZE(jb) - VARHDRSZ; + } + else + jbv = NULL; + + sbstate->privatedata = jbv; + } +} + +/* + * Select subscript expression variant. + * + * There are two expression variants of jsonb subscripts: + * 0th - unmodified expression + * 1st - expression casted to int4 (optional, if type is numeric) + * + * If the current jsonb is an array then we select 1st variant, otherwise + * default 0th variant is selected. + */ +static int +jsonb_subscript_selectexpr(SubscriptingRefState *sbstate, int num, + Oid subscriptType, Oid *exprTypes, int nExprs) +{ + bool is_array = false; + + Assert(nExprs == 1); + Assert(!OidIsValid(exprTypes[0]) || exprTypes[0] == INT4OID); + + if (sbstate->isassignment) + { + JsonbAssignState *astate = sbstate->privatedata; + JsonbSubscriptState *subscript = &astate->subscripts[num]; + + if (!subscript->exists) + { + /* NULL can be only in assignments, select int4 variant if available. */ + Assert(sbstate->isassignment); + + subscript->is_array = OidIsValid(exprTypes[0]); + subscript->array_size = 0; + } + + is_array = subscript->is_array; + } + else + { + JsonbValue *jbv = sbstate->privatedata; + + Assert(jbv); + + if (jbv->type == jbvBinary) + { + JsonbContainer *jbc = jbv->val.binary.data; + + if (JsonContainerIsArray(jbc) && !JsonContainerIsScalar(jbc)) + is_array = true; + } + } + + if (is_array && OidIsValid(exprTypes[0])) + return 1; + + return 0; +} + +/* Get the integer index from a subscript datum */ +static int32 +jsonb_subscript_get_array_index(Datum value, Oid typid, int num, + int arraySize, bool isAssignment) +{ + long lindex; + + if (typid == INT4OID) + lindex = DatumGetInt32(value); + else if (typid != TEXTOID) + elog(ERROR, "invalid jsonb subscript type: %u", typid); + else if (!jsonb_get_array_index_from_text(value, &lindex)) + { + if (isAssignment) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("jsonb array subscript is not an integer: \"%s\"", + TextDatumGetCString(value)))); + return -1; + } + + if (lindex >= 0) + return lindex; + else /* handle negative subscript */ + return arraySize + lindex; +} + +/* Get the string key from a subscript datum */ +static char * +jsonb_subscript_get_object_key(Datum value, Oid typid, int *len) +{ + if (typid == TEXTOID) + { + *len = VARSIZE_ANY_EXHDR(value); + + return VARDATA_ANY(value); + } + else if (typid == INT4OID) + { + char *key = DatumGetCString(DirectFunctionCall1(int4out, value)); + + *len = strlen(key); + + return key; + } + else + { + elog(ERROR, "invalid jsonb subscript type: %u", typid); + return NULL; + } +} + +/* Apply single susbscript to jsonb container */ +static inline JsonbValue * +jsonb_subscript_apply(JsonbValue *jbv, Datum subscriptVal, Oid subscriptTypid, + int subscriptIdx) +{ + JsonbContainer *jbc; + + if (jbv->type != jbvBinary || + JsonContainerIsScalar(jbv->val.binary.data)) + return NULL; /* scalar, extraction yields a null */ + + jbc = jbv->val.binary.data; + + if (JsonContainerIsObject(jbc)) + { + int keylen; + char *keystr = jsonb_subscript_get_object_key(subscriptVal, + subscriptTypid, + &keylen); + + return getKeyJsonValueFromContainer(jbc, keystr, keylen, jbv); + } + else if (JsonContainerIsArray(jbc) && !JsonContainerIsScalar(jbc)) + { + int32 index = jsonb_subscript_get_array_index(subscriptVal, + subscriptTypid, + subscriptIdx, + JsonContainerSize(jbc), + false); + + if (index < 0) + return NULL; + + return getIthJsonbValueFromContainer(jbc, index, jbv); + } + else + { + /* scalar, extraction yields a null */ + return NULL; + } +} + +/* Perfrom one subscript assignment step */ +static void +jsonb_subscript_step_assignment(SubscriptingRefState *sbstate, Datum value, + Oid typid, int num, bool isupper) +{ + JsonbAssignState *astate = sbstate->privatedata; + JsonbSubscriptState *subscript = &astate->subscripts[num]; + JsonbIteratorToken tok; + JsonbValue jbv; + bool last = num >= sbstate->numupper - 1; + + if (!subscript->exists) + { + /* Select the type of newly created container. */ + if (typid == INT4OID) + { + subscript->is_array = true; + subscript->array_size = 0; + } + else if (typid == TEXTOID) + subscript->is_array = false; + else + elog(ERROR, "invalid jsonb subscript type: %u", typid); + } + + subscript[1].exists = false; + + if (subscript->is_array) + { + int32 i = 0; + int32 index = jsonb_subscript_get_array_index(value, typid, num, + subscript->array_size, + true); + + pushJsonbValue(&astate->ps, WJB_BEGIN_ARRAY, NULL); + + subscript->array_index = index; + + if (index >= 0 && subscript->exists) + { + /* Try to copy preceding elements */ + for (; i < index; i++) + { + tok = JsonbIteratorNext(&astate->iter, &jbv, true); + + if (tok != WJB_ELEM) + break; + + pushJsonbValue(&astate->ps, tok, &jbv); + } + + /* Try to read replaced element */ + if (i >= index && + JsonbIteratorNext(&astate->iter, &jbv, last) != WJB_END_ARRAY) + subscript[1].exists = true; + } + } + else + { + JsonbValue key; + + key.type = jbvString; + key.val.string.val = jsonb_subscript_get_object_key(value, typid, + &key.val.string.len); + + pushJsonbValue(&astate->ps, WJB_BEGIN_OBJECT, NULL); + + if (subscript->exists && + copyJsonbObject(&astate->ps, &astate->iter, &key)) + { + subscript[1].exists = true; /* key is found */ + tok = JsonbIteratorNext(&astate->iter, &jbv, last); + } + + pushJsonbValue(&astate->ps, WJB_KEY, &key); + } + + /* If the value does exits, process and validate its type. */ + if (subscript[1].exists) + { + if (jbv.type == jbvArray) + { + subscript[1].is_array = true; + subscript[1].array_size = jbv.val.array.nElems; + } + else + subscript[1].is_array = false; + + if (!last && IsAJsonbScalar(&jbv)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot assign to subscript of scalar jsonb"))); + } +} + +/* Perform one subscripting step */ +static bool +jsonb_subscript_step(SubscriptingRefState *sbstate, int num, bool isupper) +{ + Datum value; + Oid typid; + + if (isupper) + { + value = sbstate->upperindex[num]; + typid = sbstate->uppertypid[num]; + } + else + elog(ERROR, "jsonb subscript cannot be lower"); + + if (sbstate->isassignment) + { + jsonb_subscript_step_assignment(sbstate, value, typid, num, isupper); + + return true; /* always process next subscripts */ + } + else + { + /* + * Perform one subscripting step by applying subscript value to current + * jsonb container and saving the result into private state. + */ + JsonbValue *jbv = sbstate->privatedata; + + Assert(jbv); /* NULL can only be in assignments */ + + jbv = jsonb_subscript_apply(jbv, value, typid, num); + + sbstate->privatedata = jbv; + + /* Process next subscripts only if the result is not NULL */ + return jbv != NULL; + } +} + static Datum jsonb_get_element(Jsonb *jb, Datum *path, int npath, bool *isnull, bool as_text) { - Jsonb *res; - JsonbContainer *container = &jb->root; + JsonbValue jbv; JsonbValue *jbvp = NULL; - JsonbValue tv; + JsonbContainer *container = &jb->root; int i; bool have_object = false, have_array = false; @@ -1517,16 +1901,16 @@ jsonb_get_element(Jsonb *jb, Datum *path, int npath, bool *isnull, bool as_text) *isnull = false; /* Identify whether we have object, array, or scalar at top-level */ - if (JB_ROOT_IS_OBJECT(jb)) + if (JsonContainerIsObject(container)) have_object = true; - else if (JB_ROOT_IS_ARRAY(jb) && !JB_ROOT_IS_SCALAR(jb)) + else if (JsonContainerIsArray(container) && !JsonContainerIsScalar(container)) have_array = true; else { - Assert(JB_ROOT_IS_ARRAY(jb) && JB_ROOT_IS_SCALAR(jb)); + Assert(JsonContainerIsArray(container) && JsonContainerIsScalar(container)); /* Extract the scalar value, if it is what we'll return */ if (npath <= 0) - jbvp = getIthJsonbValueFromContainer(container, 0); + jbvp = getIthJsonbValueFromContainer(container, 0, &jbv); } /* @@ -1559,19 +1943,14 @@ jsonb_get_element(Jsonb *jb, Datum *path, int npath, bool *isnull, bool as_text) jbvp = getKeyJsonValueFromContainer(container, VARDATA(path[i]), VARSIZE(path[i]) - VARHDRSZ, - NULL); + jbvp); } else if (have_array) { long lindex; uint32 index; - char *indextext = TextDatumGetCString(path[i]); - char *endptr; - errno = 0; - lindex = strtol(indextext, &endptr, 10); - if (endptr == indextext || *endptr != '\0' || errno != 0 || - lindex > INT_MAX || lindex < INT_MIN) + if (!jsonb_get_array_index_from_text(path[i], &lindex)) { *isnull = true; return PointerGetDatum(NULL); @@ -1601,7 +1980,7 @@ jsonb_get_element(Jsonb *jb, Datum *path, int npath, bool *isnull, bool as_text) index = nelements + lindex; } - jbvp = getIthJsonbValueFromContainer(container, index); + jbvp = getIthJsonbValueFromContainer(container, index, jbvp); } else { @@ -1652,32 +2031,6 @@ jsonb_get_element(Jsonb *jb, Datum *path, int npath, bool *isnull, bool as_text) } } -Datum -jsonb_set_element(Datum jsonbdatum, Datum *path, int path_len, - Datum sourceData, Oid source_type, bool is_null) -{ - Jsonb *jb = DatumGetJsonbP(jsonbdatum); - JsonbValue *newval, - *res; - JsonbParseState *state = NULL; - JsonbIterator *it; - bool *path_nulls = palloc0(path_len * sizeof(bool)); - - newval = to_jsonb_worker(sourceData, source_type, is_null); - - if (newval->type == jbvArray && newval->val.array.rawScalar) - *newval = newval->val.array.elems[0]; - - it = JsonbIteratorInit(&jb->root); - - res = setPath(&it, path, path_nulls, path_len, &state, 0, - newval, JB_PATH_CREATE); - - pfree(path_nulls); - - PG_RETURN_JSONB_P(JsonbValueToJsonb(res)); -} - /* * Return the text representation of the given JsonbValue. */ @@ -4859,10 +5212,8 @@ setPath(JsonbIterator **it, Datum *path_elems, case WJB_BEGIN_OBJECT: (void) pushJsonbValue(st, r, NULL); setPathObject(it, path_elems, path_nulls, path_len, st, level, - newval, v.val.object.nPairs, op_type); - r = JsonbIteratorNext(it, &v, true); - Assert(r == WJB_END_OBJECT); - res = pushJsonbValue(st, r, NULL); + newval, op_type); + res = pushJsonbValue(st, WJB_END_OBJECT, NULL); break; case WJB_ELEM: case WJB_VALUE: @@ -4878,109 +5229,118 @@ setPath(JsonbIterator **it, Datum *path_elems, } /* - * Object walker for setPath + * Copy object fields, but stop on the desired key if it is specified. + * + * True is returned if the key was found, otherwise false. */ -static void -setPathObject(JsonbIterator **it, Datum *path_elems, bool *path_nulls, - int path_len, JsonbParseState **st, int level, - JsonbValue *newval, uint32 npairs, int op_type) +static bool +copyJsonbObject(JsonbParseState **st, JsonbIterator **it, const JsonbValue *key) { - int i; - JsonbValue k, - v; - bool done = false; - - if (level >= path_len || path_nulls[level]) - done = true; + JsonbIteratorToken r; + JsonbValue keybuf; - /* empty object is a special case for create */ - if ((npairs == 0) && (op_type & JB_PATH_CREATE_OR_INSERT) && - (level == path_len - 1)) + while ((r = JsonbIteratorNext(it, &keybuf, true)) == WJB_KEY) { - JsonbValue newkey; + JsonbValue val; - newkey.type = jbvString; - newkey.val.string.len = VARSIZE_ANY_EXHDR(path_elems[level]); - newkey.val.string.val = VARDATA_ANY(path_elems[level]); + if (key && + key->val.string.len == keybuf.val.string.len && + memcmp(key->val.string.val, keybuf.val.string.val, + key->val.string.len) == 0) + return true; /* stop, key is found */ - (void) pushJsonbValue(st, WJB_KEY, &newkey); - (void) pushJsonbValue(st, WJB_VALUE, newval); - } - - for (i = 0; i < npairs; i++) - { - JsonbIteratorToken r = JsonbIteratorNext(it, &k, true); + (void) pushJsonbValue(st, r, &keybuf); - Assert(r == WJB_KEY); + /* Copy value */ + r = JsonbIteratorNext(it, &val, false); + (void) pushJsonbValue(st, r, r < WJB_BEGIN_ARRAY ? &val : NULL); - if (!done && - k.val.string.len == VARSIZE_ANY_EXHDR(path_elems[level]) && - memcmp(k.val.string.val, VARDATA_ANY(path_elems[level]), - k.val.string.len) == 0) + if (r == WJB_BEGIN_ARRAY || r == WJB_BEGIN_OBJECT) { - if (level == path_len - 1) - { - /* - * called from jsonb_insert(), it forbids redefining an - * existing value - */ - if (op_type & (JB_PATH_INSERT_BEFORE | JB_PATH_INSERT_AFTER)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("cannot replace existing key"), - errhint("Try using the function jsonb_set " - "to replace key value."))); + int walking_level = 1; - r = JsonbIteratorNext(it, &v, true); /* skip value */ - if (!(op_type & JB_PATH_DELETE)) - { - (void) pushJsonbValue(st, WJB_KEY, &k); - (void) pushJsonbValue(st, WJB_VALUE, newval); - } - done = true; - } - else + while (walking_level != 0) { - (void) pushJsonbValue(st, r, &k); - setPath(it, path_elems, path_nulls, path_len, - st, level + 1, newval, op_type); + r = JsonbIteratorNext(it, &val, false); + + if (r == WJB_BEGIN_ARRAY || r == WJB_BEGIN_OBJECT) + ++walking_level; + if (r == WJB_END_ARRAY || r == WJB_END_OBJECT) + --walking_level; + + (void) pushJsonbValue(st, r, r < WJB_BEGIN_ARRAY ? &val : NULL); } } - else - { - if ((op_type & JB_PATH_CREATE_OR_INSERT) && !done && - level == path_len - 1 && i == npairs - 1) - { - JsonbValue newkey; + } - newkey.type = jbvString; - newkey.val.string.len = VARSIZE_ANY_EXHDR(path_elems[level]); - newkey.val.string.val = VARDATA_ANY(path_elems[level]); + Assert(r == WJB_END_OBJECT); - (void) pushJsonbValue(st, WJB_KEY, &newkey); - (void) pushJsonbValue(st, WJB_VALUE, newval); - } + return false; /* key was not found */ +} - (void) pushJsonbValue(st, r, &k); - r = JsonbIteratorNext(it, &v, false); - (void) pushJsonbValue(st, r, r < WJB_BEGIN_ARRAY ? &v : NULL); - if (r == WJB_BEGIN_ARRAY || r == WJB_BEGIN_OBJECT) - { - int walking_level = 1; +/* + * Object walker for setPath + */ +static void +setPathObject(JsonbIterator **it, Datum *path_elems, bool *path_nulls, + int path_len, JsonbParseState **st, int level, + JsonbValue *newval, int op_type) +{ + JsonbValue *key, + keybuf, + val; - while (walking_level != 0) - { - r = JsonbIteratorNext(it, &v, false); + if (level >= path_len || path_nulls[level]) + key = NULL; + else + { + key = &keybuf; + key->type = jbvString; + key->val.string.val = VARDATA_ANY(path_elems[level]); + key->val.string.len = VARSIZE_ANY_EXHDR(path_elems[level]); + } - if (r == WJB_BEGIN_ARRAY || r == WJB_BEGIN_OBJECT) - ++walking_level; - if (r == WJB_END_ARRAY || r == WJB_END_OBJECT) - --walking_level; + /* Start copying object fields and stop on the desired key. */ + if (copyJsonbObject(st, it, key)) + { + /* The desired key was found. */ + if (level == path_len - 1) + { + /* + * called from jsonb_insert(), it forbids redefining an + * existing value + */ + if (op_type & (JB_PATH_INSERT_BEFORE | JB_PATH_INSERT_AFTER)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot replace existing key"), + errhint("Try using the function jsonb_set " + "to replace key value."))); - (void) pushJsonbValue(st, r, r < WJB_BEGIN_ARRAY ? &v : NULL); - } + (void) JsonbIteratorNext(it, &val, true); /* skip value */ + + if (!(op_type & JB_PATH_DELETE)) + { + (void) pushJsonbValue(st, WJB_KEY, key); + (void) pushJsonbValue(st, WJB_VALUE, newval); } } + else + { + (void) pushJsonbValue(st, WJB_KEY, key); + setPath(it, path_elems, path_nulls, path_len, st, level + 1, + newval, op_type); + } + + /* Copy the remaining fields. */ + (void) copyJsonbObject(st, it, NULL); + } + else if (key && (op_type & JB_PATH_CREATE_OR_INSERT) && + level == path_len - 1) + { + /* All fields were copied, but the desired key was not found. */ + (void) pushJsonbValue(st, WJB_KEY, key); + (void) pushJsonbValue(st, WJB_VALUE, newval); } } @@ -5000,18 +5360,14 @@ setPathArray(JsonbIterator **it, Datum *path_elems, bool *path_nulls, /* pick correct index */ if (level < path_len && !path_nulls[level]) { - char *c = TextDatumGetCString(path_elems[level]); long lindex; - char *badp; - errno = 0; - lindex = strtol(c, &badp, 10); - if (errno != 0 || badp == c || *badp != '\0' || lindex > INT_MAX || - lindex < INT_MIN) + if (!jsonb_get_array_index_from_text(path_elems[level], &lindex)) ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("path element at position %d is not an integer: \"%s\"", - level + 1, c))); + level + 1, TextDatumGetCString(path_elems[level])))); + idx = lindex; } else @@ -5113,14 +5469,12 @@ setPathArray(JsonbIterator **it, Datum *path_elems, bool *path_nulls, Datum jsonb_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) { - return jsonb_get_element(DatumGetJsonbP(containerSource), - sbstate->upperindex, - sbstate->numupper, - &sbstate->resnull, - false); -} + JsonbValue *jbv = sbstate->privatedata; + Assert(!sbstate->isassignment); + return JsonbPGetDatum(JsonbValueToJsonb(jbv)); +} /* * Perform an actual data extraction or modification for the jsonb @@ -5130,19 +5484,51 @@ jsonb_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) Datum jsonb_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) { - /* - * the original jsonb must be non-NULL, else we punt and return the - * original array. - */ + JsonbAssignState *astate = sbstate->privatedata; + JsonbSubscriptState *subscript = &astate->subscripts[sbstate->numupper - 1]; + JsonbValue *res = NULL; + JsonbValue *newval; + JsonbValue jbv; + JsonbIteratorToken tok; + + /* If the original jsonb is NULL, we will create a new container. */ if (sbstate->resnull) - return containerSource; + sbstate->resnull = false; - return jsonb_set_element(containerSource, - sbstate->upperindex, - sbstate->numupper, - sbstate->replacevalue, - sbstate->refelemtype, + /* Transform the new value to jsonb */ + newval = to_jsonb_worker(sbstate->replacevalue, sbstate->refelemtype, sbstate->replacenull); + + if (newval->type == jbvArray && newval->val.array.rawScalar) + *newval = newval->val.array.elems[0]; + + /* Push the new value */ + tok = subscript->is_array ? WJB_ELEM : WJB_VALUE; + res = pushJsonbValue(&astate->ps, tok, newval); + + /* Finish unclosed arrays/objects */ + for (; subscript >= astate->subscripts; subscript--) + { + /* + * If the element does exists, then all preceding subscripts must exist + * and the iterator may contain remaining elements. So we need to + * switch to copying from the iterator now. + */ + if (subscript[1].exists) + break; + + if (subscript->is_array && subscript->array_index < 0 && subscript->exists) + break; /* original elements are copied from the iterator */ + + tok = subscript->is_array ? WJB_END_ARRAY : WJB_END_OBJECT; + res = pushJsonbValue(&astate->ps, tok, NULL); + } + + /* Copy remaining elements from the iterator */ + while ((tok = JsonbIteratorNext(&astate->iter, &jbv, false)) != WJB_DONE) + res = pushJsonbValue(&astate->ps, tok, tok < WJB_BEGIN_ARRAY ? &jbv : NULL); + + return JsonbPGetDatum(JsonbValueToJsonb(res)); } /* @@ -5162,12 +5548,15 @@ Datum jsonb_subscript_handler(PG_FUNCTION_ARGS) { SubscriptRoutines *sbsroutines = (SubscriptRoutines *) - palloc(sizeof(SubscriptRoutines)); + palloc0(sizeof(SubscriptRoutines)); sbsroutines->prepare = jsonb_subscript_prepare; sbsroutines->validate = jsonb_subscript_validate; sbsroutines->fetch = jsonb_subscript_fetch; sbsroutines->assign = jsonb_subscript_assign; + sbsroutines->init = jsonb_subscript_init; + sbsroutines->step = jsonb_subscript_step; + sbsroutines->selectexpr = jsonb_subscript_selectexpr; PG_RETURN_POINTER(sbsroutines); } @@ -5190,19 +5579,37 @@ SubscriptingRef * jsonb_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, ParseState *pstate) { - List *upperIndexpr = NIL; - ListCell *l; + List *upperIndexpr = NIL; + ListCell *l; if (sbsref->reflowerindexpr != NIL) + { + Node *slice = NULL; + + /* Try to find first non-NULL lower subscript */ + foreach(l, sbsref->reflowerindexpr) + { + if (lfirst(l) != NULL) + { + slice = (Node *) lfirst(l); + break; + } + } + ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("jsonb subscript does not support slices"), - parser_errposition(pstate, exprLocation( - ((Node *) linitial(sbsref->reflowerindexpr)))))); + parser_errposition(pstate, exprLocation(slice)))); + } foreach(l, sbsref->refupperindexpr) { - Node *subexpr = (Node *) lfirst(l); + Node *subexpr = (Node *) lfirst(l); + Node *textexpr; + Node *intexpr; + Oid subexprType; + char typcategory; + bool typispreferred; if (subexpr == NULL) ereport(ERROR, @@ -5211,18 +5618,48 @@ jsonb_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, parser_errposition(pstate, exprLocation( ((Node *) linitial(sbsref->refupperindexpr)))))); - subexpr = coerce_to_target_type(pstate, - subexpr, exprType(subexpr), - TEXTOID, -1, - COERCION_ASSIGNMENT, - COERCE_IMPLICIT_CAST, - -1); - if (subexpr == NULL) + subexprType = exprType(subexpr); + + textexpr = coerce_to_target_type(pstate, + subexpr, subexprType, + TEXTOID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + + if (textexpr == NULL) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("jsonb subscript must have text type"), + errmsg("jsonb subscript must have %s type", "text"), parser_errposition(pstate, exprLocation(subexpr)))); + /* Try to coerce numeric types to int4 for array subscripting. */ + get_type_category_preferred(subexprType, &typcategory, &typispreferred); + + if (typcategory == TYPCATEGORY_NUMERIC) + { + intexpr = coerce_to_target_type(pstate, + subexpr, subexprType, + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + + if (intexpr && + (subexprType == INT4OID || + subexprType == INT2OID)) + textexpr = NULL; + } + else + intexpr = NULL; + + /* + * If int4 expression variant exists, create a list with both text and + * int4 variants. + */ + subexpr = textexpr && intexpr ? (Node *) list_make2(textexpr, intexpr) : + textexpr ? textexpr : intexpr; + upperIndexpr = lappend(upperIndexpr, subexpr); } diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index bc063061cf..d751343b4b 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -783,7 +783,7 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, else { v = getIthJsonbValueFromContainer(jb->val.binary.data, - (uint32) index); + (uint32) index, NULL); if (v == NULL) continue; diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index 846b834f2d..68bfe7aec5 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -185,6 +185,12 @@ typedef enum ExprEvalOp */ EEOP_FIELDSTORE_FORM, + /* Init subscripting */ + EEOP_SBSREF_INIT, + + /* Select an expression for container subscript evaluation */ + EEOP_SBSREF_SELECTEXPR, + /* Process a container subscript; short-circuit expression to NULL if NULL */ EEOP_SBSREF_SUBSCRIPT, @@ -494,17 +500,30 @@ typedef struct ExprEvalStep int ncolumns; } fieldstore; + /* for EEOP_SBSREF_SELECTEXPR */ + struct + { + /* too big to have inline */ + struct SubscriptingRefState *state; + int off; /* 0-based index of this subscript */ + bool isupper; /* is it upper or lower subscript? */ + int nexprs; /* subscript expression count */ + Oid *exprtypes; /* type oids of subscript expression variants */ + int *jumpdones; /* jumps to expression variants */ + } sbsref_selectexpr; + /* for EEOP_SBSREF_SUBSCRIPT */ struct { /* too big to have inline */ struct SubscriptingRefState *state; + Oid typid; /* type oid of subscript */ int off; /* 0-based index of this subscript */ bool isupper; /* is it upper or lower subscript? */ int jumpdone; /* jump here on null */ } sbsref_subscript; - /* for EEOP_SBSREF_OLD / ASSIGN / FETCH */ + /* for EEOP_SBSREF_OLD / ASSIGN / FETCH / INIT */ struct { /* too big to have inline */ @@ -651,11 +670,13 @@ typedef struct SubscriptingRefState /* at runtime, extracted subscript datums get stored in upperindex[] */ int numupper; bool upperprovided[MAX_SUBSCRIPT_DEPTH]; + Oid uppertypid[MAX_SUBSCRIPT_DEPTH]; Datum upperindex[MAX_SUBSCRIPT_DEPTH]; /* similarly for lower indexes, if any */ int numlower; bool lowerprovided[MAX_SUBSCRIPT_DEPTH]; + Oid lowertypid[MAX_SUBSCRIPT_DEPTH]; Datum lowerindex[MAX_SUBSCRIPT_DEPTH]; /* subscript expressions get evaluated into here */ @@ -672,6 +693,7 @@ typedef struct SubscriptingRefState bool resnull; struct SubscriptRoutines *sbsroutines; + void *privatedata; } SubscriptingRefState; @@ -716,6 +738,8 @@ extern void ExecEvalFieldStoreDeForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext); extern void ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext); +extern void ExecEvalSubscriptingRefInit(ExprState *state, ExprEvalStep *op); +extern int ExecEvalSubscriptingRefSelect(ExprState *state, ExprEvalStep *op); extern bool ExecEvalSubscriptingRef(ExprState *state, ExprEvalStep *op); extern void ExecEvalSubscriptingRefFetch(ExprState *state, ExprEvalStep *op); extern void ExecEvalSubscriptingRefOld(ExprState *state, ExprEvalStep *op); diff --git a/src/include/nodes/subscripting.h b/src/include/nodes/subscripting.h index 1800d5ecf5..d752bf6490 100644 --- a/src/include/nodes/subscripting.h +++ b/src/include/nodes/subscripting.h @@ -29,13 +29,25 @@ typedef Datum (*SubscriptingFetch) (Datum source, struct SubscriptingRefState *s typedef Datum (*SubscriptingAssign) (Datum source, struct SubscriptingRefState *sbrsefstate); +typedef void (*SubscriptingInit) (struct SubscriptingRefState *sbrefstate, + Datum source, bool isnull); + +typedef int (*SubscriptingSelectExpr) (struct SubscriptingRefState *sbsreftate, + int subscriptNum, Oid subscriptType, + Oid *subscriptExprTypes, int nexprs); + +typedef bool (*SubscriptingStep) (struct SubscriptingRefState *sbrefstate, + int subscriptNum, bool isupper); + typedef struct SubscriptRoutines { SubscriptingPrepare prepare; SubscriptingValidate validate; SubscriptingFetch fetch; SubscriptingAssign assign; - + SubscriptingInit init; + SubscriptingStep step; + SubscriptingSelectExpr selectexpr; } SubscriptRoutines; diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h index 6e3b75d56a..bbc962e559 100644 --- a/src/include/utils/jsonb.h +++ b/src/include/utils/jsonb.h @@ -386,7 +386,7 @@ extern JsonbValue *getKeyJsonValueFromContainer(JsonbContainer *container, const char *keyVal, int keyLen, JsonbValue *res); extern JsonbValue *getIthJsonbValueFromContainer(JsonbContainer *sheader, - uint32 i); + uint32 i, JsonbValue *result); extern JsonbValue *pushJsonbValue(JsonbParseState **pstate, JsonbIteratorToken seq, JsonbValue *jbval); extern JsonbIterator *JsonbIteratorInit(JsonbContainer *container); diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out index 04a146a7d0..16ffdcecf9 100644 --- a/src/test/regress/expected/jsonb.out +++ b/src/test/regress/expected/jsonb.out @@ -4758,12 +4758,48 @@ select ('[1, "2", null]'::jsonb)['1']; "2" (1 row) -select ('[1, "2", null]'::jsonb)[1.0]; +select ('[1, "2", null]'::jsonb)['1.0']; jsonb ------- (1 row) +select ('[1, "2", null]'::jsonb)[1.0]; + jsonb +------- + "2" +(1 row) + +select ('[1, "2", null]'::jsonb)[1.4]; + jsonb +------- + "2" +(1 row) + +select ('[1, "2", null]'::jsonb)[1.5]; + jsonb +------- + null +(1 row) + +select ('[1, "2", null]'::jsonb)[1.6]; + jsonb +------- + null +(1 row) + +select ('[1, "2", null]'::jsonb)[0.6]; + jsonb +------- + "2" +(1 row) + +select ('[1, "2", null]'::jsonb)['0.3'::numeric]; + jsonb +------- + 1 +(1 row) + select ('[1, "2", null]'::jsonb)[2]; jsonb ------- @@ -4848,6 +4884,54 @@ select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1 "ccc" (1 row) +select ('{"1": "a", "1.0": "b", "1.2": "c"}'::jsonb)[1]; + jsonb +------- + "a" +(1 row) + +select ('{"1": "a", "1.0": "b", "1.2": "c"}'::jsonb)[1.0]; + jsonb +------- + "b" +(1 row) + +select ('{"1": "a", "1.0": "b", "1.2": "c"}'::jsonb)[1.1]; + jsonb +------- + +(1 row) + +select ('{"1": "a", "1.0": "b", "1.2": "c"}'::jsonb)[1.2]; + jsonb +------- + "c" +(1 row) + +select ('{"1": "a", "1.0": "b", "1.2": "c"}'::jsonb)['1']; + jsonb +------- + "a" +(1 row) + +select ('{"1": "a", "1.0": "b", "1.2": "c"}'::jsonb)['1.0']; + jsonb +------- + "b" +(1 row) + +select ('{"1": "a", "1.0": "b", "1.2": "c"}'::jsonb)['1.1']; + jsonb +------- + +(1 row) + +select ('{"1": "a", "1.0": "b", "1.2": "c"}'::jsonb)['1.2']; + jsonb +------- + "c" +(1 row) + create TEMP TABLE test_jsonb_subscript ( id int, test_json jsonb @@ -4900,11 +4984,75 @@ select * from test_jsonb_subscript; 2 | {"a": [1, 2, 3], "key": "value"} (2 rows) +-- replace element in string subscript +update test_jsonb_subscript set test_json['a']['3'] = '4'::jsonb; +select * from test_jsonb_subscript; + id | test_json +----+------------------------------------- + 1 | {"a": [1, 2, 3, 4]} + 2 | {"a": [1, 2, 3, 4], "key": "value"} +(2 rows) + +-- bad array subscripts +update test_jsonb_subscript set test_json['a']['a'] = '5'::jsonb; +ERROR: jsonb array subscript is not an integer: "a" +select * from test_jsonb_subscript; + id | test_json +----+------------------------------------- + 1 | {"a": [1, 2, 3, 4]} + 2 | {"a": [1, 2, 3, 4], "key": "value"} +(2 rows) + +update test_jsonb_subscript set test_json['a'][0][1] = '5'::jsonb; +ERROR: cannot assign to subscript of scalar jsonb +select * from test_jsonb_subscript; + id | test_json +----+------------------------------------- + 1 | {"a": [1, 2, 3, 4]} + 2 | {"a": [1, 2, 3, 4], "key": "value"} +(2 rows) + +-- append element to array +update test_jsonb_subscript set test_json['a'][2.9] = '"4"'::jsonb; +select * from test_jsonb_subscript; + id | test_json +----+--------------------------------------- + 1 | {"a": [1, 2, 3, "4"]} + 2 | {"a": [1, 2, 3, "4"], "key": "value"} +(2 rows) + +-- append element to array with a gap filled with nulls +update test_jsonb_subscript set test_json['a'][7] = '8'::jsonb; +select * from test_jsonb_subscript; + id | test_json +----+------------------------------------------ + 1 | {"a": [1, 2, 3, "4", 8]} + 2 | {"a": [1, 2, 3, "4", 8], "key": "value"} +(2 rows) + +-- replace element in array using negative subscript +update test_jsonb_subscript set test_json['a'][-4] = '5'::jsonb; +select * from test_jsonb_subscript; + id | test_json +----+------------------------------------------ + 1 | {"a": [1, 5, 3, "4", 8]} + 2 | {"a": [1, 5, 3, "4", 8], "key": "value"} +(2 rows) + +-- prepend element to array using negative subscript with a gap filled with nulls +update test_jsonb_subscript set test_json['a'][-10] = '6'::jsonb; +select * from test_jsonb_subscript; + id | test_json +----+--------------------------------------------- + 1 | {"a": [6, 1, 5, 3, "4", 8]} + 2 | {"a": [6, 1, 5, 3, "4", 8], "key": "value"} +(2 rows) + -- use jsonb subscription in where clause select * from test_jsonb_subscript where test_json['key'] = '"value"'; - id | test_json -----+---------------------------------- - 2 | {"a": [1, 2, 3], "key": "value"} + id | test_json +----+--------------------------------------------- + 2 | {"a": [6, 1, 5, 3, "4", 8], "key": "value"} (1 row) select * from test_jsonb_subscript where test_json['key_doesnt_exists'] = '"value"'; @@ -4922,12 +5070,81 @@ update test_jsonb_subscript set test_json[NULL] = 1; ERROR: subscript in assignment must not be null update test_jsonb_subscript set test_json['another_key'] = NULL; select * from test_jsonb_subscript; - id | test_json -----+------------------------------------------------------- - 1 | {"a": [1, 2, 3], "another_key": null} - 2 | {"a": [1, 2, 3], "key": "value", "another_key": null} + id | test_json +----+------------------------------------------------------------------ + 1 | {"a": [6, 1, 5, 3, "4", 8], "another_key": null} + 2 | {"a": [6, 1, 5, 3, "4", 8], "key": "value", "another_key": null} (2 rows) +-- create a path +delete from test_jsonb_subscript; +insert into test_jsonb_subscript values (0, NULL); +update test_jsonb_subscript set test_json['a'] = '1'::jsonb; +select * from test_jsonb_subscript; + id | test_json +----+----------- + 0 | {"a": 1} +(1 row) + +update test_jsonb_subscript set test_json['b'][0] = '2'::jsonb; +select * from test_jsonb_subscript; + id | test_json +----+-------------------- + 0 | {"a": 1, "b": [2]} +(1 row) + +update test_jsonb_subscript set test_json['c'][0]['a'] = '3'::jsonb; +select * from test_jsonb_subscript; + id | test_json +----+------------------------------------- + 0 | {"a": 1, "b": [2], "c": [{"a": 3}]} +(1 row) + +update test_jsonb_subscript set test_json['d'][0]['a'][3] = '4'::jsonb; +select * from test_jsonb_subscript; + id | test_json +----+-------------------------------------------------------- + 0 | {"a": 1, "b": [2], "c": [{"a": 3}], "d": [{"a": [4]}]} +(1 row) + +update test_jsonb_subscript set test_json['d'][0]['c'][-3] = '5'::jsonb; +select * from test_jsonb_subscript; + id | test_json +----+------------------------------------------------------------------ + 0 | {"a": 1, "b": [2], "c": [{"a": 3}], "d": [{"a": [4], "c": [5]}]} +(1 row) + +update test_jsonb_subscript set test_json['d'][0]['b']['x'] = '6'::jsonb; +select * from test_jsonb_subscript; + id | test_json +----+--------------------------------------------------------------------------------- + 0 | {"a": 1, "b": [2], "c": [{"a": 3}], "d": [{"a": [4], "b": {"x": 6}, "c": [5]}]} +(1 row) + +update test_jsonb_subscript set test_json['e']['y'] = '7'::jsonb; +select * from test_jsonb_subscript; + id | test_json +----+------------------------------------------------------------------------------------------------ + 0 | {"a": 1, "b": [2], "c": [{"a": 3}], "d": [{"a": [4], "b": {"x": 6}, "c": [5]}], "e": {"y": 7}} +(1 row) + +-- updating of scalar's subscripts +update test_jsonb_subscript set test_json = '1'; +update test_jsonb_subscript set test_json['a'] = '1'::jsonb; +ERROR: cannot assign to subscript of scalar jsonb +update test_jsonb_subscript set test_json[0] = '1'::jsonb; +ERROR: cannot assign to subscript of scalar jsonb +update test_jsonb_subscript set test_json = '{"a": 1}'; +update test_jsonb_subscript set test_json['a']['a'] = '1'::jsonb; +ERROR: cannot assign to subscript of scalar jsonb +update test_jsonb_subscript set test_json['a'][0] = '1'::jsonb; +ERROR: cannot assign to subscript of scalar jsonb +select * from test_jsonb_subscript; + id | test_json +----+----------- + 0 | {"a": 1} +(1 row) + -- jsonb to tsvector select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb); to_tsvector diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql index 12541e7e50..997189f57f 100644 --- a/src/test/regress/sql/jsonb.sql +++ b/src/test/regress/sql/jsonb.sql @@ -1214,7 +1214,13 @@ select ('{"a": 1}'::jsonb)[NULL]; select ('[1, "2", null]'::jsonb)['a']; select ('[1, "2", null]'::jsonb)[0]; select ('[1, "2", null]'::jsonb)['1']; +select ('[1, "2", null]'::jsonb)['1.0']; select ('[1, "2", null]'::jsonb)[1.0]; +select ('[1, "2", null]'::jsonb)[1.4]; +select ('[1, "2", null]'::jsonb)[1.5]; +select ('[1, "2", null]'::jsonb)[1.6]; +select ('[1, "2", null]'::jsonb)[0.6]; +select ('[1, "2", null]'::jsonb)['0.3'::numeric]; select ('[1, "2", null]'::jsonb)[2]; select ('[1, "2", null]'::jsonb)[3]; select ('[1, "2", null]'::jsonb)[-2]; @@ -1229,6 +1235,15 @@ select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1' select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3']; select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1']; select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2]; +select ('{"1": "a", "1.0": "b", "1.2": "c"}'::jsonb)[1]; +select ('{"1": "a", "1.0": "b", "1.2": "c"}'::jsonb)[1.0]; +select ('{"1": "a", "1.0": "b", "1.2": "c"}'::jsonb)[1.1]; +select ('{"1": "a", "1.0": "b", "1.2": "c"}'::jsonb)[1.2]; +select ('{"1": "a", "1.0": "b", "1.2": "c"}'::jsonb)['1']; +select ('{"1": "a", "1.0": "b", "1.2": "c"}'::jsonb)['1.0']; +select ('{"1": "a", "1.0": "b", "1.2": "c"}'::jsonb)['1.1']; +select ('{"1": "a", "1.0": "b", "1.2": "c"}'::jsonb)['1.2']; + create TEMP TABLE test_jsonb_subscript ( id int, @@ -1259,6 +1274,35 @@ select * from test_jsonb_subscript; update test_jsonb_subscript set test_json['a'] = '[1, 2, 3]'::jsonb; select * from test_jsonb_subscript; +-- replace element in string subscript +update test_jsonb_subscript set test_json['a']['3'] = '4'::jsonb; +select * from test_jsonb_subscript; + +-- bad array subscripts +update test_jsonb_subscript set test_json['a']['a'] = '5'::jsonb; +select * from test_jsonb_subscript; + +update test_jsonb_subscript set test_json['a'][0][1] = '5'::jsonb; +select * from test_jsonb_subscript; + + +-- append element to array +update test_jsonb_subscript set test_json['a'][2.9] = '"4"'::jsonb; +select * from test_jsonb_subscript; + +-- append element to array with a gap filled with nulls +update test_jsonb_subscript set test_json['a'][7] = '8'::jsonb; +select * from test_jsonb_subscript; + +-- replace element in array using negative subscript +update test_jsonb_subscript set test_json['a'][-4] = '5'::jsonb; +select * from test_jsonb_subscript; + +-- prepend element to array using negative subscript with a gap filled with nulls +update test_jsonb_subscript set test_json['a'][-10] = '6'::jsonb; +select * from test_jsonb_subscript; + + -- use jsonb subscription in where clause select * from test_jsonb_subscript where test_json['key'] = '"value"'; select * from test_jsonb_subscript where test_json['key_doesnt_exists'] = '"value"'; @@ -1269,6 +1313,42 @@ update test_jsonb_subscript set test_json[NULL] = 1; update test_jsonb_subscript set test_json['another_key'] = NULL; select * from test_jsonb_subscript; +-- create a path +delete from test_jsonb_subscript; +insert into test_jsonb_subscript values (0, NULL); + +update test_jsonb_subscript set test_json['a'] = '1'::jsonb; +select * from test_jsonb_subscript; + +update test_jsonb_subscript set test_json['b'][0] = '2'::jsonb; +select * from test_jsonb_subscript; + +update test_jsonb_subscript set test_json['c'][0]['a'] = '3'::jsonb; +select * from test_jsonb_subscript; + +update test_jsonb_subscript set test_json['d'][0]['a'][3] = '4'::jsonb; +select * from test_jsonb_subscript; + +update test_jsonb_subscript set test_json['d'][0]['c'][-3] = '5'::jsonb; +select * from test_jsonb_subscript; + +update test_jsonb_subscript set test_json['d'][0]['b']['x'] = '6'::jsonb; +select * from test_jsonb_subscript; + +update test_jsonb_subscript set test_json['e']['y'] = '7'::jsonb; +select * from test_jsonb_subscript; + +-- updating of scalar's subscripts +update test_jsonb_subscript set test_json = '1'; +update test_jsonb_subscript set test_json['a'] = '1'::jsonb; +update test_jsonb_subscript set test_json[0] = '1'::jsonb; + +update test_jsonb_subscript set test_json = '{"a": 1}'; +update test_jsonb_subscript set test_json['a']['a'] = '1'::jsonb; +update test_jsonb_subscript set test_json['a'][0] = '1'::jsonb; + +select * from test_jsonb_subscript; + -- jsonb to tsvector select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb); -- 2.21.0