aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/adt/jsonfuncs.c
diff options
context:
space:
mode:
authorTeodor Sigaev <teodor@sigaev.ru>2016-04-06 19:20:17 +0300
committerTeodor Sigaev <teodor@sigaev.ru>2016-04-06 19:25:00 +0300
commit0b62fd036e1ac48a8432bb9664b21e1f036c1b08 (patch)
tree09a2b9176bb6b7ed77b86af1c212e7e2a18678a5 /src/backend/utils/adt/jsonfuncs.c
parent3b3fcc4eeaeecff315420833975e7c87d760bfe1 (diff)
downloadpostgresql-0b62fd036e1ac48a8432bb9664b21e1f036c1b08.tar.gz
postgresql-0b62fd036e1ac48a8432bb9664b21e1f036c1b08.zip
Add jsonb_insert
It inserts a new value into an jsonb array at arbitrary position or a new key to jsonb object. Author: Dmitry Dolgov Reviewers: Petr Jelinek, Vitaly Burovoy, Andrew Dunstan
Diffstat (limited to 'src/backend/utils/adt/jsonfuncs.c')
-rw-r--r--src/backend/utils/adt/jsonfuncs.c134
1 files changed, 110 insertions, 24 deletions
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 97e0e8e0596..fb149dcd477 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -33,6 +33,15 @@
#include "utils/memutils.h"
#include "utils/typcache.h"
+/* Operations available for setPath */
+#define JB_PATH_NOOP 0x0000
+#define JB_PATH_CREATE 0x0001
+#define JB_PATH_DELETE 0x0002
+#define JB_PATH_INSERT_BEFORE 0x0004
+#define JB_PATH_INSERT_AFTER 0x0008
+#define JB_PATH_CREATE_OR_INSERT \
+ (JB_PATH_INSERT_BEFORE | JB_PATH_INSERT_AFTER | JB_PATH_CREATE)
+
/* 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);
@@ -130,14 +139,14 @@ static JsonbValue *IteratorConcat(JsonbIterator **it1, JsonbIterator **it2,
static JsonbValue *setPath(JsonbIterator **it, Datum *path_elems,
bool *path_nulls, int path_len,
JsonbParseState **st, int level, Jsonb *newval,
- bool create);
+ int op_type);
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, uint32 npairs, int op_type);
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, uint32 nelems, int op_type);
static void addJsonbToParseState(JsonbParseState **jbps, Jsonb *jb);
/* state for json_object_keys */
@@ -3544,7 +3553,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, create ? JB_PATH_CREATE : JB_PATH_NOOP);
Assert(res != NULL);
@@ -3588,7 +3597,52 @@ 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, JB_PATH_DELETE);
+
+ Assert(res != NULL);
+
+ PG_RETURN_JSONB(JsonbValueToJsonb(res));
+}
+
+/*
+ * SQL function jsonb_insert(jsonb, text[], jsonb, boolean)
+ *
+ */
+Datum
+jsonb_insert(PG_FUNCTION_ARGS)
+{
+ Jsonb *in = PG_GETARG_JSONB(0);
+ ArrayType *path = PG_GETARG_ARRAYTYPE_P(1);
+ Jsonb *newval = PG_GETARG_JSONB(2);
+ bool after = PG_GETARG_BOOL(3);
+ JsonbValue *res = NULL;
+ Datum *path_elems;
+ bool *path_nulls;
+ int path_len;
+ JsonbIterator *it;
+ JsonbParseState *st = NULL;
+
+ if (ARR_NDIM(path) > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("wrong number of array subscripts")));
+
+ if (JB_ROOT_IS_SCALAR(in))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot set path in scalar")));
+
+ deconstruct_array(path, TEXTOID, -1, false, 'i',
+ &path_elems, &path_nulls, &path_len);
+
+ if (path_len == 0)
+ PG_RETURN_JSONB(in);
+
+ it = JsonbIteratorInit(&in->root);
+
+ res = setPath(&it, path_elems, path_nulls, path_len, &st, 0, newval,
+ after ? JB_PATH_INSERT_AFTER : JB_PATH_INSERT_BEFORE);
Assert(res != NULL);
@@ -3707,18 +3761,23 @@ IteratorConcat(JsonbIterator **it1, JsonbIterator **it2,
}
/*
- * Do most of the heavy work for jsonb_set
+ * Do most of the heavy work for jsonb_set/jsonb_insert
+ *
+ * If JB_PATH_DELETE bit is set in op_type, the element is to be removed.
*
- * If newval is null, the element is to be removed.
+ * If any bit mentioned in JB_PATH_CREATE_OR_INSERT is set in op_type,
+ * we create the new value if the key or array index does not exist.
*
- * If create is true, we create the new value if the key or array index
- * does not exist. All path elements before the last must already exist
- * whether or not create is true, or nothing is done.
+ * Bits JB_PATH_INSERT_BEFORE and JB_PATH_INSERT_AFTER in op_type
+ * behave as JB_PATH_CREATE if new value is inserted in JsonbObject.
+ *
+ * All path elements before the last must already exist
+ * whatever bits in op_type are set, or nothing is done.
*/
static 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, Jsonb *newval, int op_type)
{
JsonbValue v;
JsonbIteratorToken r;
@@ -3739,7 +3798,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, v.val.array.nElems, op_type);
r = JsonbIteratorNext(it, &v, false);
Assert(r == WJB_END_ARRAY);
res = pushJsonbValue(st, r, NULL);
@@ -3747,7 +3806,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, v.val.object.nPairs, op_type);
r = JsonbIteratorNext(it, &v, true);
Assert(r == WJB_END_OBJECT);
res = pushJsonbValue(st, r, NULL);
@@ -3771,7 +3830,7 @@ 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, uint32 npairs, int op_type)
{
JsonbValue v;
int i;
@@ -3782,7 +3841,8 @@ setPathObject(JsonbIterator **it, Datum *path_elems, bool *path_nulls,
done = true;
/* empty object is a special case for create */
- if ((npairs == 0) && create && (level == path_len - 1))
+ if ((npairs == 0) && (op_type & JB_PATH_CREATE_OR_INSERT) &&
+ (level == path_len - 1))
{
JsonbValue newkey;
@@ -3807,8 +3867,19 @@ setPathObject(JsonbIterator **it, Datum *path_elems, bool *path_nulls,
{
if (level == path_len - 1)
{
- r = JsonbIteratorNext(it, &v, true); /* skip */
- if (newval != NULL)
+ /*
+ * called from jsonb_insert(), it forbids redefining
+ * an existsing value
+ */
+ if (op_type & (JB_PATH_INSERT_BEFORE | JB_PATH_INSERT_AFTER))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errhint("Try using the function jsonb_set "
+ "to replace key value.")));
+
+ r = JsonbIteratorNext(it, &v, true); /* skip value */
+ if (!(op_type & JB_PATH_DELETE))
{
(void) pushJsonbValue(st, WJB_KEY, &k);
addJsonbToParseState(st, newval);
@@ -3819,12 +3890,13 @@ 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, op_type);
}
}
else
{
- if (create && !done && level == path_len - 1 && i == npairs - 1)
+ if ((op_type & JB_PATH_CREATE_OR_INSERT) && !done &&
+ level == path_len - 1 && i == npairs - 1)
{
JsonbValue newkey;
@@ -3865,7 +3937,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, uint32 nelems, int op_type)
{
JsonbValue v;
int idx,
@@ -3909,7 +3981,8 @@ setPathArray(JsonbIterator **it, Datum *path_elems, bool *path_nulls,
* what the idx value is
*/
- if ((idx == INT_MIN || nelems == 0) && create && (level == path_len - 1))
+ if ((idx == INT_MIN || nelems == 0) && (level == path_len - 1) &&
+ (op_type & JB_PATH_CREATE_OR_INSERT))
{
Assert(newval != NULL);
addJsonbToParseState(st, newval);
@@ -3926,14 +3999,26 @@ setPathArray(JsonbIterator **it, Datum *path_elems, bool *path_nulls,
if (level == path_len - 1)
{
r = JsonbIteratorNext(it, &v, true); /* skip */
- if (newval != NULL)
+
+ if (op_type & (JB_PATH_INSERT_BEFORE | JB_PATH_CREATE))
+ addJsonbToParseState(st, newval);
+
+ /*
+ * We should keep current value only in case of
+ * JB_PATH_INSERT_BEFORE or JB_PATH_INSERT_AFTER
+ * because otherwise it should be deleted or replaced
+ */
+ if (op_type & (JB_PATH_INSERT_AFTER | JB_PATH_INSERT_BEFORE))
+ (void) pushJsonbValue(st, r, &v);
+
+ if (op_type & JB_PATH_INSERT_AFTER)
addJsonbToParseState(st, newval);
done = true;
}
else
(void) setPath(it, path_elems, path_nulls, path_len,
- st, level + 1, newval, create);
+ st, level + 1, newval, op_type);
}
else
{
@@ -3958,7 +4043,8 @@ setPathArray(JsonbIterator **it, Datum *path_elems, bool *path_nulls,
}
}
- if (create && !done && level == path_len - 1 && i == nelems - 1)
+ if (op_type & JB_PATH_CREATE_OR_INSERT && !done &&
+ level == path_len - 1 && i == nelems - 1)
{
addJsonbToParseState(st, newval);
}