diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index 59b8a2e..447f2c2 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -2427,6 +2427,15 @@ JumbleExpr(pgssJumbleState *jstate, Node *node) JumbleExpr(jstate, (Node *) aref->refassgnexpr); } break; + case T_JsonbRef: + { + JsonbRef *jbref = (JsonbRef *) node; + + JumbleExpr(jstate, (Node *) jbref->refpathexpr); + JumbleExpr(jstate, (Node *) jbref->refexpr); + JumbleExpr(jstate, (Node *) jbref->refassgnexpr); + } + break; case T_FuncExpr: { FuncExpr *expr = (FuncExpr *) node; diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c index 81cb2b4..7809a1c 100644 --- a/contrib/postgres_fdw/deparse.c +++ b/contrib/postgres_fdw/deparse.c @@ -122,6 +122,7 @@ static void deparseVar(Var *node, deparse_expr_cxt *context); static void deparseConst(Const *node, deparse_expr_cxt *context); static void deparseParam(Param *node, deparse_expr_cxt *context); static void deparseArrayRef(ArrayRef *node, deparse_expr_cxt *context); +static void deparseJsonbRef(JsonbRef *node, deparse_expr_cxt *context); static void deparseFuncExpr(FuncExpr *node, deparse_expr_cxt *context); static void deparseOpExpr(OpExpr *node, deparse_expr_cxt *context); static void deparseOperatorName(StringInfo buf, Form_pg_operator opform); @@ -352,6 +353,38 @@ foreign_expr_walker(Node *node, state = FDW_COLLATE_UNSAFE; } break; + case T_JsonbRef: + { + JsonbRef *jb = (JsonbRef *) node; + + /* Assignment should not be in restrictions. */ + if (jb->refassgnexpr != NULL) + return false; + + /* + * Recurse to remaining subexpressions. + */ + if (!foreign_expr_walker((Node *) jb->refpathexpr, + glob_cxt, &inner_cxt)) + return false; + if (!foreign_expr_walker((Node *) jb->refexpr, + glob_cxt, &inner_cxt)) + return false; + + /* + * Jsonb subscripting should yield same collation as input, + * but for safety use same logic as for function nodes. + */ + collation = jb->refcollid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && + collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else + state = FDW_COLLATE_UNSAFE; + } + break; case T_FuncExpr: { FuncExpr *fe = (FuncExpr *) node; @@ -1230,6 +1263,9 @@ deparseExpr(Expr *node, deparse_expr_cxt *context) case T_ArrayRef: deparseArrayRef((ArrayRef *) node, context); break; + case T_JsonbRef: + deparseJsonbRef((JsonbRef *) node, context); + break; case T_FuncExpr: deparseFuncExpr((FuncExpr *) node, context); break; @@ -1493,6 +1529,45 @@ deparseArrayRef(ArrayRef *node, deparse_expr_cxt *context) } /* + * Deparse a jsonb subscript expression. + */ +static void +deparseJsonbRef(JsonbRef *node, deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + ListCell *lowlist_item; + ListCell *uplist_item; + + /* Always parenthesize the expression. */ + appendStringInfoChar(buf, '('); + + /* + * Deparse referenced jsonb expression first. If that expression includes + * a cast, we have to parenthesize to prevent the jsonb subscript from + * being taken as typename decoration. We can avoid that in the typical + * case of subscripting a Var, but otherwise do it. + */ + if (IsA(node->refexpr, Var)) + deparseExpr(node->refexpr, context); + else + { + appendStringInfoChar(buf, '('); + deparseExpr(node->refexpr, context); + appendStringInfoChar(buf, ')'); + } + + /* Deparse subscript expressions. */ + foreach(uplist_item, node->refpathexpr) + { + appendStringInfoChar(buf, '['); + deparseExpr(lfirst(uplist_item), context); + appendStringInfoChar(buf, ']'); + } + + appendStringInfoChar(buf, ')'); +} + +/* * Deparse a function call. */ static void diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 29f058c..ffcda22 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -57,12 +57,16 @@ #include "utils/memutils.h" #include "utils/typcache.h" #include "utils/xml.h" +#include "utils/jsonb.h" /* static function decls */ static Datum ExecEvalArrayRef(ArrayRefExprState *astate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalJsonbRef(JsonbRefExprState *astate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); static bool isAssignmentIndirectionExpr(ExprState *exprstate); static Datum ExecEvalAggref(AggrefExprState *aggref, ExprContext *econtext, @@ -481,6 +485,134 @@ ExecEvalArrayRef(ArrayRefExprState *astate, astate->refelemalign); } +/*---------- + * ExecEvalJsonbRef + * + * This function takes a JsonbRef and returns the extracted Datum + * if it's a simple reference, or the modified jsonb value if it's + * an assignment. + * + * NOTE: if we get a NULL result from a subscript expression, we return NULL + * when it's an array reference, or raise an error when it's an assignment. + *---------- + */ +static Datum +ExecEvalJsonbRef(JsonbRefExprState *jbstate, + ExprContext *econtext, + bool *isNull, + ExprDoneCond *isDone) +{ + JsonbRef *jsonbRef = (JsonbRef *) jbstate->xprstate.expr; + Datum array_source; + bool isAssignment = (jsonbRef->refassgnexpr != NULL); + bool eisnull; + ListCell *l; + int i = 0; + text **path; + + array_source = ExecEvalExpr(jbstate->refexpr, + econtext, + isNull, + isDone); + + /* + * If refexpr yields NULL, and it's a fetch, then result is NULL. In the + * assignment case, we'll cons up something below. + */ + if (*isNull) + { + if (isDone && *isDone == ExprEndResult) + return (Datum) NULL; /* end of set result */ + if (!isAssignment) + return (Datum) NULL; + } + + path = (text **) palloc(jbstate->refpathexpr->length * sizeof(text*)); + + foreach(l, jbstate->refpathexpr) + { + ExprState *eltstate = (ExprState *) lfirst(l); + + path[i++] = cstring_to_text((char *)DatumGetPointer(ExecEvalExpr(eltstate, + econtext, + &eisnull, + NULL))); + + /* If any index expr yields NULL, result is NULL or error */ + if (eisnull) + { + if (isAssignment) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("array subscript in assignment must not be null"))); + *isNull = true; + return (Datum) NULL; + } + } + + if (isAssignment) + { + Datum sourceData; + Datum save_datum; + bool save_isNull; + + /* + * We might have a nested-assignment situation, in which the + * refassgnexpr is itself a FieldStore or ArrayRef that needs to + * obtain and modify the previous value of the array element or slice + * being replaced. If so, we have to extract that value from the + * array and pass it down via the econtext's caseValue. It's safe to + * reuse the CASE mechanism because there cannot be a CASE between + * here and where the value would be needed, and an array assignment + * can't be within a CASE either. (So saving and restoring the + * caseValue is just paranoia, but let's do it anyway.) + * + * Since fetching the old element might be a nontrivial expense, do it + * only if the argument appears to actually need it. + */ + save_datum = econtext->caseValue_datum; + save_isNull = econtext->caseValue_isNull; + + /* + * Evaluate the value to be assigned into the array. + */ + sourceData = ExecEvalExpr(jbstate->refassgnexpr, + econtext, + &eisnull, + NULL); + + econtext->caseValue_datum = save_datum; + econtext->caseValue_isNull = save_isNull; + + /* + * For an assignment to a fixed-length array type, both the original + * array and the value to be assigned into it must be non-NULL, else + * we punt and return the original array. + */ + if (jbstate->refattrlength > 0) /* fixed-length array? */ + if (eisnull || *isNull) + return array_source; + + /* + * For assignment to varlena arrays, we handle a NULL original array + * by substituting an empty (zero-dimensional) array; insertion of the + * new element will result in a singleton array value. It does not + * matter whether the new element is NULL. + */ + if (*isNull) + { + array_source = PointerGetDatum(construct_empty_array(jsonbRef->refelemtype)); + *isNull = false; + } + + return jsonb_set_element(array_source, path, i, sourceData, + ((const JsonbRef *) jbstate->xprstate.expr)->refelemtype); + } + else + return jsonb_get_element(array_source, path, i, isNull); +} + + /* * Helper for ExecEvalArrayRef: is expr a nested FieldStore or ArrayRef * that might need the old element value passed down? @@ -507,6 +639,14 @@ isAssignmentIndirectionExpr(ExprState *exprstate) if (arrayRef->refexpr && IsA(arrayRef->refexpr, CaseTestExpr)) return true; } + else if (IsA(exprstate, JsonbRefExprState)) + { + JsonbRef *jsonbRef = (JsonbRef *) exprstate->expr; + + if (jsonbRef->refexpr && IsA(jsonbRef->refexpr, CaseTestExpr)) + return true; + } + return false; } @@ -4589,6 +4729,25 @@ ExecInitExpr(Expr *node, PlanState *parent) state = (ExprState *) astate; } break; + case T_JsonbRef: + { + JsonbRef *jbref = (JsonbRef *) node; + JsonbRefExprState *jbstate = makeNode(JsonbRefExprState); + + jbstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalJsonbRef; + jbstate->refpathexpr = (List *) + ExecInitExpr((Expr *) jbref->refpathexpr, parent); + jbstate->refexpr = ExecInitExpr(jbref->refexpr, parent); + jbstate->refassgnexpr = ExecInitExpr(jbref->refassgnexpr, + parent); + /* do one-time catalog lookups for type info */ + get_typlenbyvalalign(jbref->refelemtype, + &jbstate->refelemlength, + &jbstate->refelembyval, + &jbstate->refelemalign); + state = (ExprState *) jbstate; + } + break; case T_FuncExpr: { FuncExpr *funcexpr = (FuncExpr *) node; diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 62355aa..5179077 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -1277,6 +1277,24 @@ _copyArrayRef(const ArrayRef *from) } /* + * _copyJsonbRef + */ +static JsonbRef * +_copyJsonbRef(const JsonbRef *from) +{ + JsonbRef *newnode = makeNode(JsonbRef); + + COPY_SCALAR_FIELD(refelemtype); + COPY_SCALAR_FIELD(reftypmod); + COPY_SCALAR_FIELD(refcollid); + COPY_NODE_FIELD(refpathexpr); + COPY_NODE_FIELD(refexpr); + COPY_NODE_FIELD(refassgnexpr); + + return newnode; +} + +/* * _copyFuncExpr */ static FuncExpr * @@ -4361,6 +4379,8 @@ copyObject(const void *from) case T_ArrayRef: retval = _copyArrayRef(from); break; + case T_JsonbRef: + retval = _copyJsonbRef(from); case T_FuncExpr: retval = _copyFuncExpr(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 8f16833..0bc06a8 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -255,6 +255,19 @@ _equalArrayRef(const ArrayRef *a, const ArrayRef *b) } static bool +_equalJsonbRef(const JsonbRef *a, const JsonbRef *b) +{ + COMPARE_SCALAR_FIELD(refelemtype); + COMPARE_SCALAR_FIELD(reftypmod); + COMPARE_SCALAR_FIELD(refcollid); + COMPARE_NODE_FIELD(refpathexpr); + COMPARE_NODE_FIELD(refexpr); + COMPARE_NODE_FIELD(refassgnexpr); + + return true; +} + +static bool _equalFuncExpr(const FuncExpr *a, const FuncExpr *b) { COMPARE_SCALAR_FIELD(funcid); @@ -2730,6 +2743,9 @@ equal(const void *a, const void *b) case T_ArrayRef: retval = _equalArrayRef(a, b); break; + case T_JsonbRef: + retval = _equalJsonbRef(a, b); + break; case T_FuncExpr: retval = _equalFuncExpr(a, b); break; diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 4a24474..d6b9d8a 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -76,6 +76,12 @@ exprType(const Node *expr) type = arrayref->refelemtype; } break; + case T_JsonbRef: + { + /* store operations yield the jsonb type */ + type = JSONBOID; + } + break; case T_FuncExpr: type = ((const FuncExpr *) expr)->funcresulttype; break; @@ -283,6 +289,9 @@ exprTypmod(const Node *expr) case T_ArrayRef: /* typmod is the same for array or element */ return ((const ArrayRef *) expr)->reftypmod; + case T_JsonbRef: + /* typmod is the same for array or element */ + return ((const JsonbRef *) expr)->reftypmod; case T_FuncExpr: { int32 coercedTypmod; @@ -767,6 +776,9 @@ exprCollation(const Node *expr) case T_ArrayRef: coll = ((const ArrayRef *) expr)->refcollid; break; + case T_JsonbRef: + coll = ((const JsonbRef *) expr)->refcollid; + break; case T_FuncExpr: coll = ((const FuncExpr *) expr)->funccollid; break; @@ -1006,6 +1018,9 @@ exprSetCollation(Node *expr, Oid collation) case T_ArrayRef: ((ArrayRef *) expr)->refcollid = collation; break; + case T_JsonbRef: + ((JsonbRef *) expr)->refcollid = collation; + break; case T_FuncExpr: ((FuncExpr *) expr)->funccollid = collation; break; @@ -1227,6 +1242,10 @@ exprLocation(const Node *expr) /* just use array argument's location */ loc = exprLocation((Node *) ((const ArrayRef *) expr)->refexpr); break; + case T_JsonbRef: + /* just use jsonb argument's location */ + loc = exprLocation((Node *) ((const JsonbRef *) expr)->refexpr); + break; case T_FuncExpr: { const FuncExpr *fexpr = (const FuncExpr *) expr; @@ -1747,6 +1766,22 @@ expression_tree_walker(Node *node, return true; } break; + case T_JsonbRef: + { + JsonbRef *jbref = (JsonbRef *) node; + + /* recurse directly for jsonb path list */ + if (expression_tree_walker((Node *) jbref->refpathexpr, + walker, context)) + return true; + + /* walker must see the refexpr and refassgnexpr, however */ + if (walker(jbref->refexpr, context)) + return true; + if (walker(jbref->refassgnexpr, context)) + return true; + } + break; case T_FuncExpr: { FuncExpr *expr = (FuncExpr *) node; @@ -2332,6 +2367,21 @@ expression_tree_mutator(Node *node, return (Node *) newnode; } break; + case T_JsonbRef: + { + JsonbRef *jsonbref = (JsonbRef *) node; + JsonbRef *newnode; + + FLATCOPY(newnode, jsonbref, JsonbRef); + MUTATE(newnode->refpathexpr, jsonbref->refpathexpr, + List *); + MUTATE(newnode->refexpr, jsonbref->refexpr, + Expr *); + MUTATE(newnode->refassgnexpr, jsonbref->refassgnexpr, + Expr *); + return (Node *) newnode; + } + break; case T_FuncExpr: { FuncExpr *expr = (FuncExpr *) node; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index e1b49d5..0188172 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1055,6 +1055,19 @@ _outArrayRef(StringInfo str, const ArrayRef *node) } static void +_outJsonbRef(StringInfo str, const JsonbRef *node) +{ + WRITE_NODE_TYPE("JSONBREF"); + + WRITE_OID_FIELD(refelemtype); + WRITE_INT_FIELD(reftypmod); + WRITE_OID_FIELD(refcollid); + WRITE_NODE_FIELD(refpathexpr); + WRITE_NODE_FIELD(refexpr); + WRITE_NODE_FIELD(refassgnexpr); +} + +static void _outFuncExpr(StringInfo str, const FuncExpr *node) { WRITE_NODE_TYPE("FUNCEXPR"); @@ -3123,6 +3136,9 @@ _outNode(StringInfo str, const void *obj) case T_ArrayRef: _outArrayRef(str, obj); break; + case T_JsonbRef: + _outJsonbRef(str, obj); + break; case T_FuncExpr: _outFuncExpr(str, obj); break; diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index df55b76..0d7fd10 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -588,6 +588,24 @@ _readArrayRef(void) } /* + * _readJsonbRef + */ +static JsonbRef * +_readJsonbRef(void) +{ + READ_LOCALS(JsonbRef); + + READ_OID_FIELD(refelemtype); + READ_INT_FIELD(reftypmod); + READ_OID_FIELD(refcollid); + READ_NODE_FIELD(refpathexpr); + READ_NODE_FIELD(refexpr); + READ_NODE_FIELD(refassgnexpr); + + READ_DONE(); +} + +/* * _readFuncExpr */ static FuncExpr * @@ -1424,6 +1442,8 @@ parseNodeString(void) return_value = _readWindowFunc(); else if (MATCH("ARRAYREF", 8)) return_value = _readArrayRef(); + else if (MATCH("JSONBREF", 8)) + return_value = _readJsonbRef(); else if (MATCH("FUNCEXPR", 8)) return_value = _readFuncExpr(); else if (MATCH("NAMEDARGEXPR", 12)) diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index f2c8551..734ec0d 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -1360,6 +1360,13 @@ contain_nonstrict_functions_walker(Node *node, void *context) return true; /* else fall through to check args */ } + if (IsA(node, JsonbRef)) + { + /* jsonb assignment is nonstrict, but subscripting is strict */ + if (((JsonbRef *) node)->refassgnexpr != NULL) + return true; + /* else fall through to check args */ + } if (IsA(node, FuncExpr)) { FuncExpr *expr = (FuncExpr *) node; diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index fa77ef1..1a4466e 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -470,13 +470,24 @@ transformIndirection(ParseState *pstate, Node *basenode, List *indirection) } /* process trailing subscripts, if any */ if (subscripts) - result = (Node *) transformArraySubscripts(pstate, - result, - exprType(result), - InvalidOid, - exprTypmod(result), - subscripts, - NULL); + { + if (exprType(result) == JSONBOID) + result = (Node *) transformJsonbSubscripts(pstate, + result, + JSONBOID, + exprTypmod(result), + subscripts, + NULL); + else + result = (Node *) transformArraySubscripts(pstate, + result, + exprType(result), + InvalidOid, + exprTypmod(result), + subscripts, + NULL); + + } return result; } diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c index 4130cbf..ae9b76d 100644 --- a/src/backend/parser/parse_node.c +++ b/src/backend/parser/parse_node.c @@ -259,6 +259,68 @@ transformArrayType(Oid *arrayType, int32 *arrayTypmod) } /* + * transformJsonbSubscripts() + * Transform jsonb subscripting. This is used for both + * jsonb fetch and jsonb assignment. + * + * In an jsonb fetch, we are given a source jsonb value and we produce an + * expression that represents the result of extracting a single jsonb element. + * + * In an jsonb assignment, we are given a destination jsonb value plus a + * source value that is to be assigned to a single element of that jsonb. + * We produce an expression that represents the new jsonb value + * with the source data. + * + * pstate Parse state + * jsonbBase Already-transformed expression for the jsonb as a whole + * elementType OID of jsonb's element type + * jsonbTypMod typmod for the jsonb (which is also typmod for the elements) + * indirection Untransformed list of subscripts (must not be NIL) + * assignFrom NULL for jsonb fetch, else transformed expression for source. + */ + +JsonbRef * +transformJsonbSubscripts(ParseState *pstate, + Node *jsonbBase, + Oid elementType, + int32 jsonbTypMod, + List *indirection, + Node *assignFrom) +{ + List *pathExpr = NIL; + ListCell *idx; + JsonbRef *jbref; + + /* + * Transform the subscript expressions. + */ + foreach(idx, indirection) + { + A_Indices *ai = (A_Indices *) lfirst(idx); + Node *subexpr; + + Assert(IsA(ai, A_Indices)); + subexpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind); + pathExpr = lappend(pathExpr, subexpr); + } + + /* + * Ready to build the JsonbRef node. + */ + jbref = makeNode(JsonbRef); + jbref->refelemtype = elementType; + jbref->reftypmod = jsonbTypMod; + /* refcollid will be set by parse_collate.c */ + jbref->refpathexpr = pathExpr; + jbref->refexpr = (Expr *) jsonbBase; + jbref->refassgnexpr = (Expr *) assignFrom; + + return jbref; +} + + + +/* * transformArraySubscripts() * Transform array subscripting. This is used for both * array fetch and array assignment. diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 1b3fcd6..5be882c 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -45,6 +45,17 @@ static Node *transformAssignmentIndirection(ParseState *pstate, ListCell *indirection, Node *rhs, int location); + +static Node *transformJsonbAssignmentSubscripts(ParseState *pstate, + Node *basenode, + const char *targetName, + int32 targetTypMod, + Oid targetCollation, + List *subscripts, + ListCell *next_indirection, + Node *rhs, + int location); + static Node *transformAssignmentSubscripts(ParseState *pstate, Node *basenode, const char *targetName, @@ -744,27 +755,42 @@ transformAssignmentIndirection(ParseState *pstate, if (subscripts) { /* recurse, and then return because we're done */ - return transformAssignmentSubscripts(pstate, - basenode, - targetName, - targetTypeId, - targetTypMod, - targetCollation, - subscripts, - isSlice, - NULL, - rhs, - location); + if (exprType(basenode) == JSONBOID) + return transformJsonbAssignmentSubscripts(pstate, + basenode, + targetName, + targetTypMod, + targetCollation, + subscripts, + i, + rhs, + location); + else + return transformAssignmentSubscripts(pstate, + basenode, + targetName, + targetTypeId, + targetTypMod, + targetCollation, + subscripts, + isSlice, + NULL, + rhs, + location); } /* base case: just coerce RHS to match target type ID */ - result = coerce_to_target_type(pstate, - rhs, exprType(rhs), - targetTypeId, targetTypMod, - COERCION_ASSIGNMENT, - COERCE_IMPLICIT_CAST, - -1); + if (targetTypeId != InvalidOid) + result = coerce_to_target_type(pstate, + rhs, exprType(rhs), + targetTypeId, targetTypMod, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + else + result = rhs; + if (result == NULL) { if (targetIsArray) @@ -793,6 +819,47 @@ transformAssignmentIndirection(ParseState *pstate, } /* + * helper for transformAssignmentIndirection: process jsonb assignment + */ +static Node * +transformJsonbAssignmentSubscripts(ParseState *pstate, + Node *basenode, + const char *targetName, + int32 targetTypMod, + Oid targetCollation, + List *subscripts, + ListCell *next_indirection, + Node *rhs, + int location) +{ + Node *result; + + Assert(subscripts != NIL); + + /* recurse to create appropriate RHS for jsonb assign */ + rhs = transformAssignmentIndirection(pstate, + NULL, + targetName, + true, + InvalidOid, + targetTypMod, + targetCollation, + next_indirection, + rhs, + location); + + /* process subscripts */ + result = (Node *) transformJsonbSubscripts(pstate, + basenode, + exprType(rhs), + targetTypMod, + subscripts, + rhs); + + return result; +} + +/* * helper for transformAssignmentIndirection: process array assignment */ static Node * diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index 1b8e7b0..93a2ea4 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -980,6 +980,14 @@ process_matched_tle(TargetEntry *src_tle, aref->refexpr = (Expr *) prior_expr; newexpr = (Node *) aref; } + else if (IsA(src_expr, JsonbRef)) + { + JsonbRef *jbref = makeNode(JsonbRef); + + memcpy(jbref, src_expr, sizeof(JsonbRef)); + jbref->refexpr = (Expr *) prior_expr; + newexpr = (Node *) jbref; + } else { elog(ERROR, "cannot happen"); @@ -1013,6 +1021,14 @@ get_assignment_input(Node *node) return NULL; return (Node *) aref->refexpr; } + else if (IsA(node, JsonbRef)) + { + JsonbRef *jbref = (JsonbRef *) node; + + if (jbref->refassgnexpr == NULL) + return NULL; + return (Node *) jbref->refexpr; + } return NULL; } diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index f0f1651..707f5a9 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -1960,3 +1960,45 @@ jsonb_object_agg_finalfn(PG_FUNCTION_ARGS) PG_RETURN_POINTER(out); } + + +Datum +jsonb_set_element(Datum jsonbdatum, + text **path, int path_len, Datum sourceData, Oid source_type) +{ + Jsonb *jb = DatumGetJsonb(jsonbdatum); + JsonbInState result; + JsonbTypeCategory tcategory; + Oid outfuncoid; + JsonbValue newval; + JsonbParseState *state = NULL; + JsonbIterator *it; + JsonbValue *res = NULL; + int i = 0; + bool *path_nulls = palloc(path_len * sizeof(bool)); + + jsonb_categorize_type(source_type, + &tcategory, &outfuncoid); + memset(&result, 0, sizeof(JsonbInState)); + result.parseState = NULL; + datum_to_jsonb(sourceData, false, &result, tcategory, outfuncoid, false); + + it = JsonbIteratorInit(&jb->root); + + newval = *result.res; + + if (newval.type == jbvArray && newval.val.array.rawScalar == true) + { + newval = newval.val.array.elems[0]; + } + + for(i = 0; i < path_len; i++) + { + path_nulls[i]= false; + } + + res = setPath(&it, (Datum *) path, path_nulls, path_len, &state, 0, + (void *)&newval, true, true); + + PG_RETURN_JSONB(JsonbValueToJsonb(res)); +} diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c index 154a883..f60eafd 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -33,6 +33,12 @@ #include "utils/memutils.h" #include "utils/typcache.h" +#define add_newval(state, newval, unpacked) \ + if (unpacked) \ + (void) pushJsonbValue(st, WJB_VALUE, (JsonbValue *)newval); \ + else \ + addJsonbToParseState(st, (Jsonb *)newval); + /* 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); @@ -127,17 +133,13 @@ static JsonbValue *findJsonbValueFromContainerLen(JsonbContainer *container, /* functions supporting jsonb_delete, jsonb_set and jsonb_concat */ static JsonbValue *IteratorConcat(JsonbIterator **it1, JsonbIterator **it2, JsonbParseState **state); -static JsonbValue *setPath(JsonbIterator **it, Datum *path_elems, - bool *path_nulls, int path_len, - JsonbParseState **st, int level, Jsonb *newval, - bool create); static void setPathObject(JsonbIterator **it, Datum *path_elems, bool *path_nulls, int path_len, JsonbParseState **st, int level, - Jsonb *newval, uint32 npairs, bool create); + Jsonb *newval, bool unpacked, uint32 npairs, bool create); static void setPathArray(JsonbIterator **it, Datum *path_elems, bool *path_nulls, int path_len, JsonbParseState **st, - int level, Jsonb *newval, uint32 nelems, bool create); + int level, Jsonb *newval, bool unpacked, uint32 nelems, bool create); static void addJsonbToParseState(JsonbParseState **jbps, Jsonb *jb); /* state for json_object_keys */ @@ -3544,7 +3546,7 @@ jsonb_set(PG_FUNCTION_ARGS) it = JsonbIteratorInit(&in->root); res = setPath(&it, path_elems, path_nulls, path_len, &st, - 0, newval, create); + 0, newval, false, create); Assert(res != NULL); @@ -3588,7 +3590,7 @@ jsonb_delete_path(PG_FUNCTION_ARGS) it = JsonbIteratorInit(&in->root); - res = setPath(&it, path_elems, path_nulls, path_len, &st, 0, NULL, false); + res = setPath(&it, path_elems, path_nulls, path_len, &st, 0, NULL, false, false); Assert(res != NULL); @@ -3715,10 +3717,10 @@ IteratorConcat(JsonbIterator **it1, JsonbIterator **it2, * does not exist. All path elements before the last must already exist * whether or not create is true, or nothing is done. */ -static JsonbValue * +JsonbValue * setPath(JsonbIterator **it, Datum *path_elems, bool *path_nulls, int path_len, - JsonbParseState **st, int level, Jsonb *newval, bool create) + JsonbParseState **st, int level, void *newval, bool unpacked, bool create) { JsonbValue v; JsonbValue *res = NULL; @@ -3731,7 +3733,7 @@ setPath(JsonbIterator **it, Datum *path_elems, case WJB_BEGIN_ARRAY: (void) pushJsonbValue(st, r, NULL); setPathArray(it, path_elems, path_nulls, path_len, st, level, - newval, v.val.array.nElems, create); + newval, unpacked, v.val.array.nElems, create); r = JsonbIteratorNext(it, &v, false); Assert(r == WJB_END_ARRAY); res = pushJsonbValue(st, r, NULL); @@ -3740,7 +3742,7 @@ 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, create); + newval, unpacked, v.val.object.nPairs, create); r = JsonbIteratorNext(it, &v, true); Assert(r == WJB_END_OBJECT); res = pushJsonbValue(st, r, NULL); @@ -3763,9 +3765,9 @@ setPath(JsonbIterator **it, Datum *path_elems, static void setPathObject(JsonbIterator **it, Datum *path_elems, bool *path_nulls, int path_len, JsonbParseState **st, int level, - Jsonb *newval, uint32 npairs, bool create) + Jsonb *newval, bool unpacked, uint32 npairs, bool create) { - JsonbValue v; + JsonbValue v, newkey; int i; JsonbValue k; bool done = false; @@ -3783,7 +3785,7 @@ setPathObject(JsonbIterator **it, Datum *path_elems, bool *path_nulls, newkey.val.string.val = VARDATA_ANY(path_elems[level]); (void) pushJsonbValue(st, WJB_KEY, &newkey); - addJsonbToParseState(st, newval); + add_newval(st, newval, unpacked); } for (i = 0; i < npairs; i++) @@ -3803,7 +3805,7 @@ setPathObject(JsonbIterator **it, Datum *path_elems, bool *path_nulls, if (newval != NULL) { (void) pushJsonbValue(st, WJB_KEY, &k); - addJsonbToParseState(st, newval); + add_newval(st, newval, unpacked); } done = true; } @@ -3811,7 +3813,7 @@ setPathObject(JsonbIterator **it, Datum *path_elems, bool *path_nulls, { (void) pushJsonbValue(st, r, &k); setPath(it, path_elems, path_nulls, path_len, - st, level + 1, newval, create); + st, level + 1, newval, unpacked, create); } } else @@ -3825,7 +3827,7 @@ setPathObject(JsonbIterator **it, Datum *path_elems, bool *path_nulls, newkey.val.string.val = VARDATA_ANY(path_elems[level]); (void) pushJsonbValue(st, WJB_KEY, &newkey); - addJsonbToParseState(st, newval); + add_newval(st, newval, unpacked); } (void) pushJsonbValue(st, r, &k); @@ -3857,7 +3859,7 @@ setPathObject(JsonbIterator **it, Datum *path_elems, bool *path_nulls, static void setPathArray(JsonbIterator **it, Datum *path_elems, bool *path_nulls, int path_len, JsonbParseState **st, int level, - Jsonb *newval, uint32 nelems, bool create) + Jsonb *newval, bool unpacked, uint32 nelems, bool create) { JsonbValue v; int idx, @@ -3902,7 +3904,7 @@ setPathArray(JsonbIterator **it, Datum *path_elems, bool *path_nulls, if ((idx == INT_MIN || nelems == 0) && create && (level == path_len - 1)) { Assert(newval != NULL); - addJsonbToParseState(st, newval); + add_newval(st, newval, unpacked); done = true; } @@ -3917,13 +3919,15 @@ setPathArray(JsonbIterator **it, Datum *path_elems, bool *path_nulls, { r = JsonbIteratorNext(it, &v, true); /* skip */ if (newval != NULL) - addJsonbToParseState(st, newval); + { + add_newval(st, newval, unpacked); + } done = true; } else (void) setPath(it, path_elems, path_nulls, path_len, - st, level + 1, newval, create); + st, level + 1, newval, unpacked, create); } else { @@ -3950,9 +3954,46 @@ setPathArray(JsonbIterator **it, Datum *path_elems, bool *path_nulls, if (create && !done && level == path_len - 1 && i == nelems - 1) { - addJsonbToParseState(st, newval); + add_newval(st, newval, unpacked); } } } } + + +Datum +jsonb_get_element(Datum jsonbdatum, + text **path, int path_len, bool *isNull) +{ + Jsonb *jb = DatumGetJsonb(jsonbdatum); + JsonbValue *v; + int level = 1; + + if (!JB_ROOT_IS_OBJECT(jb)) + { + *isNull = true; + return (Datum) 0; + } + + v = findJsonbValueFromContainerLen(&jb->root, JB_FOBJECT, + VARDATA_ANY(path[0]), + VARSIZE_ANY_EXHDR(path[0])); + + while (v != NULL && + v->type == jbvBinary && level < path_len) + { + v = findJsonbValueFromContainerLen(v->val.binary.data, JB_FOBJECT, + VARDATA_ANY(path[level]), + VARSIZE_ANY_EXHDR(path[level])); + level++; + } + + if (v != NULL && level == path_len) + { + PG_RETURN_JSONB(JsonbValueToJsonb(v)); + } + + *isNull = true; + return (Datum) 0; +} diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 51391f6..8d51fa5 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -419,7 +419,7 @@ static void get_opclass_name(Oid opclass, Oid actual_datatype, StringInfo buf); static Node *processIndirection(Node *node, deparse_context *context, bool printit); -static void printSubscripts(ArrayRef *aref, deparse_context *context); +static void printSubscripts(Node *node, deparse_context *context); static char *get_relation_name(Oid relid); static char *generate_relation_name(Oid relid, List *namespaces); static char *generate_function_name(Oid funcid, int nargs, @@ -5640,6 +5640,14 @@ get_update_query_targetlist_def(Query *query, List *targetList, break; expr = (Node *) aref->refassgnexpr; } + else if (IsA(expr, JsonbRef)) + { + JsonbRef *jbref = (JsonbRef *) expr; + + if (jbref->refassgnexpr == NULL) + break; + expr = (Node *) jbref->refassgnexpr; + } else break; } @@ -6643,6 +6651,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags) return true; case T_ArrayRef: + case T_JsonbRef: case T_ArrayExpr: case T_RowExpr: case T_CoalesceExpr: @@ -6759,6 +6768,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags) } case T_BoolExpr: /* lower precedence */ case T_ArrayRef: /* other separators */ + case T_JsonbRef: /* other separators */ case T_ArrayExpr: /* other separators */ case T_RowExpr: /* other separators */ case T_CoalesceExpr: /* own parentheses */ @@ -6809,6 +6819,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags) return true; /* own parentheses */ } case T_ArrayRef: /* other separators */ + case T_JsonbRef: /* other separators */ case T_ArrayExpr: /* other separators */ case T_RowExpr: /* other separators */ case T_CoalesceExpr: /* own parentheses */ @@ -7054,7 +7065,72 @@ get_rule_expr(Node *node, deparse_context *context, else { /* Just an ordinary array fetch, so print subscripts */ - printSubscripts(aref, context); + printSubscripts(node, context); + } + } + break; + + case T_JsonbRef: + { + JsonbRef *jbref = (JsonbRef *) node; + bool need_parens; + + /* + * If the argument is a CaseTestExpr, we must be inside a + * FieldStore, ie, we are assigning to an element of a jsonb + * within a composite column. Since we already punted on + * displaying the FieldStore's target information, just punt + * here too, and display only the assignment source + * expression. + */ + if (IsA(jbref->refexpr, CaseTestExpr)) + { + Assert(jbref->refassgnexpr); + get_rule_expr((Node *) jbref->refassgnexpr, + context, showimplicit); + break; + } + + /* + * Parenthesize the argument unless it's a simple Var or a + * FieldSelect. (In particular, if it's another JsonbRef, we + * *must* parenthesize to avoid confusion.) + */ + need_parens = !IsA(jbref->refexpr, Var) && + !IsA(jbref->refexpr, FieldSelect); + if (need_parens) + appendStringInfoChar(buf, '('); + get_rule_expr((Node *) jbref->refexpr, context, showimplicit); + if (need_parens) + appendStringInfoChar(buf, ')'); + + /* + * If there's a refassgnexpr, we want to print the node in the + * format "jsonb[subscripts] := refassgnexpr". This is not + * legal SQL, so decompilation of INSERT or UPDATE statements + * should always use processIndirection as part of the + * statement-level syntax. We should only see this when + * EXPLAIN tries to print the targetlist of a plan resulting + * from such a statement. + */ + if (jbref->refassgnexpr) + { + Node *refassgnexpr; + + /* + * Use processIndirection to print this node's subscripts + * as well as any additional field selections or + * subscripting in immediate descendants. It returns the + * RHS expr that is actually being "assigned". + */ + refassgnexpr = processIndirection(node, context, true); + appendStringInfoString(buf, " := "); + get_rule_expr(refassgnexpr, context, showimplicit); + } + else + { + /* Just an ordinary array fetch, so print subscripts */ + printSubscripts(node, context); } } break; @@ -7240,7 +7316,7 @@ get_rule_expr(Node *node, deparse_context *context, * WRONG to not parenthesize a Var argument; simplicity is not * the issue here, having the right number of names is. */ - need_parens = !IsA(arg, ArrayRef) &&!IsA(arg, FieldSelect); + need_parens = !IsA(arg, ArrayRef) &&!IsA(arg, JsonbRef) &&!IsA(arg, FieldSelect); if (need_parens) appendStringInfoChar(buf, '('); get_rule_expr(arg, context, true); @@ -9250,7 +9326,7 @@ processIndirection(Node *node, deparse_context *context, bool printit) if (aref->refassgnexpr == NULL) break; if (printit) - printSubscripts(aref, context); + printSubscripts(node, context); /* * We ignore refexpr since it should be an uninteresting reference @@ -9258,6 +9334,22 @@ processIndirection(Node *node, deparse_context *context, bool printit) */ node = (Node *) aref->refassgnexpr; } + else if (IsA(node, JsonbRef)) + { + JsonbRef *jbref = (JsonbRef *) node; + + if (jbref->refassgnexpr == NULL) + break; + if (printit) + printSubscripts(node, context); + + /* + * We ignore refexpr since it should be an uninteresting reference + * to the target column or subcolumn. + */ + node = (Node *) jbref->refassgnexpr; + } + else break; } @@ -9266,24 +9358,40 @@ processIndirection(Node *node, deparse_context *context, bool printit) } static void -printSubscripts(ArrayRef *aref, deparse_context *context) +printSubscripts(Node *node, deparse_context *context) { StringInfo buf = context->buf; - ListCell *lowlist_item; - ListCell *uplist_item; + if (IsA(node, ArrayRef)) + { + ArrayRef *aref = (ArrayRef *) node; + ListCell *lowlist_item; + ListCell *uplist_item; - lowlist_item = list_head(aref->reflowerindexpr); /* could be NULL */ - foreach(uplist_item, aref->refupperindexpr) + lowlist_item = list_head(aref->reflowerindexpr); /* could be NULL */ + foreach(uplist_item, aref->refupperindexpr) + { + appendStringInfoChar(buf, '['); + if (lowlist_item) + { + get_rule_expr((Node *) lfirst(lowlist_item), context, false); + appendStringInfoChar(buf, ':'); + lowlist_item = lnext(lowlist_item); + } + get_rule_expr((Node *) lfirst(uplist_item), context, false); + appendStringInfoChar(buf, ']'); + } + } + else if (IsA(node, JsonbRef)) { - appendStringInfoChar(buf, '['); - if (lowlist_item) + JsonbRef *jbref = (JsonbRef *) node; + ListCell *path_item; + + foreach(path_item, jbref->refpathexpr) { - get_rule_expr((Node *) lfirst(lowlist_item), context, false); - appendStringInfoChar(buf, ':'); - lowlist_item = lnext(lowlist_item); + appendStringInfoChar(buf, '['); + get_rule_expr((Node *) lfirst(path_item), context, false); + appendStringInfoChar(buf, ']'); } - get_rule_expr((Node *) lfirst(uplist_item), context, false); - appendStringInfoChar(buf, ']'); } } diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 4ae2f3e..f8397ed 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -662,6 +662,22 @@ typedef struct ArrayRefExprState } ArrayRefExprState; /* ---------------- + * JsonbRefExprState node + * ---------------- + */ +typedef struct JsonbRefExprState +{ + ExprState xprstate; + List *refpathexpr; /* states for child nodes */ + ExprState *refexpr; + ExprState *refassgnexpr; + int16 refattrlength; /* typlen of jsonb type */ + int16 refelemlength; /* typlen of the jsonb element type */ + bool refelembyval; /* is the element type pass-by-value? */ + char refelemalign; /* typalign of the element type */ +} JsonbRefExprState; + +/* ---------------- * FuncExprState node * * Although named for FuncExpr, this is also used for OpExpr, DistinctExpr, diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 274480e..3a99da6 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -178,6 +178,7 @@ typedef enum NodeTag T_FromExpr, T_OnConflictExpr, T_IntoClause, + T_JsonbRef, /* * TAGS FOR EXPRESSION STATE NODES (execnodes.h) @@ -192,6 +193,7 @@ typedef enum NodeTag T_GroupingFuncExprState, T_WindowFuncExprState, T_ArrayRefExprState, + T_JsonbRefExprState, T_FuncExprState, T_ScalarArrayOpExprState, T_BoolExprState, diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 60c1ca2..7e6d9d1 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -367,6 +367,27 @@ typedef struct ArrayRef * fetch */ } ArrayRef; +/* ---------------- + * JsonbRef: describes a jsonb subscripting operation + * + * A JsonbRef can describe fetching a single element from a jsonb + * and storing a single value into a jsonb. + * ---------------- + */ +typedef struct JsonbRef +{ + Expr xpr; + Oid refelemtype; /* type of the jsonb elements */ + int32 reftypmod; /* typmod of the jsonb (and elements too) */ + Oid refcollid; /* OID of collation, or InvalidOid if none */ + List *refpathexpr; /* expressions that evaluate to jsonb path */ + Expr *refexpr; /* the expression that evaluates to a jsonb + * value */ + Expr *refassgnexpr; /* expression for the source value, or NULL if + * fetch */ +} JsonbRef; + + /* * CoercionContext - distinguishes the allowed set of type casts * diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h index 5249945..c6957af 100644 --- a/src/include/parser/parse_node.h +++ b/src/include/parser/parse_node.h @@ -224,6 +224,12 @@ extern void cancel_parser_errposition_callback(ParseCallbackState *pcbstate); extern Var *make_var(ParseState *pstate, RangeTblEntry *rte, int attrno, int location); extern Oid transformArrayType(Oid *arrayType, int32 *arrayTypmod); +extern JsonbRef *transformJsonbSubscripts(ParseState *pstate, + Node *arrayBase, + Oid elementType, + int32 arrayTypMod, + List *indirection, + Node *assignFrom); extern ArrayRef *transformArraySubscripts(ParseState *pstate, Node *arrayBase, Oid arrayType, diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h index 3049a87..124b6b4 100644 --- a/src/include/utils/jsonb.h +++ b/src/include/utils/jsonb.h @@ -433,5 +433,11 @@ extern char *JsonbToCString(StringInfo out, JsonbContainer *in, extern char *JsonbToCStringIndent(StringInfo out, JsonbContainer *in, int estimated_len); +extern Datum jsonb_set_element(Datum datum, text **path, int path_len, Datum sourceData, Oid source_type); +extern Datum jsonb_get_element(Datum datum, text **path, int path_len, bool *isNull); +extern JsonbValue *setPath(JsonbIterator **it, Datum *path_elems, + bool *path_nulls, int path_len, + JsonbParseState **st, int level, void *newval, + bool unpacked, bool create); #endif /* __JSONB_H__ */ diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index c73f20b..c1006e5 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -6312,6 +6312,20 @@ exec_simple_check_node(Node *node) return TRUE; } + case T_JsonbRef: + { + JsonbRef *expr = (JsonbRef *) node; + + if (!exec_simple_check_node((Node *) expr->refpathexpr)) + return FALSE; + if (!exec_simple_check_node((Node *) expr->refexpr)) + return FALSE; + if (!exec_simple_check_node((Node *) expr->refassgnexpr)) + return FALSE; + + return TRUE; + } + case T_FuncExpr: { FuncExpr *expr = (FuncExpr *) node; diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out index 82d1b69..986a371 100644 --- a/src/test/regress/expected/jsonb.out +++ b/src/test/regress/expected/jsonb.out @@ -3331,3 +3331,115 @@ select jsonb_set('[]','{-99}','{"foo":123}'); [{"foo": 123}] (1 row) +-- jsonb subscript +select ('{"a": 1}'::jsonb)['a']; + jsonb +------- + 1 +(1 row) + +select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b']; + jsonb +------- + "c" +(1 row) + +select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']; + jsonb +----------- + [1, 2, 3] +(1 row) + +select ('{"a": 1}'::jsonb)['not_exist']; + jsonb +------- + +(1 row) + +select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']; + jsonb +--------------- + {"a2": "aaa"} +(1 row) + +select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']; + jsonb +------- + "aaa" +(1 row) + +select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3']; + jsonb +------- + +(1 row) + +create TEMP TABLE test_jsonb_subscript ( + id int, + test_json jsonb +); +insert into test_jsonb_subscript values +(1, '{}'), -- empty jsonb +(2, '{"key": "value"}'); -- jsonb with data +-- update empty jsonb +update test_jsonb_subscript set test_json['a'] = 1 where id = 1; +select * from test_jsonb_subscript; + id | test_json +----+------------------ + 2 | {"key": "value"} + 1 | {"a": 1} +(2 rows) + +-- update jsonb with some data +update test_jsonb_subscript set test_json['a'] = 1 where id = 2; +select * from test_jsonb_subscript; + id | test_json +----+-------------------------- + 1 | {"a": 1} + 2 | {"a": 1, "key": "value"} +(2 rows) + +-- replace jsonb +update test_jsonb_subscript set test_json['a'] = 'test'; +select * from test_jsonb_subscript; + id | test_json +----+------------------------------- + 1 | {"a": "test"} + 2 | {"a": "test", "key": "value"} +(2 rows) + +-- replace by object +update test_jsonb_subscript set test_json['a'] = '{"b": 1}'::jsonb; +select * from test_jsonb_subscript; + id | test_json +----+--------------------------------- + 1 | {"a": {"b": 1}} + 2 | {"a": {"b": 1}, "key": "value"} +(2 rows) + +-- replace by array +update test_jsonb_subscript set test_json['a'] = '[1, 2, 3]'::jsonb; +select * from test_jsonb_subscript; + id | test_json +----+---------------------------------- + 1 | {"a": [1, 2, 3]} + 2 | {"a": [1, 2, 3], "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"} +(1 row) + +select * from test_jsonb_subscript where test_json['key_doesnt_exists'] = '"value"'; + id | test_json +----+----------- +(0 rows) + +select * from test_jsonb_subscript where test_json['key'] = '"wrong_value"'; + id | test_json +----+----------- +(0 rows) + diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql index cb03ada..f48ff79 100644 --- a/src/test/regress/sql/jsonb.sql +++ b/src/test/regress/sql/jsonb.sql @@ -816,3 +816,46 @@ select jsonb_set('{}','{x}','{"foo":123}'); select jsonb_set('[]','{0}','{"foo":123}'); select jsonb_set('[]','{99}','{"foo":123}'); select jsonb_set('[]','{-99}','{"foo":123}'); + +-- jsonb subscript +select ('{"a": 1}'::jsonb)['a']; +select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b']; +select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']; +select ('{"a": 1}'::jsonb)['not_exist']; +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']; +select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3']; + +create TEMP TABLE test_jsonb_subscript ( + id int, + test_json jsonb +); + +insert into test_jsonb_subscript values +(1, '{}'), -- empty jsonb +(2, '{"key": "value"}'); -- jsonb with data + +-- update empty jsonb +update test_jsonb_subscript set test_json['a'] = 1 where id = 1; +select * from test_jsonb_subscript; + +-- update jsonb with some data +update test_jsonb_subscript set test_json['a'] = 1 where id = 2; +select * from test_jsonb_subscript; + +-- replace jsonb +update test_jsonb_subscript set test_json['a'] = 'test'; +select * from test_jsonb_subscript; + +-- replace by object +update test_jsonb_subscript set test_json['a'] = '{"b": 1}'::jsonb; +select * from test_jsonb_subscript; + +-- replace by array +update test_jsonb_subscript set test_json['a'] = '[1, 2, 3]'::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"'; +select * from test_jsonb_subscript where test_json['key'] = '"wrong_value"';