jsonb array-style subscription
Hi,
Here is a reworked version of patch for jsonb subscription.
There weren't many changes in functionality:
=# create TEMP TABLE test_jsonb_subscript (
id int,
test_json jsonb
);
=# insert into test_jsonb_subscript values
(1, '{}'),
(2, '{}');
=# update test_jsonb_subscript set test_json['a'] = 42;
=# select * from test_jsonb_subscript;
id | test_json
----+--------------------------
1 | {"a": 42}
2 | {"a": 42}
(2 rows)
=# select test_json['a'] from test_jsonb_subscript;
test_json
------------
{"a": 42}
{"a": 42}
(2 rows)
I've cleaned up the code, created a separate JsonbRef node (and there are a
lot of small changes because of that), abandoned an idea of "deep nesting"
of assignments (because it doesn't relate to jsonb subscription, is more
about the
"jsonb_set" function, and anyway it's not a good idea). It looks fine for
me, and I need a little guidance - is it ok to propose this feature for
commitfest 2016-03 for a review?
Attachments:
jsonb_subscription.patchapplication/octet-stream; name=jsonb_subscription.patchDownload
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"';
On Mon, Jan 18, 2016 at 9:32 PM, Dmitry Dolgov <9erthalion6@gmail.com> wrote:
I've cleaned up the code, created a separate JsonbRef node (and there are a
lot of small changes because of that), abandoned an idea of "deep nesting"
of assignments (because it doesn't relate to jsonb subscription, is more
about the
"jsonb_set" function, and anyway it's not a good idea). It looks fine for
me, and I need a little guidance - is it ok to propose this feature for
commitfest 2016-03 for a review?
That's what the CF is for. If you are confident enough, I think that
you should register it, elsewhise the fate of this patch will be
likely to fall in oblivion.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Dmitry Dolgov wrote:
I've cleaned up the code, created a separate JsonbRef node (and there are a
lot of small changes because of that), abandoned an idea of "deep nesting"
of assignments (because it doesn't relate to jsonb subscription, is more
about the
"jsonb_set" function, and anyway it's not a good idea). It looks fine for
me, and I need a little guidance - is it ok to propose this feature for
commitfest 2016-03 for a review?
Has this patch been proposed in some commitfest previously? One of the
less-commonly-invoked rules of commitfests is that you can't add patches
that are too invasive to the last one -- so your last chance for 9.6 was
2016-01. This is harsh to patch submitters, but it helps keep the size
of the last commitfest down to a reasonable level; otherwise we are
never able to finish it.
--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 20 January 2016 at 02:14, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:
Dmitry Dolgov wrote:
I've cleaned up the code, created a separate JsonbRef node (and there
are a
lot of small changes because of that), abandoned an idea of "deep
nesting"
of assignments (because it doesn't relate to jsonb subscription, is more
about the
"jsonb_set" function, and anyway it's not a good idea). It looks fine for
me, and I need a little guidance - is it ok to propose this feature for
commitfest 2016-03 for a review?Has this patch been proposed in some commitfest previously?
Unfortunately, it wasn't. I just sent an intermediate version of this patch
to hackers several months ago to discuss it.
you can't add patches that are too invasive to the last one -- so your last
chance for 9.6 was 2016-01.
Yes, that's what I was worried about (although I didn't know exactly about
this rule before).
On 1/19/16, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
Dmitry Dolgov wrote:
I've cleaned up the code, created a separate JsonbRef node (and there are
a
lot of small changes because of that), abandoned an idea of "deep
nesting"
of assignments (because it doesn't relate to jsonb subscription, is more
about the
"jsonb_set" function, and anyway it's not a good idea). It looks fine for
me, and I need a little guidance - is it ok to propose this feature for
commitfest 2016-03 for a review?Has this patch been proposed in some commitfest previously? One of the
less-commonly-invoked rules of commitfests is that you can't add patches
that are too invasive to the last one -- so your last chance for 9.6 was
2016-01. This is harsh to patch submitters, but it helps keep the size
of the last commitfest down to a reasonable level; otherwise we are
never able to finish it.
I'd like to be a reviewer for the patch. It does not look big and very invasive.
Is it a final decision or it has a chance? If something there hurts
committers, it can end up as "Rejected with feedback" (since the patch
is already in the CF[1]https://commitfest.postgresql.org/9/485/ -- Best regards, Vitaly Burovoy)?
[1]: https://commitfest.postgresql.org/9/485/ -- Best regards, Vitaly Burovoy
--
Best regards,
Vitaly Burovoy
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Vitaly Burovoy <vitaly.burovoy@gmail.com> writes:
I'd like to be a reviewer for the patch. It does not look big and very invasive.
Is it a final decision or it has a chance? If something there hurts
committers, it can end up as "Rejected with feedback" (since the patch
is already in the CF[1])?
Well, it is pretty invasive, and I'm not sure anyone has bought in on the
design. The problem I've got with it is that it's a one-off that embeds
a whole lot of new parser/planner/executor infrastructure to serve exactly
one datatype, ie jsonb. That does not seem like a good design approach,
nor in keeping with the way Postgres usually goes at things.
If the patch were proposing a similar amount of new infrastructure to
support some datatype-extensible concept of subscripting, I'd be much
happier about it.
I believe there's been some handwaving in the past about extensible
approaches to subscripting, though I haven't got time to troll the
archives for it right now.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 3/2/16 6:24 PM, Tom Lane wrote:
If the patch were proposing a similar amount of new infrastructure to
support some datatype-extensible concept of subscripting, I'd be much
happier about it.
+1
I believe there's been some handwaving in the past about extensible
approaches to subscripting, though I haven't got time to troll the
archives for it right now.
I'd be able to make use of that in my ndarray data type. It would also
be nice to be able to add things like matrix types, sparse arrays, and
variable size arrays (ie: list of lists), and subscripting is how you'd
want to interface with all of those.
Presumably the point type is handled specially today, so that should be
taken care off too.
--
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Experts in Analytics, Data Architecture and PostgreSQL
Data in Trouble? Get it in Treble! http://BlueTreble.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
If the patch were proposing a similar amount of new infrastructure to
support some datatype-extensible concept of subscripting, I'd be much
happier about it.
Well, actually, I agree with that. I can try to rework the patch to achieve
this goal.
On Thu, Mar 3, 2016 at 2:31 AM, Dmitry Dolgov <9erthalion6@gmail.com> wrote:
Well, actually, I agree with that. I can try to rework the patch to achieve
this goal.
Good idea.
I wonder, having taken a quick look at the patch, how this works?:
+select * from test_jsonb_subscript where
test_json['key_doesnt_exists'] = '"value"';
+ id | test_json
+----+-----------
+(0 rows)
Can this use an index, in principle? If not, do you have a plan to get
it to a place where it can? How can the expression be made to map on
to existing indexable operators, such as the containment operator @>,
or even B-Tree opclass operators like = or <=, for example?
This kind of thing was always my main problem with jsonb array-style
subscription. I think it's really quite desirable in theory, but I
also think that these problems need to be fixed first. Think that I
made this point before.
ISTM that these expressions need to be indexable in some way, which
seems like a significantly harder project, especially because the
mapping between an expression in a predicate like this and an
indexable operator like @> is completely non-obvious. Making such a
mapping itself extensible seems even more tricky, which is what it
would take, I suspect. Indexing is always of great importance for
jsonb. It's already too complicated.
--
Peter Geoghega
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi Dmitry,
On 3/3/16 5:31 AM, Dmitry Dolgov wrote:
If the patch were proposing a similar amount of new infrastructure to
support some datatype-extensible concept of subscripting, I'd be much
happier about it.Well, actually, I agree with that. I can try to rework the patch to
achieve this goal.
Do you have an updated patch ready? If so please post it by Monday or
this patch will be marked "returned with feedback".
Thanks,
--
-David
david@pgmasters.net
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Do you have an updated patch ready?
No, I'm afraid it will not be ready for Monday.
On 3/20/16 2:29 PM, Dmitry Dolgov wrote:
Do you have an updated patch ready?
No, I'm afraid it will not be ready for Monday.
I have marked this "returned with feedback". Please feel free to submit
a reworked patch for 9.7!
--
-David
david@pgmasters.net
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers