aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/adt/jsonb_util.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/utils/adt/jsonb_util.c')
-rw-r--r--src/backend/utils/adt/jsonb_util.c929
1 files changed, 341 insertions, 588 deletions
diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index 411144618d6..f6d6fab74e8 100644
--- a/src/backend/utils/adt/jsonb_util.c
+++ b/src/backend/utils/adt/jsonb_util.c
@@ -1,7 +1,7 @@
/*-------------------------------------------------------------------------
*
* jsonb_util.c
- * Utilities for jsonb datatype
+ * converting between Jsonb and JsonbValues, and iterating.
*
* Copyright (c) 2014, PostgreSQL Global Development Group
*
@@ -15,7 +15,6 @@
#include "access/hash.h"
#include "catalog/pg_collation.h"
-#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/jsonb.h"
@@ -38,49 +37,30 @@
JENTRY_POSMASK))
/*
- * State used while converting an arbitrary JsonbValue into a Jsonb value
- * (4-byte varlena uncompressed representation of a Jsonb)
- *
- * ConvertLevel: Bookkeeping around particular level when converting.
- */
-typedef struct convertLevel
-{
- uint32 i; /* Iterates once per element, or once per pair */
- uint32 *header; /* Pointer to current container header */
- JEntry *meta; /* This level's metadata */
- char *begin; /* Pointer into convertState.buffer */
-} convertLevel;
-
-/*
- * convertState: Overall bookkeeping state for conversion
+ * convertState: a resizeable buffer used when constructing a Jsonb datum
*/
-typedef struct convertState
+typedef struct
{
- /* Preallocated buffer in which to form varlena/Jsonb value */
- Jsonb *buffer;
- /* Pointer into buffer */
- char *ptr;
-
- /* State for */
- convertLevel *allState, /* Overall state array */
- *contPtr; /* Cur container pointer (in allState) */
-
- /* Current size of buffer containing allState array */
- Size levelSz;
-
+ char *buffer;
+ int len;
+ int allocatedsz;
} convertState;
+static void fillJsonbValue(JEntry *entry, char *payload_base, JsonbValue *result);
static int compareJsonbScalarValue(JsonbValue *a, JsonbValue *b);
static int lexicalCompareJsonbStringValue(const void *a, const void *b);
-static Size convertJsonb(JsonbValue *val, Jsonb *buffer);
-static inline short addPaddingInt(convertState *cstate);
-static void walkJsonbValueConversion(JsonbValue *val, convertState *cstate,
- uint32 nestlevel);
-static void putJsonbValueConversion(convertState *cstate, JsonbValue *val,
- uint32 flags, uint32 level);
-static void putScalarConversion(convertState *cstate, JsonbValue *scalarVal,
- uint32 level, uint32 i);
-static void iteratorFromContainerBuf(JsonbIterator *it, char *buffer);
+static Jsonb *convertToJsonb(JsonbValue *val);
+static void convertJsonbValue(convertState *buffer, JEntry *header, JsonbValue *val, int level);
+static void convertJsonbArray(convertState *buffer, JEntry *header, JsonbValue *val, int level);
+static void convertJsonbObject(convertState *buffer, JEntry *header, JsonbValue *val, int level);
+static void convertJsonbScalar(convertState *buffer, JEntry *header, JsonbValue *scalarVal);
+
+static int reserveFromBuffer(convertState *buffer, int len);
+static void appendToBuffer(convertState *buffer, char *data, int len);
+static void copyToBuffer(convertState *buffer, int offset, char *data, int len);
+static short padBufferToInt(convertState *buffer);
+
+static void iteratorFromContainer(JsonbIterator *it, JsonbContainer *container);
static bool formIterIsContainer(JsonbIterator **it, JsonbValue *val,
JEntry *ent, bool skipNested);
static JsonbIterator *freeAndGetParent(JsonbIterator *it);
@@ -91,7 +71,6 @@ static void appendElement(JsonbParseState *pstate, JsonbValue *scalarVal);
static int lengthCompareJsonbStringValue(const void *a, const void *b, void *arg);
static int lengthCompareJsonbPair(const void *a, const void *b, void *arg);
static void uniqueifyJsonbObject(JsonbValue *object);
-static void uniqueifyJsonbArray(JsonbValue *array);
/*
* Turn an in-memory JsonbValue into a Jsonb for on-disk storage.
@@ -110,7 +89,6 @@ Jsonb *
JsonbValueToJsonb(JsonbValue *val)
{
Jsonb *out;
- Size sz;
if (IsAJsonbScalar(val))
{
@@ -127,17 +105,11 @@ JsonbValueToJsonb(JsonbValue *val)
pushJsonbValue(&pstate, WJB_ELEM, val);
res = pushJsonbValue(&pstate, WJB_END_ARRAY, NULL);
- out = palloc(VARHDRSZ + res->estSize);
- sz = convertJsonb(res, out);
- Assert(sz <= res->estSize);
- SET_VARSIZE(out, sz + VARHDRSZ);
+ out = convertToJsonb(res);
}
else if (val->type == jbvObject || val->type == jbvArray)
{
- out = palloc(VARHDRSZ + val->estSize);
- sz = convertJsonb(val, out);
- Assert(sz <= val->estSize);
- SET_VARSIZE(out, VARHDRSZ + sz);
+ out = convertToJsonb(val);
}
else
{
@@ -161,7 +133,7 @@ JsonbValueToJsonb(JsonbValue *val)
* memory here.
*/
int
-compareJsonbSuperHeaderValue(JsonbSuperHeader a, JsonbSuperHeader b)
+compareJsonbContainers(JsonbContainer *a, JsonbContainer *b)
{
JsonbIterator *ita,
*itb;
@@ -288,90 +260,51 @@ compareJsonbSuperHeaderValue(JsonbSuperHeader a, JsonbSuperHeader b)
*
* In order to proceed with the search, it is necessary for callers to have
* both specified an interest in exactly one particular container type with an
- * appropriate flag, as well as having the pointed-to Jsonb superheader be of
+ * appropriate flag, as well as having the pointed-to Jsonb container be of
* one of those same container types at the top level. (Actually, we just do
* whichever makes sense to save callers the trouble of figuring it out - at
- * most one can make sense, because the super header either points to an array
- * (possible a "raw scalar" pseudo array) or an object.)
+ * most one can make sense, because the container either points to an array
+ * (possibly a "raw scalar" pseudo array) or an object.)
*
* Note that we can return a jbvBinary JsonbValue if this is called on an
* object, but we never do so on an array. If the caller asks to look through
- * a container type that is not of the type pointed to by the superheader,
+ * a container type that is not of the type pointed to by the container,
* immediately fall through and return NULL. If we cannot find the value,
* return NULL. Otherwise, return palloc()'d copy of value.
- *
- * lowbound can be NULL, but if not it's used to establish a point at which to
- * start searching. If the value searched for is found, then lowbound is then
- * set to an offset into the array or object. Typically, this is used to
- * exploit the ordering of objects to avoid redundant work, by also sorting a
- * list of items to be checked using the internal sort criteria for objects
- * (object pair keys), and then, when searching for the second or subsequent
- * item, picking it up where we left off knowing that the second or subsequent
- * item can not be at a point below the low bound set when the first was found.
- * This is only useful for objects, not arrays (which have a user-defined
- * order), so array superheader Jsonbs should just pass NULL. Moreover, it's
- * only useful because we only match object pairs on the basis of their key, so
- * presumably anyone exploiting this is only interested in matching Object keys
- * with a String. lowbound is given in units of pairs, not underlying values.
*/
JsonbValue *
-findJsonbValueFromSuperHeader(JsonbSuperHeader sheader, uint32 flags,
- uint32 *lowbound, JsonbValue *key)
+findJsonbValueFromContainer(JsonbContainer *container, uint32 flags,
+ JsonbValue *key)
{
- uint32 superheader = *(uint32 *) sheader;
- JEntry *array = (JEntry *) (sheader + sizeof(uint32));
- int count = (superheader & JB_CMASK);
+ JEntry *array = container->children;
+ int count = (container->header & JB_CMASK);
JsonbValue *result = palloc(sizeof(JsonbValue));
Assert((flags & ~(JB_FARRAY | JB_FOBJECT)) == 0);
- if (flags & JB_FARRAY & superheader)
+ if (flags & JB_FARRAY & container->header)
{
- char *data = (char *) (array + (superheader & JB_CMASK));
+ char *data = (char *) (array + (container->header & JB_CMASK));
int i;
for (i = 0; i < count; i++)
{
JEntry *e = array + i;
- if (JBE_ISNULL(*e) && key->type == jbvNull)
- {
- result->type = jbvNull;
- result->estSize = sizeof(JEntry);
- }
- else if (JBE_ISSTRING(*e) && key->type == jbvString)
- {
- result->type = jbvString;
- result->val.string.val = data + JBE_OFF(*e);
- result->val.string.len = JBE_LEN(*e);
- result->estSize = sizeof(JEntry) + result->val.string.len;
- }
- else if (JBE_ISNUMERIC(*e) && key->type == jbvNumeric)
- {
- result->type = jbvNumeric;
- result->val.numeric = (Numeric) (data + INTALIGN(JBE_OFF(*e)));
+ fillJsonbValue(e, data, result);
- result->estSize = 2 * sizeof(JEntry) +
- VARSIZE_ANY(result->val.numeric);
- }
- else if (JBE_ISBOOL(*e) && key->type == jbvBool)
+ if (key->type == result->type)
{
- result->type = jbvBool;
- result->val.boolean = JBE_ISBOOL_TRUE(*e) != 0;
- result->estSize = sizeof(JEntry);
+ if (compareJsonbScalarValue(key, result) == 0)
+ return result;
}
- else
- continue;
-
- if (compareJsonbScalarValue(key, result) == 0)
- return result;
}
}
- else if (flags & JB_FOBJECT & superheader)
+ else if (flags & JB_FOBJECT & container->header)
{
/* Since this is an object, account for *Pairs* of Jentrys */
- char *data = (char *) (array + (superheader & JB_CMASK) * 2);
- uint32 stopLow = lowbound ? *lowbound : 0,
+ char *data = (char *) (array + (container->header & JB_CMASK) * 2);
+ uint32 stopLow = 0,
stopMiddle;
/* Object key past by caller must be a string */
@@ -395,7 +328,6 @@ findJsonbValueFromSuperHeader(JsonbSuperHeader sheader, uint32 flags,
candidate.type = jbvString;
candidate.val.string.val = data + JBE_OFF(*entry);
candidate.val.string.len = JBE_LEN(*entry);
- candidate.estSize = sizeof(JEntry) + candidate.val.string.len;
difference = lengthCompareJsonbStringValue(&candidate, key, NULL);
@@ -404,47 +336,7 @@ findJsonbValueFromSuperHeader(JsonbSuperHeader sheader, uint32 flags,
/* Found our value (from key/value pair) */
JEntry *v = entry + 1;
- if (lowbound)
- *lowbound = stopMiddle + 1;
-
- if (JBE_ISNULL(*v))
- {
- result->type = jbvNull;
- result->estSize = sizeof(JEntry);
- }
- else if (JBE_ISSTRING(*v))
- {
- result->type = jbvString;
- result->val.string.val = data + JBE_OFF(*v);
- result->val.string.len = JBE_LEN(*v);
- result->estSize = sizeof(JEntry) + result->val.string.len;
- }
- else if (JBE_ISNUMERIC(*v))
- {
- result->type = jbvNumeric;
- result->val.numeric = (Numeric) (data + INTALIGN(JBE_OFF(*v)));
-
- result->estSize = 2 * sizeof(JEntry) +
- VARSIZE_ANY(result->val.numeric);
- }
- else if (JBE_ISBOOL(*v))
- {
- result->type = jbvBool;
- result->val.boolean = JBE_ISBOOL_TRUE(*v) != 0;
- result->estSize = sizeof(JEntry);
- }
- else
- {
- /*
- * See header comments to understand why this never
- * happens with arrays
- */
- result->type = jbvBinary;
- result->val.binary.data = data + INTALIGN(JBE_OFF(*v));
- result->val.binary.len = JBE_LEN(*v) -
- (INTALIGN(JBE_OFF(*v)) - JBE_OFF(*v));
- result->estSize = 2 * sizeof(JEntry) + result->val.binary.len;
- }
+ fillJsonbValue(v, data, result);
return result;
}
@@ -456,9 +348,6 @@ findJsonbValueFromSuperHeader(JsonbSuperHeader sheader, uint32 flags,
count = stopMiddle;
}
}
-
- if (lowbound)
- *lowbound = stopLow;
}
/* Not found */
@@ -467,70 +356,80 @@ findJsonbValueFromSuperHeader(JsonbSuperHeader sheader, uint32 flags,
}
/*
- * Get i-th value of Jsonb array from superheader.
+ * Get i-th value of a Jsonb array.
*
- * Returns palloc()'d copy of value.
+ * Returns palloc()'d copy of the value, or NULL if it does not exist.
*/
JsonbValue *
-getIthJsonbValueFromSuperHeader(JsonbSuperHeader sheader, uint32 i)
+getIthJsonbValueFromContainer(JsonbContainer *container, uint32 i)
{
- uint32 superheader = *(uint32 *) sheader;
JsonbValue *result;
- JEntry *array,
- *e;
+ JEntry *e;
char *data;
+ uint32 nelements;
- result = palloc(sizeof(JsonbValue));
+ if ((container->header & JB_FARRAY) == 0)
+ elog(ERROR, "not a jsonb array");
+
+ nelements = container->header & JB_CMASK;
- if (i >= (superheader & JB_CMASK))
+ if (i >= nelements)
return NULL;
- array = (JEntry *) (sheader + sizeof(uint32));
+ e = &container->children[i];
- if (superheader & JB_FARRAY)
- {
- e = array + i;
- data = (char *) (array + (superheader & JB_CMASK));
- }
- else
- {
- elog(ERROR, "not a jsonb array");
- }
+ data = (char *) &container->children[nelements];
- if (JBE_ISNULL(*e))
+ result = palloc(sizeof(JsonbValue));
+
+ fillJsonbValue(e, data, result);
+
+ return result;
+}
+
+/*
+ * Given the JEntry header, and the base address of the data that the offset
+ * in the JEntry refers to, fill a JsonbValue.
+ *
+ * An array or object will be returned as jbvBinary, ie. it won't be
+ * expanded.
+ */
+static void
+fillJsonbValue(JEntry *entry, char *payload_base, JsonbValue *result)
+{
+ if (JBE_ISNULL(*entry))
{
result->type = jbvNull;
- result->estSize = sizeof(JEntry);
}
- else if (JBE_ISSTRING(*e))
+ else if (JBE_ISSTRING(*entry))
{
result->type = jbvString;
- result->val.string.val = data + JBE_OFF(*e);
- result->val.string.len = JBE_LEN(*e);
- result->estSize = sizeof(JEntry) + result->val.string.len;
+ result->val.string.val = payload_base + JBE_OFF(*entry);
+ result->val.string.len = JBE_LEN(*entry);
+ Assert(result->val.string.len >= 0);
}
- else if (JBE_ISNUMERIC(*e))
+ else if (JBE_ISNUMERIC(*entry))
{
result->type = jbvNumeric;
- result->val.numeric = (Numeric) (data + INTALIGN(JBE_OFF(*e)));
-
- result->estSize = 2 * sizeof(JEntry) + VARSIZE_ANY(result->val.numeric);
+ result->val.numeric = (Numeric) (payload_base + INTALIGN(JBE_OFF(*entry)));
+ }
+ else if (JBE_ISBOOL_TRUE(*entry))
+ {
+ result->type = jbvBool;
+ result->val.boolean = true;
}
- else if (JBE_ISBOOL(*e))
+ else if (JBE_ISBOOL_FALSE(*entry))
{
result->type = jbvBool;
- result->val.boolean = JBE_ISBOOL_TRUE(*e) != 0;
- result->estSize = sizeof(JEntry);
+ result->val.boolean = false;
}
else
{
+ Assert(JBE_ISCONTAINER(*entry));
result->type = jbvBinary;
- result->val.binary.data = data + INTALIGN(JBE_OFF(*e));
- result->val.binary.len = JBE_LEN(*e) - (INTALIGN(JBE_OFF(*e)) - JBE_OFF(*e));
- result->estSize = result->val.binary.len + 2 * sizeof(JEntry);
+ result->val.binary.data = (JsonbContainer *) (payload_base + INTALIGN(JBE_OFF(*entry)));
+ result->val.binary.len = JBE_LEN(*entry) - (INTALIGN(JBE_OFF(*entry)) - JBE_OFF(*entry));
}
-
- return result;
}
/*
@@ -547,7 +446,8 @@ getIthJsonbValueFromSuperHeader(JsonbSuperHeader sheader, uint32 i)
* "raw scalar" pseudo array to append that.
*/
JsonbValue *
-pushJsonbValue(JsonbParseState **pstate, int seq, JsonbValue *scalarVal)
+pushJsonbValue(JsonbParseState **pstate, JsonbIteratorToken seq,
+ JsonbValue *scalarVal)
{
JsonbValue *result = NULL;
@@ -558,7 +458,6 @@ pushJsonbValue(JsonbParseState **pstate, int seq, JsonbValue *scalarVal)
*pstate = pushState(pstate);
result = &(*pstate)->contVal;
(*pstate)->contVal.type = jbvArray;
- (*pstate)->contVal.estSize = 3 * sizeof(JEntry);
(*pstate)->contVal.val.array.nElems = 0;
(*pstate)->contVal.val.array.rawScalar = (scalarVal &&
scalarVal->val.array.rawScalar);
@@ -580,7 +479,6 @@ pushJsonbValue(JsonbParseState **pstate, int seq, JsonbValue *scalarVal)
*pstate = pushState(pstate);
result = &(*pstate)->contVal;
(*pstate)->contVal.type = jbvObject;
- (*pstate)->contVal.estSize = 3 * sizeof(JEntry);
(*pstate)->contVal.val.object.nPairs = 0;
(*pstate)->size = 4;
(*pstate)->contVal.val.object.pairs = palloc(sizeof(JsonbPair) *
@@ -602,6 +500,7 @@ pushJsonbValue(JsonbParseState **pstate, int seq, JsonbValue *scalarVal)
break;
case WJB_END_OBJECT:
uniqueifyJsonbObject(&(*pstate)->contVal);
+ /* fall through! */
case WJB_END_ARRAY:
/* Steps here common to WJB_END_OBJECT case */
Assert(!scalarVal);
@@ -635,17 +534,17 @@ pushJsonbValue(JsonbParseState **pstate, int seq, JsonbValue *scalarVal)
}
/*
- * Given a Jsonb superheader, expand to JsonbIterator to iterate over items
+ * Given a JsonbContainer, expand to JsonbIterator to iterate over items
* fully expanded to in-memory representation for manipulation.
*
* See JsonbIteratorNext() for notes on memory management.
*/
JsonbIterator *
-JsonbIteratorInit(JsonbSuperHeader sheader)
+JsonbIteratorInit(JsonbContainer *container)
{
JsonbIterator *it = palloc(sizeof(JsonbIterator));
- iteratorFromContainerBuf(it, sheader);
+ iteratorFromContainer(it, container);
it->parent = NULL;
return it;
@@ -679,7 +578,7 @@ JsonbIteratorInit(JsonbSuperHeader sheader)
* or Object element/pair buffers, since their element/pair pointers are
* garbage.
*/
-int
+JsonbIteratorToken
JsonbIteratorNext(JsonbIterator **it, JsonbValue *val, bool skipNested)
{
JsonbIterState state;
@@ -875,10 +774,9 @@ JsonbDeepContains(JsonbIterator **val, JsonbIterator **mContained)
Assert(rcont == WJB_KEY);
/* First, find value by key... */
- lhsVal = findJsonbValueFromSuperHeader((*val)->buffer,
- JB_FOBJECT,
- NULL,
- &vcontained);
+ lhsVal = findJsonbValueFromContainer((JsonbContainer *) (*val)->buffer,
+ JB_FOBJECT,
+ &vcontained);
if (!lhsVal)
return false;
@@ -978,10 +876,9 @@ JsonbDeepContains(JsonbIterator **val, JsonbIterator **mContained)
if (IsAJsonbScalar(&vcontained))
{
- if (!findJsonbValueFromSuperHeader((*val)->buffer,
- JB_FARRAY,
- NULL,
- &vcontained))
+ if (!findJsonbValueFromContainer((JsonbContainer *) (*val)->buffer,
+ JB_FARRAY,
+ &vcontained))
return false;
}
else
@@ -1057,63 +954,6 @@ JsonbDeepContains(JsonbIterator **val, JsonbIterator **mContained)
}
/*
- * Convert a Postgres text array to a Jsonb array, sorted and with
- * de-duplicated key elements. This is used for searching an object for items
- * in the array, so we enforce that the number of strings cannot exceed
- * JSONB_MAX_PAIRS.
- */
-JsonbValue *
-arrayToJsonbSortedArray(ArrayType *array)
-{
- Datum *key_datums;
- bool *key_nulls;
- int elem_count;
- JsonbValue *result;
- int i,
- j;
-
- /* Extract data for sorting */
- deconstruct_array(array, TEXTOID, -1, false, 'i', &key_datums, &key_nulls,
- &elem_count);
-
- if (elem_count == 0)
- return NULL;
-
- /*
- * A text array uses at least eight bytes per element, so any overflow in
- * "key_count * sizeof(JsonbPair)" is small enough for palloc() to catch.
- * However, credible improvements to the array format could invalidate
- * that assumption. Therefore, use an explicit check rather than relying
- * on palloc() to complain.
- */
- if (elem_count > JSONB_MAX_PAIRS)
- ereport(ERROR,
- (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
- errmsg("number of array elements (%d) exceeds maximum allowed Jsonb pairs (%zu)",
- elem_count, JSONB_MAX_PAIRS)));
-
- result = palloc(sizeof(JsonbValue));
- result->type = jbvArray;
- result->val.array.rawScalar = false;
- result->val.array.elems = palloc(sizeof(JsonbPair) * elem_count);
-
- for (i = 0, j = 0; i < elem_count; i++)
- {
- if (!key_nulls[i])
- {
- result->val.array.elems[j].type = jbvString;
- result->val.array.elems[j].val.string.val = VARDATA(key_datums[i]);
- result->val.array.elems[j].val.string.len = VARSIZE(key_datums[i]) - VARHDRSZ;
- j++;
- }
- }
- result->val.array.nElems = j;
-
- uniqueifyJsonbArray(result);
- return result;
-}
-
-/*
* Hash a JsonbValue scalar value, mixing the hash value into an existing
* hash provided by the caller.
*
@@ -1212,331 +1052,333 @@ lexicalCompareJsonbStringValue(const void *a, const void *b)
vb->val.string.len, DEFAULT_COLLATION_OID);
}
+
/*
- * Given a JsonbValue, convert to Jsonb and store in preallocated Jsonb buffer
- * sufficiently large to fit the value
+ * Functions for manipulating the resizeable buffer used by convertJsonb and
+ * its subroutines.
*/
-static Size
-convertJsonb(JsonbValue *val, Jsonb *buffer)
-{
- convertState state;
- Size len;
- /* Should not already have binary representation */
- Assert(val->type != jbvBinary);
+/*
+ * Rervere 'len' bytes, at the end of the buffer, enlarging it if necessary.
+ * Returns the offset to the reserved area. The caller is expected to copy
+ * the data to the reserved area later with copyToBuffer()
+ */
+static int
+reserveFromBuffer(convertState *buffer, int len)
+{
+ int offset;
- state.buffer = buffer;
- /* Start from superheader */
- state.ptr = VARDATA(state.buffer);
- state.levelSz = 8;
- state.allState = palloc(sizeof(convertLevel) * state.levelSz);
+ /* Make more room if needed */
+ if (buffer->len + len > buffer->allocatedsz)
+ {
+ buffer->allocatedsz *= 2;
+ buffer->buffer = repalloc(buffer->buffer, buffer->allocatedsz);
+ }
- walkJsonbValueConversion(val, &state, 0);
+ /* remember current offset */
+ offset = buffer->len;
- len = state.ptr - VARDATA(state.buffer);
+ /* reserve the space */
+ buffer->len += len;
- Assert(len <= val->estSize);
- return len;
+ return offset;
}
/*
- * Walk the tree representation of Jsonb, as part of the process of converting
- * a JsonbValue to a Jsonb.
- *
- * This high-level function takes care of recursion into sub-containers, but at
- * the top level calls putJsonbValueConversion once per sequential processing
- * token (in a manner similar to generic iteration).
+ * Copy 'len' bytes to a previously reserved area in buffer.
*/
static void
-walkJsonbValueConversion(JsonbValue *val, convertState *cstate,
- uint32 nestlevel)
+copyToBuffer(convertState *buffer, int offset, char *data, int len)
{
- int i;
+ memcpy(buffer->buffer + offset, data, len);
+}
- check_stack_depth();
+/*
+ * A shorthand for reserveFromBuffer + copyToBuffer.
+ */
+static void
+appendToBuffer(convertState *buffer, char *data, int len)
+{
+ int offset;
- if (!val)
- return;
+ offset = reserveFromBuffer(buffer, len);
+ copyToBuffer(buffer, offset, data, len);
+}
- switch (val->type)
- {
- case jbvArray:
- putJsonbValueConversion(cstate, val, WJB_BEGIN_ARRAY, nestlevel);
- for (i = 0; i < val->val.array.nElems; i++)
- {
- if (IsAJsonbScalar(&val->val.array.elems[i]) ||
- val->val.array.elems[i].type == jbvBinary)
- putJsonbValueConversion(cstate, val->val.array.elems + i,
- WJB_ELEM, nestlevel);
- else
- walkJsonbValueConversion(val->val.array.elems + i, cstate,
- nestlevel + 1);
- }
- putJsonbValueConversion(cstate, val, WJB_END_ARRAY, nestlevel);
+/*
+ * Append padding, so that the length of the StringInfo is int-aligned.
+ * Returns the number of padding bytes appended.
+ */
+static short
+padBufferToInt(convertState *buffer)
+{
+ short padlen,
+ p;
+ int offset;
- break;
- case jbvObject:
+ padlen = INTALIGN(buffer->len) - buffer->len;
- putJsonbValueConversion(cstate, val, WJB_BEGIN_OBJECT, nestlevel);
- for (i = 0; i < val->val.object.nPairs; i++)
- {
- putJsonbValueConversion(cstate, &val->val.object.pairs[i].key,
- WJB_KEY, nestlevel);
-
- if (IsAJsonbScalar(&val->val.object.pairs[i].value) ||
- val->val.object.pairs[i].value.type == jbvBinary)
- putJsonbValueConversion(cstate,
- &val->val.object.pairs[i].value,
- WJB_VALUE, nestlevel);
- else
- walkJsonbValueConversion(&val->val.object.pairs[i].value,
- cstate, nestlevel + 1);
- }
- putJsonbValueConversion(cstate, val, WJB_END_OBJECT, nestlevel);
+ offset = reserveFromBuffer(buffer, padlen);
+ for (p = 0; p < padlen; p++)
+ buffer->buffer[offset + p] = 0;
- break;
- default:
- elog(ERROR, "unknown type of jsonb container");
- }
+ return padlen;
}
/*
- * walkJsonbValueConversion() worker. Add padding sufficient to int-align our
- * access to conversion buffer.
+ * Given a JsonbValue, convert to Jsonb. The result is palloc'd.
*/
-static inline
-short
-addPaddingInt(convertState *cstate)
+static Jsonb *
+convertToJsonb(JsonbValue *val)
{
- short padlen,
- p;
+ convertState buffer;
+ JEntry jentry;
+ Jsonb *res;
- padlen = INTALIGN(cstate->ptr - VARDATA(cstate->buffer)) -
- (cstate->ptr - VARDATA(cstate->buffer));
+ /* Should not already have binary representation */
+ Assert(val->type != jbvBinary);
- for (p = padlen; p > 0; p--)
- {
- *cstate->ptr = '\0';
- cstate->ptr++;
- }
+ /* Allocate an output buffer. It will be enlarged as needed */
+ buffer.buffer = palloc(128);
+ buffer.len = 0;
+ buffer.allocatedsz = 128;
- return padlen;
+ /* Make room for the varlena header */
+ reserveFromBuffer(&buffer, sizeof(VARHDRSZ));
+
+ convertJsonbValue(&buffer, &jentry, val, 0);
+
+ /*
+ * Note: the JEntry of the root is not discarded. Therefore the root
+ * JsonbContainer struct must contain enough information to tell what
+ * kind of value it is.
+ */
+
+ res = (Jsonb *) buffer.buffer;
+
+ SET_VARSIZE(res, buffer.len);
+
+ return res;
}
/*
- * walkJsonbValueConversion() worker.
+ * Subroutine of convertJsonb: serialize a single JsonbValue into buffer.
*
+ * The JEntry header for this node is returned in *header. It is filled in
+ * with the length of this value, but if
+ * it is stored in an array or an object (which is always, except for the root
+ * node), it is the caller's responsibility to adjust it with the offset
+ * within the container.
+ *
+ * If the value is an array or an object, this recurses. 'level' is only used
+ * for debugging purposes.
+
* As part of the process of converting an arbitrary JsonbValue to a Jsonb,
- * copy over an arbitrary individual JsonbValue. This function may copy any
- * type of value, even containers (Objects/arrays). However, it is not
- * responsible for recursive aspects of walking the tree (so only top-level
- * Object/array details are handled).
+ * serialize and copy a scalar value into buffer.
*
- * No details about their keys/values/elements are handled recursively -
- * rather, the function is called as required for the start of an Object/Array,
- * and the end (i.e. there is one call per sequential processing WJB_* token).
+ * This is a worker function for putJsonbValueConversion() (itself a worker for
+ * walkJsonbValueConversion()). It handles the details with regard to Jentry
+ * metadata peculiar to each scalar type.
+ *
+ * It is the callers responsibility to shift the offset if this is stored
+ * in an array or object.
*/
static void
-putJsonbValueConversion(convertState *cstate, JsonbValue *val, uint32 flags,
- uint32 level)
+convertJsonbValue(convertState *buffer, JEntry *header, JsonbValue *val, int level)
{
- if (level == cstate->levelSz)
- {
- cstate->levelSz *= 2;
- cstate->allState = repalloc(cstate->allState,
- sizeof(convertLevel) * cstate->levelSz);
- }
-
- cstate->contPtr = cstate->allState + level;
+ check_stack_depth();
- if (flags & (WJB_BEGIN_ARRAY | WJB_BEGIN_OBJECT))
- {
- Assert(((flags & WJB_BEGIN_ARRAY) && val->type == jbvArray) ||
- ((flags & WJB_BEGIN_OBJECT) && val->type == jbvObject));
+ if (!val)
+ return;
- /* Initialize pointer into conversion buffer at this level */
- cstate->contPtr->begin = cstate->ptr;
+ if (IsAJsonbScalar(val) || val->type == jbvBinary)
+ convertJsonbScalar(buffer, header, val);
+ else if (val->type == jbvArray)
+ convertJsonbArray(buffer, header, val, level);
+ else if (val->type == jbvObject)
+ convertJsonbObject(buffer, header, val, level);
+ else
+ elog(ERROR, "unknown type of jsonb container");
+}
- addPaddingInt(cstate);
+static void
+convertJsonbArray(convertState *buffer, JEntry *pheader, JsonbValue *val, int level)
+{
+ int offset;
+ int metaoffset;
+ int i;
+ int totallen;
+ uint32 header;
- /* Initialize everything else at this level */
- cstate->contPtr->header = (uint32 *) cstate->ptr;
- /* Advance past header */
- cstate->ptr += sizeof(uint32);
- cstate->contPtr->meta = (JEntry *) cstate->ptr;
- cstate->contPtr->i = 0;
+ /* Initialize pointer into conversion buffer at this level */
+ offset = buffer->len;
- if (val->type == jbvArray)
- {
- *cstate->contPtr->header = val->val.array.nElems | JB_FARRAY;
- cstate->ptr += sizeof(JEntry) * val->val.array.nElems;
+ padBufferToInt(buffer);
- if (val->val.array.rawScalar)
- {
- Assert(val->val.array.nElems == 1);
- Assert(level == 0);
- *cstate->contPtr->header |= JB_FSCALAR;
- }
- }
- else
- {
- *cstate->contPtr->header = val->val.object.nPairs | JB_FOBJECT;
- cstate->ptr += sizeof(JEntry) * val->val.object.nPairs * 2;
- }
- }
- else if (flags & WJB_ELEM)
+ /*
+ * Construct the header Jentry, stored in the beginning of the variable-
+ * length payload.
+ */
+ header = val->val.array.nElems | JB_FARRAY;
+ if (val->val.array.rawScalar)
{
- putScalarConversion(cstate, val, level, cstate->contPtr->i);
- cstate->contPtr->i++;
+ Assert(val->val.array.nElems == 1);
+ Assert(level == 0);
+ header |= JB_FSCALAR;
}
- else if (flags & WJB_KEY)
- {
- Assert(val->type == jbvString);
- putScalarConversion(cstate, val, level, cstate->contPtr->i * 2);
- }
- else if (flags & WJB_VALUE)
+ appendToBuffer(buffer, (char *) &header, sizeof(uint32));
+ /* reserve space for the JEntries of the elements. */
+ metaoffset = reserveFromBuffer(buffer, sizeof(JEntry) * val->val.array.nElems);
+
+ totallen = 0;
+ for (i = 0; i < val->val.array.nElems; i++)
{
- putScalarConversion(cstate, val, level, cstate->contPtr->i * 2 + 1);
- cstate->contPtr->i++;
+ JsonbValue *elem = &val->val.array.elems[i];
+ int len;
+ JEntry meta;
+
+ convertJsonbValue(buffer, &meta, elem, level + 1);
+ len = meta & JENTRY_POSMASK;
+ totallen += len;
+
+ if (totallen > JENTRY_POSMASK)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("total size of jsonb array elements exceeds the maximum of %u bytes",
+ JENTRY_POSMASK)));
+
+ if (i == 0)
+ meta |= JENTRY_ISFIRST;
+ else
+ meta = (meta & ~JENTRY_POSMASK) | totallen;
+ copyToBuffer(buffer, metaoffset, (char *) &meta, sizeof(JEntry));
+ metaoffset += sizeof(JEntry);
}
- else if (flags & (WJB_END_ARRAY | WJB_END_OBJECT))
- {
- convertLevel *prevPtr; /* Prev container pointer */
- uint32 len,
- i;
- Assert(((flags & WJB_END_ARRAY) && val->type == jbvArray) ||
- ((flags & WJB_END_OBJECT) && val->type == jbvObject));
+ totallen = buffer->len - offset;
- if (level == 0)
- return;
+ /* Initialize the header of this node, in the container's JEntry array */
+ *pheader = JENTRY_ISCONTAINER | totallen;
+}
- len = cstate->ptr - (char *) cstate->contPtr->begin;
+static void
+convertJsonbObject(convertState *buffer, JEntry *pheader, JsonbValue *val, int level)
+{
+ uint32 header;
+ int offset;
+ int metaoffset;
+ int i;
+ int totallen;
- prevPtr = cstate->contPtr - 1;
+ /* Initialize pointer into conversion buffer at this level */
+ offset = buffer->len;
- if (*prevPtr->header & JB_FARRAY)
- {
- i = prevPtr->i;
+ padBufferToInt(buffer);
- prevPtr->meta[i].header = JENTRY_ISNEST;
+ /* Initialize header */
+ header = val->val.object.nPairs | JB_FOBJECT;
+ appendToBuffer(buffer, (char *) &header, sizeof(uint32));
- if (i == 0)
- prevPtr->meta[0].header |= JENTRY_ISFIRST | len;
- else
- prevPtr->meta[i].header |=
- (prevPtr->meta[i - 1].header & JENTRY_POSMASK) + len;
- }
- else if (*prevPtr->header & JB_FOBJECT)
- {
- i = 2 * prevPtr->i + 1; /* Value, not key */
+ /* reserve space for the JEntries of the keys and values */
+ metaoffset = reserveFromBuffer(buffer, sizeof(JEntry) * val->val.object.nPairs * 2);
- prevPtr->meta[i].header = JENTRY_ISNEST;
+ totallen = 0;
+ for (i = 0; i < val->val.object.nPairs; i++)
+ {
+ JsonbPair *pair = &val->val.object.pairs[i];
+ int len;
+ JEntry meta;
- prevPtr->meta[i].header |=
- (prevPtr->meta[i - 1].header & JENTRY_POSMASK) + len;
- }
- else
- {
- elog(ERROR, "invalid jsonb container type");
- }
+ /* put key */
+ convertJsonbScalar(buffer, &meta, &pair->key);
- Assert(cstate->ptr - cstate->contPtr->begin <= val->estSize);
- prevPtr->i++;
- }
- else
- {
- elog(ERROR, "unknown flag encountered during jsonb tree walk");
+ len = meta & JENTRY_POSMASK;
+ totallen += len;
+
+ if (totallen > JENTRY_POSMASK)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("total size of jsonb array elements exceeds the maximum of %u bytes",
+ JENTRY_POSMASK)));
+
+ if (i == 0)
+ meta |= JENTRY_ISFIRST;
+ else
+ meta = (meta & ~JENTRY_POSMASK) | totallen;
+ copyToBuffer(buffer, metaoffset, (char *) &meta, sizeof(JEntry));
+ metaoffset += sizeof(JEntry);
+
+ convertJsonbValue(buffer, &meta, &pair->value, level);
+ len = meta & JENTRY_POSMASK;
+ totallen += len;
+
+ if (totallen > JENTRY_POSMASK)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("total size of jsonb array elements exceeds the maximum of %u bytes",
+ JENTRY_POSMASK)));
+
+ meta = (meta & ~JENTRY_POSMASK) | totallen;
+ copyToBuffer(buffer, metaoffset, (char *) &meta, sizeof(JEntry));
+ metaoffset += sizeof(JEntry);
}
+
+ totallen = buffer->len - offset;
+
+ *pheader = JENTRY_ISCONTAINER | totallen;
}
-/*
- * As part of the process of converting an arbitrary JsonbValue to a Jsonb,
- * serialize and copy a scalar value into buffer.
- *
- * This is a worker function for putJsonbValueConversion() (itself a worker for
- * walkJsonbValueConversion()). It handles the details with regard to Jentry
- * metadata peculiar to each scalar type.
- */
static void
-putScalarConversion(convertState *cstate, JsonbValue *scalarVal, uint32 level,
- uint32 i)
+convertJsonbScalar(convertState *buffer, JEntry *jentry, JsonbValue *scalarVal)
{
int numlen;
short padlen;
- cstate->contPtr = cstate->allState + level;
-
- if (i == 0)
- cstate->contPtr->meta[0].header = JENTRY_ISFIRST;
- else
- cstate->contPtr->meta[i].header = 0;
-
switch (scalarVal->type)
{
case jbvNull:
- cstate->contPtr->meta[i].header |= JENTRY_ISNULL;
-
- if (i > 0)
- cstate->contPtr->meta[i].header |=
- cstate->contPtr->meta[i - 1].header & JENTRY_POSMASK;
+ *jentry = JENTRY_ISNULL;
break;
+
case jbvString:
- memcpy(cstate->ptr, scalarVal->val.string.val, scalarVal->val.string.len);
- cstate->ptr += scalarVal->val.string.len;
+ appendToBuffer(buffer, scalarVal->val.string.val, scalarVal->val.string.len);
- if (i == 0)
- cstate->contPtr->meta[0].header |= scalarVal->val.string.len;
- else
- cstate->contPtr->meta[i].header |=
- (cstate->contPtr->meta[i - 1].header & JENTRY_POSMASK) +
- scalarVal->val.string.len;
+ *jentry = scalarVal->val.string.len;
break;
+
case jbvNumeric:
numlen = VARSIZE_ANY(scalarVal->val.numeric);
- padlen = addPaddingInt(cstate);
+ padlen = padBufferToInt(buffer);
- memcpy(cstate->ptr, scalarVal->val.numeric, numlen);
- cstate->ptr += numlen;
+ appendToBuffer(buffer, (char *) scalarVal->val.numeric, numlen);
- cstate->contPtr->meta[i].header |= JENTRY_ISNUMERIC;
- if (i == 0)
- cstate->contPtr->meta[0].header |= padlen + numlen;
- else
- cstate->contPtr->meta[i].header |=
- (cstate->contPtr->meta[i - 1].header & JENTRY_POSMASK)
- + padlen + numlen;
+ *jentry = JENTRY_ISNUMERIC | (padlen + numlen);
break;
- case jbvBool:
- cstate->contPtr->meta[i].header |= (scalarVal->val.boolean) ?
- JENTRY_ISTRUE : JENTRY_ISFALSE;
- if (i > 0)
- cstate->contPtr->meta[i].header |=
- cstate->contPtr->meta[i - 1].header & JENTRY_POSMASK;
+ case jbvBool:
+ *jentry = (scalarVal->val.boolean) ?
+ JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE;
break;
+
default:
elog(ERROR, "invalid jsonb scalar type");
}
}
/*
- * Given superheader pointer into buffer, initialize iterator. Must be a
- * container type.
+ * Initialize an iterator for iterating all elements in a container.
*/
static void
-iteratorFromContainerBuf(JsonbIterator *it, JsonbSuperHeader sheader)
+iteratorFromContainer(JsonbIterator *it, JsonbContainer *container)
{
- uint32 superheader = *(uint32 *) sheader;
-
- it->containerType = superheader & (JB_FARRAY | JB_FOBJECT);
- it->nElems = superheader & JB_CMASK;
- it->buffer = sheader;
+ it->containerType = container->header & (JB_FARRAY | JB_FOBJECT);
+ it->nElems = container->header & JB_CMASK;
+ it->buffer = (char *) container;
/* Array starts just after header */
- it->meta = (JEntry *) (sheader + sizeof(uint32));
+ it->meta = container->children;
it->state = jbi_start;
switch (it->containerType)
@@ -1544,7 +1386,7 @@ iteratorFromContainerBuf(JsonbIterator *it, JsonbSuperHeader sheader)
case JB_FARRAY:
it->dataProper =
(char *) it->meta + it->nElems * sizeof(JEntry);
- it->isScalar = (superheader & JB_FSCALAR) != 0;
+ it->isScalar = (container->header & JB_FSCALAR) != 0;
/* This is either a "raw scalar", or an array */
Assert(!it->isScalar || it->nElems == 1);
break;
@@ -1584,60 +1426,21 @@ static bool
formIterIsContainer(JsonbIterator **it, JsonbValue *val, JEntry *ent,
bool skipNested)
{
- if (JBE_ISNULL(*ent))
- {
- val->type = jbvNull;
- val->estSize = sizeof(JEntry);
+ fillJsonbValue(ent, (*it)->dataProper, val);
+ if (IsAJsonbScalar(val) || skipNested)
return false;
- }
- else if (JBE_ISSTRING(*ent))
- {
- val->type = jbvString;
- val->val.string.val = (*it)->dataProper + JBE_OFF(*ent);
- val->val.string.len = JBE_LEN(*ent);
- val->estSize = sizeof(JEntry) + val->val.string.len;
-
- return false;
- }
- else if (JBE_ISNUMERIC(*ent))
- {
- val->type = jbvNumeric;
- val->val.numeric = (Numeric) ((*it)->dataProper + INTALIGN(JBE_OFF(*ent)));
-
- val->estSize = 2 * sizeof(JEntry) + VARSIZE_ANY(val->val.numeric);
-
- return false;
- }
- else if (JBE_ISBOOL(*ent))
- {
- val->type = jbvBool;
- val->val.boolean = JBE_ISBOOL_TRUE(*ent) != 0;
- val->estSize = sizeof(JEntry);
-
- return false;
- }
- else if (skipNested)
- {
- val->type = jbvBinary;
- val->val.binary.data = (*it)->dataProper + INTALIGN(JBE_OFF(*ent));
- val->val.binary.len = JBE_LEN(*ent) - (INTALIGN(JBE_OFF(*ent)) - JBE_OFF(*ent));
- val->estSize = val->val.binary.len + 2 * sizeof(JEntry);
-
- return false;
- }
else
{
/*
- * Must be container type, so setup caller's iterator to point to
+ * It's a container type, so setup caller's iterator to point to
* that, and return indication of that.
*
* Get child iterator.
*/
JsonbIterator *child = palloc(sizeof(JsonbIterator));
- iteratorFromContainerBuf(child,
- (*it)->dataProper + INTALIGN(JBE_OFF(*ent)));
+ iteratorFromContainer(child, val->val.binary.data);
child->parent = *it;
*it = child;
@@ -1697,8 +1500,6 @@ appendKey(JsonbParseState *pstate, JsonbValue *string)
object->val.object.pairs[object->val.object.nPairs].key = *string;
object->val.object.pairs[object->val.object.nPairs].order = object->val.object.nPairs;
-
- object->estSize += string->estSize;
}
/*
@@ -1713,7 +1514,6 @@ appendValue(JsonbParseState *pstate, JsonbValue *scalarVal)
Assert(object->type == jbvObject);
object->val.object.pairs[object->val.object.nPairs++].value = *scalarVal;
- object->estSize += scalarVal->estSize;
}
/*
@@ -1740,7 +1540,6 @@ appendElement(JsonbParseState *pstate, JsonbValue *scalarVal)
}
array->val.array.elems[array->val.array.nElems++] = *scalarVal;
- array->estSize += scalarVal->estSize;
}
/*
@@ -1835,11 +1634,7 @@ uniqueifyJsonbObject(JsonbValue *object)
while (ptr - object->val.object.pairs < object->val.object.nPairs)
{
/* Avoid copying over duplicate */
- if (lengthCompareJsonbStringValue(ptr, res, NULL) == 0)
- {
- object->estSize -= ptr->key.estSize + ptr->value.estSize;
- }
- else
+ if (lengthCompareJsonbStringValue(ptr, res, NULL) != 0)
{
res++;
if (ptr != res)
@@ -1851,45 +1646,3 @@ uniqueifyJsonbObject(JsonbValue *object)
object->val.object.nPairs = res + 1 - object->val.object.pairs;
}
}
-
-/*
- * Sort and unique-ify JsonbArray.
- *
- * Sorting uses internal ordering.
- */
-static void
-uniqueifyJsonbArray(JsonbValue *array)
-{
- bool hasNonUniq = false;
-
- Assert(array->type == jbvArray);
-
- /*
- * Actually sort values, determining if any were equal on the basis of
- * full binary equality (rather than just having the same string length).
- */
- if (array->val.array.nElems > 1)
- qsort_arg(array->val.array.elems, array->val.array.nElems,
- sizeof(JsonbValue), lengthCompareJsonbStringValue,
- &hasNonUniq);
-
- if (hasNonUniq)
- {
- JsonbValue *ptr = array->val.array.elems + 1,
- *res = array->val.array.elems;
-
- while (ptr - array->val.array.elems < array->val.array.nElems)
- {
- /* Avoid copying over duplicate */
- if (lengthCompareJsonbStringValue(ptr, res, NULL) != 0)
- {
- res++;
- *res = *ptr;
- }
-
- ptr++;
- }
-
- array->val.array.nElems = res + 1 - array->val.array.elems;
- }
-}