aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/adt/jsonb_util.c
diff options
context:
space:
mode:
authorHeikki Linnakangas <heikki.linnakangas@iki.fi>2014-05-07 23:16:19 +0300
committerHeikki Linnakangas <heikki.linnakangas@iki.fi>2014-05-07 23:16:19 +0300
commit364ddc3e5cbd01c93a39896b5260509129a9883e (patch)
tree88b49bb7ade35e082ce176d76a5d404cf1c9a85e /src/backend/utils/adt/jsonb_util.c
parent4d155d8b08fe08c1a1649fdbad61c6dcf4a8671f (diff)
downloadpostgresql-364ddc3e5cbd01c93a39896b5260509129a9883e.tar.gz
postgresql-364ddc3e5cbd01c93a39896b5260509129a9883e.zip
Clean up jsonb code.
The main target of this cleanup is the convertJsonb() function, but I also touched a lot of other things that I spotted into in the process. The new convertToJsonb() function uses an output buffer that's resized on demand, so the code to estimate of the size of JsonbValue is removed. The on-disk format was not changed, even though I refactored the structs used to handle it. The term "superheader" is replaced with "container". The jsonb_exists_any and jsonb_exists_all functions no longer sort the input array. That was a premature optimization, the idea being that if there are duplicates in the input array, you only need to check them once. Also, sorting the array saves some effort in the binary search used to find a key within an object. But there were drawbacks too: the sorting and deduplicating obviously isn't free, and in the typical case there are no duplicates to remove, and the gain in the binary search was minimal. Remove all that, which makes the code simpler too. This includes a bug-fix; the total length of the elements in a jsonb array or object mustn't exceed 2^28. That is now checked.
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;
- }
-}