/* * jsonb_concat: * Concatenation of two jsonb. There are few allowed combinations: * - concatenation of two objects * - concatenation of two arrays * - concatenation of object and array * * The result for first two is new object and array accordingly. * The last one return new array, which contains all elements from * original array, and one extra element (which is actually * other argument of this function with type jbvObject) at the first or last position. */ Datum jsonb_concat(PG_FUNCTION_ARGS) { Jsonb *jb1 = PG_GETARG_JSONB(0); Jsonb *jb2 = PG_GETARG_JSONB(1); Jsonb *out = palloc(VARSIZE(jb1) + VARSIZE(jb2)); JsonbParseState *state = NULL; JsonbValue *res; JsonbIterator *it1, *it2; /* * If one of the jsonb is empty, * just return other. */ if (JB_ROOT_COUNT(jb1) == 0) { memcpy(out, jb2, VARSIZE(jb2)); PG_RETURN_POINTER(out); } else if (JB_ROOT_COUNT(jb2) == 0) { memcpy(out, jb1, VARSIZE(jb1)); PG_RETURN_POINTER(out); } it1 = JsonbIteratorInit(&jb1->root); it2 = JsonbIteratorInit(&jb2->root); res = IteratorConcat(&it1, &it2, &state); if (res == NULL || (res->type == jbvArray && res->val.array.nElems == 0) || (res->type == jbvObject && res->val.object.nPairs == 0) ) { SET_VARSIZE(out, VARHDRSZ); } else { if (res->type == jbvArray && res->val.array.nElems > 1) res->val.array.rawScalar = false; out = JsonbValueToJsonb(res); } PG_RETURN_JSONB(out); }
Datum jsonb_contains(PG_FUNCTION_ARGS) { Jsonb *val = PG_GETARG_JSONB(0); Jsonb *tmpl = PG_GETARG_JSONB(1); JsonbIterator *it1, *it2; if (JB_ROOT_COUNT(val) < JB_ROOT_COUNT(tmpl) || JB_ROOT_IS_OBJECT(val) != JB_ROOT_IS_OBJECT(tmpl)) PG_RETURN_BOOL(false); it1 = JsonbIteratorInit(VARDATA(val)); it2 = JsonbIteratorInit(VARDATA(tmpl)); PG_RETURN_BOOL(JsonbDeepContains(&it1, &it2)); }
Datum gin_extract_jsonb(PG_FUNCTION_ARGS) { Jsonb *jb = (Jsonb *) PG_GETARG_JSONB_P(0); int32 *nentries = (int32 *) PG_GETARG_POINTER(1); int total = 2 * JB_ROOT_COUNT(jb); JsonbIterator *it; JsonbValue v; JsonbIteratorToken r; int i = 0; Datum *entries; /* If the root level is empty, we certainly have no keys */ if (total == 0) { *nentries = 0; PG_RETURN_POINTER(NULL); } /* Otherwise, use 2 * root count as initial estimate of result size */ entries = (Datum *) palloc(sizeof(Datum) * total); it = JsonbIteratorInit(&jb->root); while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) { /* Since we recurse into the object, we might need more space */ if (i >= total) { total *= 2; entries = (Datum *) repalloc(entries, sizeof(Datum) * total); } switch (r) { case WJB_KEY: entries[i++] = make_scalar_key(&v, true); break; case WJB_ELEM: /* Pretend string array elements are keys, see jsonb.h */ entries[i++] = make_scalar_key(&v, (v.type == jbvString)); break; case WJB_VALUE: entries[i++] = make_scalar_key(&v, false); break; default: /* we can ignore structural items */ break; } } *nentries = i; PG_RETURN_POINTER(entries); }
/* * jsonb_delete: * Return copy of jsonb with the specified item removed. * Item is a one key or element from jsonb, specified by name. * If there are many keys or elements with than name, * the first one will be removed. */ Datum jsonb_delete(PG_FUNCTION_ARGS) { Jsonb *in = PG_GETARG_JSONB(0); text *key = PG_GETARG_TEXT_PP(1); char *keyptr = VARDATA_ANY(key); int keylen = VARSIZE_ANY_EXHDR(key); JsonbParseState *state = NULL; JsonbIterator *it; uint32 r; JsonbValue v, *res = NULL; bool skipped = false; if (JB_ROOT_IS_SCALAR(in)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("cannot delete from scalar"))); if (JB_ROOT_COUNT(in) == 0) { PG_RETURN_JSONB(in); } it = JsonbIteratorInit(&in->root); while((r = JsonbIteratorNext(&it, &v, false)) != 0) { if (!skipped && (r == WJB_ELEM || r == WJB_KEY) && (v.type == jbvString && keylen == v.val.string.len && memcmp(keyptr, v.val.string.val, keylen) == 0)) { /* we should delete only one key/element */ skipped = true; if (r == WJB_KEY) { /* skip corresponding value */ JsonbIteratorNext(&it, &v, true); } continue; } res = pushJsonbValue(&state, r, r < WJB_BEGIN_ARRAY ? &v : NULL); } Assert(res != NULL); PG_RETURN_JSONB(JsonbValueToJsonb(res)); }
Datum gin_extract_jsonb(PG_FUNCTION_ARGS) { Jsonb *jb = (Jsonb *) PG_GETARG_JSONB_P(0); int32 *nentries = (int32 *) PG_GETARG_POINTER(1); int total = JB_ROOT_COUNT(jb); JsonbIterator *it; JsonbValue v; JsonbIteratorToken r; GinEntries entries; /* If the root level is empty, we certainly have no keys */ if (total == 0) { *nentries = 0; PG_RETURN_POINTER(NULL); } /* Otherwise, use 2 * root count as initial estimate of result size */ init_gin_entries(&entries, 2 * total); it = JsonbIteratorInit(&jb->root); while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) { switch (r) { case WJB_KEY: add_gin_entry(&entries, make_scalar_key(&v, true)); break; case WJB_ELEM: /* Pretend string array elements are keys, see jsonb.h */ add_gin_entry(&entries, make_scalar_key(&v, v.type == jbvString)); break; case WJB_VALUE: add_gin_entry(&entries, make_scalar_key(&v, false)); break; default: /* we can ignore structural items */ break; } } *nentries = entries.count; PG_RETURN_POINTER(entries.buf); }
/* * jsonb_set: * Replace/create value of jsonb key or jsonb element, which can be found by the specified path. * Path must be replesented as an array of key names or indexes. If indexes will be used, * the same rules implied as for jsonb_delete_idx (negative indexing and edge cases) */ Datum jsonb_set(PG_FUNCTION_ARGS) { Jsonb *in = PG_GETARG_JSONB(0); ArrayType *path = PG_GETARG_ARRAYTYPE_P(1); Jsonb *newval = PG_GETARG_JSONB(2); bool create = 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"))); if (JB_ROOT_COUNT(in) == 0 && !create) { PG_RETURN_JSONB(in); } 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, create); Assert (res != NULL); PG_RETURN_JSONB(JsonbValueToJsonb(res)); }
Datum jsonb_hash_extended(PG_FUNCTION_ARGS) { Jsonb *jb = PG_GETARG_JSONB(0); uint64 seed = PG_GETARG_INT64(1); JsonbIterator *it; JsonbValue v; JsonbIteratorToken r; uint64 hash = 0; if (JB_ROOT_COUNT(jb) == 0) PG_RETURN_UINT64(seed); it = JsonbIteratorInit(&jb->root); while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) { switch (r) { /* Rotation is left to JsonbHashScalarValueExtended() */ case WJB_BEGIN_ARRAY: hash ^= ((uint64) JB_FARRAY) << 32 | JB_FARRAY; break; case WJB_BEGIN_OBJECT: hash ^= ((uint64) JB_FOBJECT) << 32 | JB_FOBJECT; break; case WJB_KEY: case WJB_VALUE: case WJB_ELEM: JsonbHashScalarValueExtended(&v, &hash, seed); break; case WJB_END_ARRAY: case WJB_END_OBJECT: break; default: elog(ERROR, "invalid JsonbIteratorNext rc: %d", (int) r); } } PG_FREE_IF_COPY(jb, 0); PG_RETURN_UINT64(hash); }
/* * Hash operator class jsonb hashing function */ Datum jsonb_hash(PG_FUNCTION_ARGS) { Jsonb *jb = PG_GETARG_JSONB(0); JsonbIterator *it; int32 r; JsonbValue v; uint32 hash = 0; if (JB_ROOT_COUNT(jb) == 0) PG_RETURN_INT32(0); it = JsonbIteratorInit(VARDATA(jb)); while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) { switch (r) { /* Rotation is left to JsonbHashScalarValue() */ case WJB_BEGIN_ARRAY: hash ^= JB_FARRAY; break; case WJB_BEGIN_OBJECT: hash ^= JB_FOBJECT; break; case WJB_KEY: case WJB_VALUE: case WJB_ELEM: JsonbHashScalarValue(&v, &hash); break; case WJB_END_ARRAY: case WJB_END_OBJECT: break; default: elog(ERROR, "invalid JsonbIteratorNext rc: %d", r); } } PG_FREE_IF_COPY(jb, 0); PG_RETURN_INT32(hash); }
Datum jsonb_bool(PG_FUNCTION_ARGS) { Jsonb *j = PG_GETARG_JSONB(0); if (JB_ROOT_IS_SCALAR(j)) { JsonbValue *jv; jv = getIthJsonbValueFromContainer(&j->root, 0); switch (jv->type) { case jbvNull: PG_RETURN_NULL(); case jbvString: PG_RETURN_BOOL(jv->val.string.len > 0); case jbvNumeric: { Datum b; if (numeric_is_nan(jv->val.numeric)) PG_RETURN_BOOL(false); b = DirectFunctionCall2(numeric_ne, NumericGetDatum(jv->val.numeric), get_numeric_0_datum()); PG_RETURN_DATUM(b); } case jbvBool: PG_RETURN_BOOL(jv->val.boolean); default: elog(ERROR, "unknown jsonb scalar type"); } } Assert(JB_ROOT_IS_OBJECT(j) || JB_ROOT_IS_ARRAY(j)); PG_RETURN_BOOL(JB_ROOT_COUNT(j) > 0); }
Datum gin_extract_jsonb_path(PG_FUNCTION_ARGS) { Jsonb *jb = PG_GETARG_JSONB_P(0); int32 *nentries = (int32 *) PG_GETARG_POINTER(1); int total = 2 * JB_ROOT_COUNT(jb); JsonbIterator *it; JsonbValue v; JsonbIteratorToken r; PathHashStack tail; PathHashStack *stack; int i = 0; Datum *entries; /* If the root level is empty, we certainly have no keys */ if (total == 0) { *nentries = 0; PG_RETURN_POINTER(NULL); } /* Otherwise, use 2 * root count as initial estimate of result size */ entries = (Datum *) palloc(sizeof(Datum) * total); /* We keep a stack of partial hashes corresponding to parent key levels */ tail.parent = NULL; tail.hash = 0; stack = &tail; it = JsonbIteratorInit(&jb->root); while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) { PathHashStack *parent; /* Since we recurse into the object, we might need more space */ if (i >= total) { total *= 2; entries = (Datum *) repalloc(entries, sizeof(Datum) * total); } switch (r) { case WJB_BEGIN_ARRAY: case WJB_BEGIN_OBJECT: /* Push a stack level for this object */ parent = stack; stack = (PathHashStack *) palloc(sizeof(PathHashStack)); /* * We pass forward hashes from outer nesting levels so that * the hashes for nested values will include outer keys as * well as their own keys. * * Nesting an array within another array will not alter * innermost scalar element hash values, but that seems * inconsequential. */ stack->hash = parent->hash; stack->parent = parent; break; case WJB_KEY: /* mix this key into the current outer hash */ JsonbHashScalarValue(&v, &stack->hash); /* hash is now ready to incorporate the value */ break; case WJB_ELEM: case WJB_VALUE: /* mix the element or value's hash into the prepared hash */ JsonbHashScalarValue(&v, &stack->hash); /* and emit an index entry */ entries[i++] = UInt32GetDatum(stack->hash); /* reset hash for next key, value, or sub-object */ stack->hash = stack->parent->hash; break; case WJB_END_ARRAY: case WJB_END_OBJECT: /* Pop the stack */ parent = stack->parent; pfree(stack); stack = parent; /* reset hash for next key, value, or sub-object */ if (stack->parent) stack->hash = stack->parent->hash; else stack->hash = 0; break; default: elog(ERROR, "invalid JsonbIteratorNext rc: %d", (int) r); } } *nentries = i; PG_RETURN_POINTER(entries); }
Datum gin_extract_jsonb(PG_FUNCTION_ARGS) { Jsonb *jb = (Jsonb *) PG_GETARG_JSONB(0); int32 *nentries = (int32 *) PG_GETARG_POINTER(1); Datum *entries = NULL; int total = 2 * JB_ROOT_COUNT(jb); int i = 0, r; JsonbIterator *it; JsonbValue v; if (total == 0) { *nentries = 0; PG_RETURN_POINTER(NULL); } entries = (Datum *) palloc(sizeof(Datum) * total); it = JsonbIteratorInit(VARDATA(jb)); while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) { if (i >= total) { total *= 2; entries = (Datum *) repalloc(entries, sizeof(Datum) * total); } /* * Serialize keys and elements equivalently, but only when elements * are Jsonb strings. Otherwise, serialize elements as values. Array * elements are indexed as keys, for the benefit of * JsonbExistsStrategyNumber. Our definition of existence does not * allow for checking the existence of a non-jbvString element (just * like the definition of the underlying operator), because the * operator takes a text rhs argument (which is taken as a proxy for an * equivalent Jsonb string). * * The way existence is represented does not preclude an alternative * existence operator, that takes as its rhs value an arbitrarily * internally-typed Jsonb. The only reason that isn't the case here is * that the existence operator is only really intended to determine if * an object has a certain key (object pair keys are of course * invariably strings), which is extended to jsonb arrays. You could * think of the default Jsonb definition of existence as being * equivalent to a definition where all types of scalar array elements * are keys that we can check the existence of, while just forbidding * non-string notation. This inflexibility prevents the user from * having to qualify that the rhs string is a raw scalar string (that * is, naturally no internal string quoting in required for the text * argument), and allows us to not set the reset flag for * JsonbExistsStrategyNumber, since we know that keys are strings for * both objects and arrays, and don't have to further account for type * mismatch. Not having to set the reset flag makes it less than * tempting to tighten up the definition of existence to preclude array * elements entirely, which would arguably be a simpler alternative. * In any case the infrastructure used to implement the existence * operator could trivially support this hypothetical, slightly * distinct definition of existence. */ switch (r) { case WJB_KEY: /* Serialize key separately, for existence strategies */ entries[i++] = PointerGetDatum(make_scalar_key(&v, JKEYELEM)); break; case WJB_ELEM: if (v.type == jbvString) entries[i++] = PointerGetDatum(make_scalar_key(&v, JKEYELEM)); else entries[i++] = PointerGetDatum(make_scalar_key(&v, JVAL)); break; case WJB_VALUE: entries[i++] = PointerGetDatum(make_scalar_key(&v, JVAL)); break; default: continue; } } *nentries = i; PG_RETURN_POINTER(entries); }
Datum gin_extract_jsonb_hash(PG_FUNCTION_ARGS) { Jsonb *jb = PG_GETARG_JSONB(0); int32 *nentries = (int32 *) PG_GETARG_POINTER(1); int total = 2 * JB_ROOT_COUNT(jb); JsonbIterator *it; JsonbValue v; PathHashStack tail; PathHashStack *stack; int i = 0, r; Datum *entries = NULL; if (total == 0) { *nentries = 0; PG_RETURN_POINTER(NULL); } entries = (Datum *) palloc(sizeof(Datum) * total); it = JsonbIteratorInit(VARDATA(jb)); tail.parent = NULL; tail.hash = 0; stack = &tail; while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) { PathHashStack *tmp; if (i >= total) { total *= 2; entries = (Datum *) repalloc(entries, sizeof(Datum) * total); } switch (r) { case WJB_BEGIN_ARRAY: case WJB_BEGIN_OBJECT: tmp = stack; stack = (PathHashStack *) palloc(sizeof(PathHashStack)); /* * Nesting an array within another array will not alter * innermost scalar element hash values, but that seems * inconsequential */ if (tmp->parent) { /* * We pass forward hashes from previous container nesting * levels so that nested arrays with an outermost nested * object will have element hashes mixed with the outermost * key. It's also somewhat useful to have nested objects * innermost values have hashes that are a function of not * just their own key, but outer keys too. */ stack->hash = tmp->hash; } else { /* * At least nested level, initialize with stable container * type proxy value */ stack->hash = (r == WJB_BEGIN_ARRAY)? JB_FARRAY:JB_FOBJECT; } stack->parent = tmp; break; case WJB_KEY: /* Initialize hash from parent */ stack->hash = stack->parent->hash; JsonbHashScalarValue(&v, &stack->hash); break; case WJB_ELEM: /* Elements have parent hash mixed in separately */ stack->hash = stack->parent->hash; case WJB_VALUE: /* Element/value case */ JsonbHashScalarValue(&v, &stack->hash); entries[i++] = stack->hash; break; case WJB_END_ARRAY: case WJB_END_OBJECT: /* Pop the stack */ tmp = stack->parent; pfree(stack); stack = tmp; break; default: elog(ERROR, "invalid JsonbIteratorNext rc: %d", r); } } *nentries = i; PG_RETURN_POINTER(entries); }
Datum gin_extract_jsonb_path(PG_FUNCTION_ARGS) { Jsonb *jb = PG_GETARG_JSONB(0); int32 *nentries = (int32 *) PG_GETARG_POINTER(1); int total = 2 * JB_ROOT_COUNT(jb); JsonbIterator *it; JsonbValue v; JsonbIteratorToken r; PathHashStack tail; PathHashStack *stack; int i = 0; Datum *entries; /* If the root level is empty, we certainly have no keys */ if (total == 0) { *nentries = 0; PG_RETURN_POINTER(NULL); } /* Otherwise, use 2 * root count as initial estimate of result size */ entries = (Datum *) palloc(sizeof(Datum) * total); /* We keep a stack of partial hashes corresponding to parent key levels */ tail.parent = NULL; tail.hash = 0; stack = &tail; it = JsonbIteratorInit(&jb->root); while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) { PathHashStack *parent; /* Since we recurse into the object, we might need more space */ if (i >= total) { total *= 2; entries = (Datum *) repalloc(entries, sizeof(Datum) * total); } switch (r) { case WJB_BEGIN_ARRAY: case WJB_BEGIN_OBJECT: /* Push a stack level for this object */ parent = stack; stack = (PathHashStack *) palloc(sizeof(PathHashStack)); if (parent->parent) { /* * We pass forward hashes from previous container nesting * levels so that nested arrays with an outermost nested * object will have element hashes mixed with the * outermost key. It's also somewhat useful to have * nested objects' innermost values have hashes that are a * function of not just their own key, but outer keys too. * * Nesting an array within another array will not alter * innermost scalar element hash values, but that seems * inconsequential. */ stack->hash = parent->hash; } else { /* * At the outermost level, initialize hash with container * type proxy value. Note that this makes JB_FARRAY and * JB_FOBJECT part of the on-disk representation, but they * are that in the base jsonb object storage already. */ stack->hash = (r == WJB_BEGIN_ARRAY) ? JB_FARRAY : JB_FOBJECT; } stack->parent = parent; break; case WJB_KEY: /* initialize hash from parent */ stack->hash = stack->parent->hash; /* and mix in this key */ JsonbHashScalarValue(&v, &stack->hash); /* hash is now ready to incorporate the value */ break; case WJB_ELEM: /* array elements use parent hash mixed with element's hash */ stack->hash = stack->parent->hash; /* FALL THRU */ case WJB_VALUE: /* mix the element or value's hash into the prepared hash */ JsonbHashScalarValue(&v, &stack->hash); /* and emit an index entry */ entries[i++] = UInt32GetDatum(stack->hash); /* Note: we assume we'll see KEY before another VALUE */ break; case WJB_END_ARRAY: case WJB_END_OBJECT: /* Pop the stack */ parent = stack->parent; pfree(stack); stack = parent; break; default: elog(ERROR, "invalid JsonbIteratorNext rc: %d", (int) r); } } *nentries = i; PG_RETURN_POINTER(entries); }
/* * jsonb_delete_idx: * Return a copy of jsonb withour specified items. * Delete key (only from the top level of object) or element from jsonb by index (idx). * Negative idx value is supported, and it implies the countdown from the last key/element. * If idx is more, than numbers of keys/elements, or equal - nothing will be deleted. * If idx is negative and -idx is more, than number of keys/elements - the last one will be deleted. * * TODO: take care about nesting values. */ Datum jsonb_delete_idx(PG_FUNCTION_ARGS) { Jsonb *in = PG_GETARG_JSONB(0); int idx = PG_GETARG_INT32(1); JsonbParseState *state = NULL; JsonbIterator *it; uint32 r, i = 0, n; JsonbValue v, *res = NULL; if (JB_ROOT_IS_SCALAR(in)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("cannot delete from scalar"))); if (JB_ROOT_COUNT(in) == 0) { PG_RETURN_JSONB(in); } it = JsonbIteratorInit(&in->root); r = JsonbIteratorNext(&it, &v, false); if (r == WJB_BEGIN_ARRAY) n = v.val.array.nElems; else n = v.val.object.nPairs; if (idx < 0) { if (-idx > n) idx = n; else idx = n + idx; } if (idx >= n) { PG_RETURN_JSONB(in); } pushJsonbValue(&state, r, r < WJB_BEGIN_ARRAY ? &v : NULL); while((r = JsonbIteratorNext(&it, &v, true)) != 0) { if (r == WJB_ELEM || r == WJB_KEY) { if (i++ == idx) { if (r == WJB_KEY) JsonbIteratorNext(&it, &v, true); /* skip value */ continue; } } res = pushJsonbValue(&state, r, r < WJB_BEGIN_ARRAY ? &v : NULL); } Assert (res != NULL); PG_RETURN_JSONB(JsonbValueToJsonb(res)); }