/*-------------------------------------------------------------------------
*
* jsonb_util.c
- * Utilities for jsonb datatype
+ * converting between Jsonb and JsonbValues, and iterating.
*
- * Copyright (c) 2014, PostgreSQL Global Development Group
+ * Copyright (c) 2014-2019, PostgreSQL Global Development Group
*
*
* IDENTIFICATION
*/
#include "postgres.h"
-#include "access/hash.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "utils/builtins.h"
+#include "utils/datetime.h"
+#include "utils/hashutils.h"
+#include "utils/jsonapi.h"
#include "utils/jsonb.h"
#include "utils/memutils.h"
+#include "utils/varlena.h"
/*
- * Twice as many values may be stored within pairs (for an Object) than within
- * elements (for an Array), modulo the current MaxAllocSize limitation. Note
- * that JSONB_MAX_PAIRS is derived from the number of possible pairs, not
- * values (as is the case for arrays and their elements), because we're
- * concerned about limitations on the representation of the number of pairs.
- * Over twice the memory is required to store n JsonbPairs as n JsonbValues.
- * It only takes exactly twice as much disk space for storage, though. The
- * JsonbPair (not an actual pair of values) representation is used here because
- * that is what is subject to the MaxAllocSize restriction when building an
- * object.
- */
-#define JSONB_MAX_ELEMS (Min(MaxAllocSize / sizeof(JsonbValue), JENTRY_POSMASK))
-#define JSONB_MAX_PAIRS (Min(MaxAllocSize / sizeof(JsonbPair), \
- JENTRY_POSMASK))
-
-/*
- * State used while converting an arbitrary JsonbValue into a Jsonb value
- * (4-byte varlena uncompressed representation of a Jsonb)
+ * Maximum number of elements in an array (or key/value pairs in an object).
+ * This is limited by two things: the size of the JEntry array must fit
+ * in MaxAllocSize, and the number of elements (or pairs) must fit in the bits
+ * reserved for that in the JsonbContainer.header field.
*
- * 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
+ * (The total size of an array's or object's elements is also limited by
+ * JENTRY_OFFLENMASK, but we're not concerned about that here.)
*/
-typedef struct convertState
-{
- /* 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;
-
-} convertState;
+#define JSONB_MAX_ELEMS (Min(MaxAllocSize / sizeof(JsonbValue), JB_CMASK))
+#define JSONB_MAX_PAIRS (Min(MaxAllocSize / sizeof(JsonbPair), JB_CMASK))
+static void fillJsonbValue(JsonbContainer *container, int index,
+ char *base_addr, uint32 offset,
+ JsonbValue *result);
+static bool equalsJsonbScalarValue(JsonbValue *a, JsonbValue *b);
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 bool formIterIsContainer(JsonbIterator **it, JsonbValue *val,
- JEntry *ent, bool skipNested);
+static Jsonb *convertToJsonb(JsonbValue *val);
+static void convertJsonbValue(StringInfo buffer, JEntry *header, JsonbValue *val, int level);
+static void convertJsonbArray(StringInfo buffer, JEntry *header, JsonbValue *val, int level);
+static void convertJsonbObject(StringInfo buffer, JEntry *header, JsonbValue *val, int level);
+static void convertJsonbScalar(StringInfo buffer, JEntry *header, JsonbValue *scalarVal);
+
+static int reserveFromBuffer(StringInfo buffer, int len);
+static void appendToBuffer(StringInfo buffer, const char *data, int len);
+static void copyToBuffer(StringInfo buffer, int offset, const char *data, int len);
+static short padBufferToInt(StringInfo buffer);
+
+static JsonbIterator *iteratorFromContainer(JsonbContainer *container, JsonbIterator *parent);
static JsonbIterator *freeAndGetParent(JsonbIterator *it);
static JsonbParseState *pushState(JsonbParseState **pstate);
static void appendKey(JsonbParseState *pstate, JsonbValue *scalarVal);
static void appendValue(JsonbParseState *pstate, JsonbValue *scalarVal);
static void appendElement(JsonbParseState *pstate, JsonbValue *scalarVal);
-static int lengthCompareJsonbStringValue(const void *a, const void *b, void *arg);
+static int lengthCompareJsonbStringValue(const void *a, const void *b);
+static int lengthCompareJsonbString(const char *val1, int len1,
+ const char *val2, int len2);
static int lengthCompareJsonbPair(const void *a, const void *b, void *arg);
static void uniqueifyJsonbObject(JsonbValue *object);
-static void uniqueifyJsonbArray(JsonbValue *array);
+static JsonbValue *pushJsonbValueScalar(JsonbParseState **pstate,
+ JsonbIteratorToken seq,
+ JsonbValue *scalarVal);
/*
* Turn an in-memory JsonbValue into a Jsonb for on-disk storage.
*
* There isn't a JsonbToJsonbValue(), because generally we find it more
* convenient to directly iterate through the Jsonb representation and only
- * really convert nested scalar values. formIterIsContainer() does this, so
- * that clients of the iteration code don't have to directly deal with the
- * binary representation (JsonbDeepContains() is a notable exception, although
- * all exceptions are internal to this module). In general, functions that
- * accept a JsonbValue argument are concerned with the manipulation of scalar
- * values, or simple containers of scalar values, where it would be
- * inconvenient to deal with a great amount of other state.
+ * really convert nested scalar values. JsonbIteratorNext() does this, so that
+ * clients of the iteration code don't have to directly deal with the binary
+ * representation (JsonbDeepContains() is a notable exception, although all
+ * exceptions are internal to this module). In general, functions that accept
+ * a JsonbValue argument are concerned with the manipulation of scalar values,
+ * or simple containers of scalar values, where it would be inconvenient to
+ * deal with a great amount of other state.
*/
Jsonb *
JsonbValueToJsonb(JsonbValue *val)
{
Jsonb *out;
- Size sz;
if (IsAJsonbScalar(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
{
return out;
}
+/*
+ * Get the offset of the variable-length portion of a Jsonb node within
+ * the variable-length-data part of its container. The node is identified
+ * by index within the container's JEntry array.
+ */
+uint32
+getJsonbOffset(const JsonbContainer *jc, int index)
+{
+ uint32 offset = 0;
+ int i;
+
+ /*
+ * Start offset of this entry is equal to the end offset of the previous
+ * entry. Walk backwards to the most recent entry stored as an end
+ * offset, returning that offset plus any lengths in between.
+ */
+ for (i = index - 1; i >= 0; i--)
+ {
+ offset += JBE_OFFLENFLD(jc->children[i]);
+ if (JBE_HAS_OFF(jc->children[i]))
+ break;
+ }
+
+ return offset;
+}
+
+/*
+ * Get the length of the variable-length portion of a Jsonb node.
+ * The node is identified by index within the container's JEntry array.
+ */
+uint32
+getJsonbLength(const JsonbContainer *jc, int index)
+{
+ uint32 off;
+ uint32 len;
+
+ /*
+ * If the length is stored directly in the JEntry, just return it.
+ * Otherwise, get the begin offset of the entry, and subtract that from
+ * the stored end+1 offset.
+ */
+ if (JBE_HAS_OFF(jc->children[index]))
+ {
+ off = getJsonbOffset(jc, index);
+ len = JBE_OFFLENFLD(jc->children[index]) - off;
+ }
+ else
+ len = JBE_OFFLENFLD(jc->children[index]);
+
+ return len;
+}
+
/*
* BT comparator worker function. Returns an integer less than, equal to, or
* greater than zero, indicating whether a is less than, equal to, or greater
* memory here.
*/
int
-compareJsonbSuperHeaderValue(JsonbSuperHeader a, JsonbSuperHeader b)
+compareJsonbContainers(JsonbContainer *a, JsonbContainer *b)
{
JsonbIterator *ita,
*itb;
{
JsonbValue va,
vb;
- int ra,
+ JsonbIteratorToken ra,
rb;
ra = JsonbIteratorNext(&ita, &va, false);
rb = JsonbIteratorNext(&itb, &vb, false);
- /*
- * To a limited extent we'll redundantly iterate over an array/object
- * while re-performing the same test without any reasonable
- * expectation of the same container types having differing lengths
- * (as when we process a WJB_BEGIN_OBJECT, and later the corresponding
- * WJB_END_OBJECT), but no matter.
- */
if (ra == rb)
{
if (ra == WJB_DONE)
break;
}
+ if (ra == WJB_END_ARRAY || ra == WJB_END_OBJECT)
+ {
+ /*
+ * There is no array or object to compare at this stage of
+ * processing. jbvArray/jbvObject values are compared
+ * initially, at the WJB_BEGIN_ARRAY and WJB_BEGIN_OBJECT
+ * tokens.
+ */
+ continue;
+ }
+
if (va.type == vb.type)
{
switch (va.type)
{
case jbvString:
- res = lexicalCompareJsonbStringValue(&va, &vb);
- break;
case jbvNull:
case jbvNumeric:
case jbvBool:
break;
case jbvBinary:
elog(ERROR, "unexpected jbvBinary value");
+ break;
+ case jbvDatetime:
+ elog(ERROR, "unexpected jbvDatetime value");
+ break;
}
}
else
else
{
/*
- * It's safe to assume that the types differed.
+ * It's safe to assume that the types differed, and that the va
+ * and vb values passed were set.
*
- * If the two values were the same container type, then there'd
+ * If the two values were of the same container type, then there'd
* have been a chance to observe the variation in the number of
- * elements/pairs (when processing WJB_BEGIN_OBJECT, say). They
- * can't be scalar types either, because then they'd have to be
- * contained in containers already ruled unequal due to differing
- * numbers of pairs/elements, or already directly ruled unequal
- * with a call to the underlying type's comparator.
+ * elements/pairs (when processing WJB_BEGIN_OBJECT, say). They're
+ * either two heterogeneously-typed containers, or a container and
+ * some scalar type.
+ *
+ * We don't have to consider the WJB_END_ARRAY and WJB_END_OBJECT
+ * cases here, because we would have seen the corresponding
+ * WJB_BEGIN_ARRAY and WJB_BEGIN_OBJECT tokens first, and
+ * concluded that they don't match.
*/
+ Assert(ra != WJB_END_ARRAY && ra != WJB_END_OBJECT);
+ Assert(rb != WJB_END_ARRAY && rb != WJB_END_OBJECT);
+
Assert(va.type != vb.type);
- Assert(va.type == jbvArray || va.type == jbvObject);
- Assert(vb.type == jbvArray || vb.type == jbvObject);
+ Assert(va.type != jbvBinary);
+ Assert(vb.type != jbvBinary);
/* Type-defined order */
res = (va.type > vb.type) ? 1 : -1;
}
*
* 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);
- JsonbValue *result = palloc(sizeof(JsonbValue));
+ JEntry *children = container->children;
+ int count = JsonContainerSize(container);
Assert((flags & ~(JB_FARRAY | JB_FOBJECT)) == 0);
- if (flags & JB_FARRAY & superheader)
+ /* Quick out without a palloc cycle if object/array is empty */
+ if (count <= 0)
+ return NULL;
+
+ if ((flags & JB_FARRAY) && JsonContainerIsArray(container))
{
- char *data = (char *) (array + (superheader & JB_CMASK));
+ JsonbValue *result = palloc(sizeof(JsonbValue));
+ char *base_addr = (char *) (children + count);
+ uint32 offset = 0;
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(container, i, base_addr, offset, 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 (equalsJsonbScalarValue(key, result))
+ return result;
}
- else
- continue;
- if (compareJsonbScalarValue(key, result) == 0)
- return result;
+ JBE_ADVANCE_OFFSET(offset, children[i]);
}
+
+ pfree(result);
}
- else if (flags & JB_FOBJECT & superheader)
+ else if ((flags & JB_FOBJECT) && JsonContainerIsObject(container))
{
- /* Since this is an object, account for *Pairs* of Jentrys */
- char *data = (char *) (array + (superheader & JB_CMASK) * 2);
- uint32 stopLow = lowbound ? *lowbound : 0,
- stopMiddle;
-
- /* Object key past by caller must be a string */
+ /* Object key passed by caller must be a string */
Assert(key->type == jbvString);
- /* Binary search on object/pair keys *only* */
- while (stopLow < count)
- {
- JEntry *entry;
- int difference;
- JsonbValue candidate;
+ return getKeyJsonValueFromContainer(container, key->val.string.val,
+ key->val.string.len, NULL);
+ }
- /*
- * Note how we compensate for the fact that we're iterating
- * through pairs (not entries) throughout.
- */
- stopMiddle = stopLow + (count - stopLow) / 2;
+ /* Not found */
+ return NULL;
+}
- entry = array + stopMiddle * 2;
+/*
+ * Find value by key in Jsonb object and fetch it into 'res', which is also
+ * returned.
+ *
+ * 'res' can be passed in as NULL, in which case it's newly palloc'ed here.
+ */
+JsonbValue *
+getKeyJsonValueFromContainer(JsonbContainer *container,
+ const char *keyVal, int keyLen, JsonbValue *res)
+{
+ JEntry *children = container->children;
+ int count = JsonContainerSize(container);
+ char *baseAddr;
+ uint32 stopLow,
+ stopHigh;
- 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;
+ Assert(JsonContainerIsObject(container));
- difference = lengthCompareJsonbStringValue(&candidate, key, NULL);
+ /* Quick out without a palloc cycle if object is empty */
+ if (count <= 0)
+ return NULL;
- if (difference == 0)
- {
- /* Found our value (from key/value pair) */
- JEntry *v = entry + 1;
+ /*
+ * Binary search the container. Since we know this is an object, account
+ * for *Pairs* of Jentrys
+ */
+ baseAddr = (char *) (children + count * 2);
+ stopLow = 0;
+ stopHigh = count;
+ while (stopLow < stopHigh)
+ {
+ uint32 stopMiddle;
+ int difference;
+ const char *candidateVal;
+ int candidateLen;
- if (lowbound)
- *lowbound = stopMiddle + 1;
+ stopMiddle = stopLow + (stopHigh - stopLow) / 2;
- 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)));
+ candidateVal = baseAddr + getJsonbOffset(container, stopMiddle);
+ candidateLen = getJsonbLength(container, stopMiddle);
- 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;
- }
+ difference = lengthCompareJsonbString(candidateVal, candidateLen,
+ keyVal, keyLen);
- return result;
- }
+ if (difference == 0)
+ {
+ /* Found our key, return corresponding value */
+ int index = stopMiddle + count;
+
+ if (!res)
+ res = palloc(sizeof(JsonbValue));
+
+ fillJsonbValue(container, index, baseAddr,
+ getJsonbOffset(container, index),
+ res);
+
+ return res;
+ }
+ else
+ {
+ if (difference < 0)
+ stopLow = stopMiddle + 1;
else
- {
- if (difference < 0)
- stopLow = stopMiddle + 1;
- else
- count = stopMiddle;
- }
+ stopHigh = stopMiddle;
}
-
- if (lowbound)
- *lowbound = stopLow;
}
/* Not found */
- pfree(result);
return NULL;
}
/*
- * 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;
- char *data;
+ char *base_addr;
+ uint32 nelements;
- result = palloc(sizeof(JsonbValue));
+ if (!JsonContainerIsArray(container))
+ elog(ERROR, "not a jsonb array");
- if (i >= (superheader & JB_CMASK))
+ nelements = JsonContainerSize(container);
+ base_addr = (char *) &container->children[nelements];
+
+ if (i >= nelements)
return NULL;
- array = (JEntry *) (sheader + sizeof(uint32));
+ result = palloc(sizeof(JsonbValue));
- if (superheader & JB_FARRAY)
- {
- e = array + i;
- data = (char *) (array + (superheader & JB_CMASK));
- }
- else
- {
- elog(ERROR, "not a jsonb array");
- }
+ fillJsonbValue(container, i, base_addr,
+ getJsonbOffset(container, i),
+ result);
+
+ return result;
+}
+
+/*
+ * A helper function to fill in a JsonbValue to represent an element of an
+ * array, or a key or value of an object.
+ *
+ * The node's JEntry is at container->children[index], and its variable-length
+ * data is at base_addr + offset. We make the caller determine the offset
+ * since in many cases the caller can amortize that work across multiple
+ * children. When it can't, it can just call getJsonbOffset().
+ *
+ * A nested array or object will be returned as jbvBinary, ie. it won't be
+ * expanded.
+ */
+static void
+fillJsonbValue(JsonbContainer *container, int index,
+ char *base_addr, uint32 offset,
+ JsonbValue *result)
+{
+ JEntry entry = container->children[index];
- if (JBE_ISNULL(*e))
+ 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 = base_addr + offset;
+ result->val.string.len = getJsonbLength(container, index);
+ 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) (base_addr + INTALIGN(offset));
+ }
+ 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);
+ /* Remove alignment padding from data pointer and length */
+ result->val.binary.data = (JsonbContainer *) (base_addr + INTALIGN(offset));
+ result->val.binary.len = getJsonbLength(container, index) -
+ (INTALIGN(offset) - offset);
}
-
- return result;
}
/*
*
* Only sequential tokens pertaining to non-container types should pass a
* JsonbValue. There is one exception -- WJB_BEGIN_ARRAY callers may pass a
- * "raw scalar" pseudo array to append that.
+ * "raw scalar" pseudo array to append it - the actual scalar should be passed
+ * next and it will be added as the only member of the array.
+ *
+ * Values of type jbvBinary, which are rolled up arrays and objects,
+ * are unpacked before being added to the result.
*/
JsonbValue *
-pushJsonbValue(JsonbParseState **pstate, int seq, JsonbValue *scalarVal)
+pushJsonbValue(JsonbParseState **pstate, JsonbIteratorToken seq,
+ JsonbValue *jbval)
+{
+ JsonbIterator *it;
+ JsonbValue *res = NULL;
+ JsonbValue v;
+ JsonbIteratorToken tok;
+
+ if (!jbval || (seq != WJB_ELEM && seq != WJB_VALUE) ||
+ jbval->type != jbvBinary)
+ {
+ /* drop through */
+ return pushJsonbValueScalar(pstate, seq, jbval);
+ }
+
+ /* unpack the binary and add each piece to the pstate */
+ it = JsonbIteratorInit(jbval->val.binary.data);
+ while ((tok = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
+ res = pushJsonbValueScalar(pstate, tok,
+ tok < WJB_BEGIN_ARRAY ? &v : NULL);
+
+ return res;
+}
+
+/*
+ * Do the actual pushing, with only scalar or pseudo-scalar-array values
+ * accepted.
+ */
+static JsonbValue *
+pushJsonbValueScalar(JsonbParseState **pstate, JsonbIteratorToken seq,
+ JsonbValue *scalarVal)
{
JsonbValue *result = NULL;
*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);
+ scalarVal->val.array.rawScalar);
if (scalarVal && scalarVal->val.array.nElems > 0)
{
/* Assume that this array is still really a scalar */
*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) *
appendKey(*pstate, scalarVal);
break;
case WJB_VALUE:
- Assert(IsAJsonbScalar(scalarVal) ||
- scalarVal->type == jbvBinary);
+ Assert(IsAJsonbScalar(scalarVal));
appendValue(*pstate, scalarVal);
break;
case WJB_ELEM:
- Assert(IsAJsonbScalar(scalarVal) ||
- scalarVal->type == jbvBinary);
+ Assert(IsAJsonbScalar(scalarVal));
appendElement(*pstate, 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);
}
/*
- * Given a Jsonb superheader, expand to JsonbIterator to iterate over items
+ * pushJsonbValue() worker: Iteration-like forming of Jsonb
+ */
+static JsonbParseState *
+pushState(JsonbParseState **pstate)
+{
+ JsonbParseState *ns = palloc(sizeof(JsonbParseState));
+
+ ns->next = *pstate;
+ return ns;
+}
+
+/*
+ * pushJsonbValue() worker: Append a pair key to state when generating a Jsonb
+ */
+static void
+appendKey(JsonbParseState *pstate, JsonbValue *string)
+{
+ JsonbValue *object = &pstate->contVal;
+
+ Assert(object->type == jbvObject);
+ Assert(string->type == jbvString);
+
+ if (object->val.object.nPairs >= JSONB_MAX_PAIRS)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("number of jsonb object pairs exceeds the maximum allowed (%zu)",
+ JSONB_MAX_PAIRS)));
+
+ if (object->val.object.nPairs >= pstate->size)
+ {
+ pstate->size *= 2;
+ object->val.object.pairs = repalloc(object->val.object.pairs,
+ sizeof(JsonbPair) * pstate->size);
+ }
+
+ object->val.object.pairs[object->val.object.nPairs].key = *string;
+ object->val.object.pairs[object->val.object.nPairs].order = object->val.object.nPairs;
+}
+
+/*
+ * pushJsonbValue() worker: Append a pair value to state when generating a
+ * Jsonb
+ */
+static void
+appendValue(JsonbParseState *pstate, JsonbValue *scalarVal)
+{
+ JsonbValue *object = &pstate->contVal;
+
+ Assert(object->type == jbvObject);
+
+ object->val.object.pairs[object->val.object.nPairs++].value = *scalarVal;
+}
+
+/*
+ * pushJsonbValue() worker: Append an element to state when generating a Jsonb
+ */
+static void
+appendElement(JsonbParseState *pstate, JsonbValue *scalarVal)
+{
+ JsonbValue *array = &pstate->contVal;
+
+ Assert(array->type == jbvArray);
+
+ if (array->val.array.nElems >= JSONB_MAX_ELEMS)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("number of jsonb array elements exceeds the maximum allowed (%zu)",
+ JSONB_MAX_ELEMS)));
+
+ if (array->val.array.nElems >= pstate->size)
+ {
+ pstate->size *= 2;
+ array->val.array.elems = repalloc(array->val.array.elems,
+ sizeof(JsonbValue) * pstate->size);
+ }
+
+ array->val.array.elems[array->val.array.nElems++] = *scalarVal;
+}
+
+/*
+ * 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);
- it->parent = NULL;
-
- return it;
+ return iteratorFromContainer(container, NULL);
}
/*
* It is our job to expand the jbvBinary representation without bothering them
* with it. However, clients should not take it upon themselves to touch array
* or Object element/pair buffers, since their element/pair pointers are
- * garbage.
+ * garbage. Also, *val will not be set when returning WJB_END_ARRAY or
+ * WJB_END_OBJECT, on the assumption that it's only useful to access values
+ * when recursing in.
*/
-int
+JsonbIteratorToken
JsonbIteratorNext(JsonbIterator **it, JsonbValue *val, bool skipNested)
{
- JsonbIterState state;
-
- /* Guard against stack overflow due to overly complex Jsonb */
- check_stack_depth();
-
- /* Recursive caller may have original caller's iterator */
if (*it == NULL)
return WJB_DONE;
- state = (*it)->state;
-
- if ((*it)->containerType == JB_FARRAY)
+ /*
+ * When stepping into a nested container, we jump back here to start
+ * processing the child. We will not recurse further in one call, because
+ * processing the child will always begin in JBI_ARRAY_START or
+ * JBI_OBJECT_START state.
+ */
+recurse:
+ switch ((*it)->state)
{
- if (state == jbi_start)
- {
+ case JBI_ARRAY_START:
/* Set v to array on first array call */
val->type = jbvArray;
val->val.array.nElems = (*it)->nElems;
* a full conversion
*/
val->val.array.rawScalar = (*it)->isScalar;
- (*it)->i = 0;
+ (*it)->curIndex = 0;
+ (*it)->curDataOffset = 0;
+ (*it)->curValueOffset = 0; /* not actually used */
/* Set state for next call */
- (*it)->state = jbi_elem;
+ (*it)->state = JBI_ARRAY_ELEM;
return WJB_BEGIN_ARRAY;
- }
- else if (state == jbi_elem)
- {
- if ((*it)->i >= (*it)->nElems)
+
+ case JBI_ARRAY_ELEM:
+ if ((*it)->curIndex >= (*it)->nElems)
{
/*
* All elements within array already processed. Report this
*it = freeAndGetParent(*it);
return WJB_END_ARRAY;
}
- else if (formIterIsContainer(it, val, &(*it)->meta[(*it)->i++],
- skipNested))
+
+ fillJsonbValue((*it)->container, (*it)->curIndex,
+ (*it)->dataProper, (*it)->curDataOffset,
+ val);
+
+ JBE_ADVANCE_OFFSET((*it)->curDataOffset,
+ (*it)->children[(*it)->curIndex]);
+ (*it)->curIndex++;
+
+ if (!IsAJsonbScalar(val) && !skipNested)
{
- /*
- * New child iterator acquired within formIterIsContainer.
- * Recurse into container. Don't directly return jbvBinary
- * value to top-level client.
- */
- return JsonbIteratorNext(it, val, skipNested);
+ /* Recurse into container. */
+ *it = iteratorFromContainer(val->val.binary.data, *it);
+ goto recurse;
}
else
{
- /* Scalar item in array */
+ /*
+ * Scalar item in array, or a container and caller didn't want
+ * us to recurse into it.
+ */
return WJB_ELEM;
}
- }
- }
- else if ((*it)->containerType == JB_FOBJECT)
- {
- if (state == jbi_start)
- {
+
+ case JBI_OBJECT_START:
/* Set v to object on first object call */
val->type = jbvObject;
val->val.object.nPairs = (*it)->nElems;
* v->val.object.pairs is not actually set, because we aren't
* doing a full conversion
*/
- (*it)->i = 0;
+ (*it)->curIndex = 0;
+ (*it)->curDataOffset = 0;
+ (*it)->curValueOffset = getJsonbOffset((*it)->container,
+ (*it)->nElems);
/* Set state for next call */
- (*it)->state = jbi_key;
+ (*it)->state = JBI_OBJECT_KEY;
return WJB_BEGIN_OBJECT;
- }
- else if (state == jbi_key)
- {
- if ((*it)->i >= (*it)->nElems)
+
+ case JBI_OBJECT_KEY:
+ if ((*it)->curIndex >= (*it)->nElems)
{
/*
* All pairs within object already processed. Report this to
}
else
{
- /*
- * Return binary item key (ensured by setting skipNested to
- * false directly). No child iterator, no further recursion.
- * When control reaches here, it's probably from a recursive
- * call.
- */
- if (formIterIsContainer(it, val, &(*it)->meta[(*it)->i * 2], false))
- elog(ERROR, "unexpected container as object key");
+ /* Return key of a key/value pair. */
+ fillJsonbValue((*it)->container, (*it)->curIndex,
+ (*it)->dataProper, (*it)->curDataOffset,
+ val);
+ if (val->type != jbvString)
+ elog(ERROR, "unexpected jsonb type as object key");
- Assert(val->type == jbvString);
/* Set state for next call */
- (*it)->state = jbi_value;
+ (*it)->state = JBI_OBJECT_VALUE;
return WJB_KEY;
}
- }
- else if (state == jbi_value)
- {
+
+ case JBI_OBJECT_VALUE:
/* Set state for next call */
- (*it)->state = jbi_key;
+ (*it)->state = JBI_OBJECT_KEY;
+
+ fillJsonbValue((*it)->container, (*it)->curIndex + (*it)->nElems,
+ (*it)->dataProper, (*it)->curValueOffset,
+ val);
+
+ JBE_ADVANCE_OFFSET((*it)->curDataOffset,
+ (*it)->children[(*it)->curIndex]);
+ JBE_ADVANCE_OFFSET((*it)->curValueOffset,
+ (*it)->children[(*it)->curIndex + (*it)->nElems]);
+ (*it)->curIndex++;
/*
* Value may be a container, in which case we recurse with new,
- * child iterator. If it is, don't bother !skipNested callers
- * with dealing with the jbvBinary representation.
+ * child iterator (unless the caller asked not to, by passing
+ * skipNested).
*/
- if (formIterIsContainer(it, val, &(*it)->meta[((*it)->i++) * 2 + 1],
- skipNested))
- return JsonbIteratorNext(it, val, skipNested);
+ if (!IsAJsonbScalar(val) && !skipNested)
+ {
+ *it = iteratorFromContainer(val->val.binary.data, *it);
+ goto recurse;
+ }
else
return WJB_VALUE;
- }
}
elog(ERROR, "invalid iterator state");
return -1;
}
+/*
+ * Initialize an iterator for iterating all elements in a container.
+ */
+static JsonbIterator *
+iteratorFromContainer(JsonbContainer *container, JsonbIterator *parent)
+{
+ JsonbIterator *it;
+
+ it = palloc0(sizeof(JsonbIterator));
+ it->container = container;
+ it->parent = parent;
+ it->nElems = JsonContainerSize(container);
+
+ /* Array starts just after header */
+ it->children = container->children;
+
+ switch (container->header & (JB_FARRAY | JB_FOBJECT))
+ {
+ case JB_FARRAY:
+ it->dataProper =
+ (char *) it->children + it->nElems * sizeof(JEntry);
+ it->isScalar = JsonContainerIsScalar(container);
+ /* This is either a "raw scalar", or an array */
+ Assert(!it->isScalar || it->nElems == 1);
+
+ it->state = JBI_ARRAY_START;
+ break;
+
+ case JB_FOBJECT:
+ it->dataProper =
+ (char *) it->children + it->nElems * sizeof(JEntry) * 2;
+ it->state = JBI_OBJECT_START;
+ break;
+
+ default:
+ elog(ERROR, "unknown type of jsonb container");
+ }
+
+ return it;
+}
+
+/*
+ * JsonbIteratorNext() worker: Return parent, while freeing memory for current
+ * iterator
+ */
+static JsonbIterator *
+freeAndGetParent(JsonbIterator *it)
+{
+ JsonbIterator *v = it->parent;
+
+ pfree(it);
+ return v;
+}
+
/*
* Worker for "contains" operator's function
*
bool
JsonbDeepContains(JsonbIterator **val, JsonbIterator **mContained)
{
- uint32 rval,
- rcont;
JsonbValue vval,
vcontained;
+ JsonbIteratorToken rval,
+ rcont;
/*
* Guard against stack overflow due to overly complex Jsonb.
}
else if (rcont == WJB_BEGIN_OBJECT)
{
- JsonbValue *lhsVal; /* lhsVal is from pair in lhs object */
-
+ Assert(vval.type == jbvObject);
Assert(vcontained.type == jbvObject);
+ /*
+ * If the lhs has fewer pairs than the rhs, it can't possibly contain
+ * the rhs. (This conclusion is safe only because we de-duplicate
+ * keys in all Jsonb objects; thus there can be no corresponding
+ * optimization in the array case.) The case probably won't arise
+ * often, but since it's such a cheap check we may as well make it.
+ */
+ if (vval.val.object.nPairs < vcontained.val.object.nPairs)
+ return false;
+
/* Work through rhs "is it contained within?" object */
for (;;)
{
+ JsonbValue *lhsVal; /* lhsVal is from pair in lhs object */
+ JsonbValue lhsValBuf;
+
rcont = JsonbIteratorNext(mContained, &vcontained, false);
/*
return true;
Assert(rcont == WJB_KEY);
+ Assert(vcontained.type == jbvString);
/* First, find value by key... */
- lhsVal = findJsonbValueFromSuperHeader((*val)->buffer,
- JB_FOBJECT,
- NULL,
- &vcontained);
-
+ lhsVal =
+ getKeyJsonValueFromContainer((*val)->container,
+ vcontained.val.string.val,
+ vcontained.val.string.len,
+ &lhsValBuf);
if (!lhsVal)
return false;
}
else if (IsAJsonbScalar(lhsVal))
{
- if (compareJsonbScalarValue(lhsVal, &vcontained) != 0)
+ if (!equalsJsonbScalarValue(lhsVal, &vcontained))
return false;
}
else
JsonbValue *lhsConts = NULL;
uint32 nLhsElems = vval.val.array.nElems;
+ Assert(vval.type == jbvArray);
Assert(vcontained.type == jbvArray);
/*
if (IsAJsonbScalar(&vcontained))
{
- if (!findJsonbValueFromSuperHeader((*val)->buffer,
- JB_FARRAY,
- NULL,
- &vcontained))
+ if (!findJsonbValueFromContainer((*val)->container,
+ JB_FARRAY,
+ &vcontained))
return false;
}
else
}
/*
- * 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 in the hash value with an existing
+ * Hash a JsonbValue scalar value, mixing the hash value into an existing
* hash provided by the caller.
*
* Some callers may wish to independently XOR in JB_FOBJECT and JB_FARRAY
void
JsonbHashScalarValue(const JsonbValue *scalarVal, uint32 *hash)
{
- int tmp;
+ uint32 tmp;
+
+ /* Compute hash value for scalarVal */
+ switch (scalarVal->type)
+ {
+ case jbvNull:
+ tmp = 0x01;
+ break;
+ case jbvString:
+ tmp = DatumGetUInt32(hash_any((const unsigned char *) scalarVal->val.string.val,
+ scalarVal->val.string.len));
+ break;
+ case jbvNumeric:
+ /* Must hash equal numerics to equal hash codes */
+ tmp = DatumGetUInt32(DirectFunctionCall1(hash_numeric,
+ NumericGetDatum(scalarVal->val.numeric)));
+ break;
+ case jbvBool:
+ tmp = scalarVal->val.boolean ? 0x02 : 0x04;
+
+ break;
+ default:
+ elog(ERROR, "invalid jsonb scalar type");
+ tmp = 0; /* keep compiler quiet */
+ break;
+ }
/*
* Combine hash values of successive keys, values and elements by rotating
* key/value/element's hash value.
*/
*hash = (*hash << 1) | (*hash >> 31);
+ *hash ^= tmp;
+}
+
+/*
+ * Hash a value to a 64-bit value, with a seed. Otherwise, similar to
+ * JsonbHashScalarValue.
+ */
+void
+JsonbHashScalarValueExtended(const JsonbValue *scalarVal, uint64 *hash,
+ uint64 seed)
+{
+ uint64 tmp;
+
switch (scalarVal->type)
{
case jbvNull:
- *hash ^= 0x01;
- return;
+ tmp = seed + 0x01;
+ break;
case jbvString:
- tmp = hash_any((unsigned char *) scalarVal->val.string.val,
- scalarVal->val.string.len);
- *hash ^= tmp;
- return;
+ tmp = DatumGetUInt64(hash_any_extended((const unsigned char *) scalarVal->val.string.val,
+ scalarVal->val.string.len,
+ seed));
+ break;
case jbvNumeric:
- /* Must be unaffected by trailing zeroes */
- tmp = DatumGetInt32(DirectFunctionCall1(hash_numeric,
- NumericGetDatum(scalarVal->val.numeric)));
- *hash ^= tmp;
- return;
+ tmp = DatumGetUInt64(DirectFunctionCall2(hash_numeric_extended,
+ NumericGetDatum(scalarVal->val.numeric),
+ UInt64GetDatum(seed)));
+ break;
case jbvBool:
- *hash ^= scalarVal->val.boolean ? 0x02 : 0x04;
- return;
+ if (seed)
+ tmp = DatumGetUInt64(DirectFunctionCall2(hashcharextended,
+ BoolGetDatum(scalarVal->val.boolean),
+ UInt64GetDatum(seed)));
+ else
+ tmp = scalarVal->val.boolean ? 0x02 : 0x04;
+
+ break;
default:
elog(ERROR, "invalid jsonb scalar type");
+ break;
}
+
+ *hash = ROTATE_HIGH_AND_LOW_32BITS(*hash);
+ *hash ^= tmp;
}
/*
* Are two scalar JsonbValues of the same type a and b equal?
+ */
+static bool
+equalsJsonbScalarValue(JsonbValue *aScalar, JsonbValue *bScalar)
+{
+ if (aScalar->type == bScalar->type)
+ {
+ switch (aScalar->type)
+ {
+ case jbvNull:
+ return true;
+ case jbvString:
+ return lengthCompareJsonbStringValue(aScalar, bScalar) == 0;
+ case jbvNumeric:
+ return DatumGetBool(DirectFunctionCall2(numeric_eq,
+ PointerGetDatum(aScalar->val.numeric),
+ PointerGetDatum(bScalar->val.numeric)));
+ case jbvBool:
+ return aScalar->val.boolean == bScalar->val.boolean;
+
+ default:
+ elog(ERROR, "invalid jsonb scalar type");
+ }
+ }
+ elog(ERROR, "jsonb scalar type mismatch");
+ return false;
+}
+
+/*
+ * Compare two scalar JsonbValues, returning -1, 0, or 1.
*
- * Does not use lexical comparisons. Therefore, it is essentially that this
- * never be used against Strings for anything other than searching for values
- * within a single jsonb.
+ * Strings are compared using the default collation. Used by B-tree
+ * operators, where a lexical sort order is generally expected.
*/
static int
compareJsonbScalarValue(JsonbValue *aScalar, JsonbValue *bScalar)
case jbvNull:
return 0;
case jbvString:
- return lengthCompareJsonbStringValue(aScalar, bScalar, NULL);
+ return varstr_cmp(aScalar->val.string.val,
+ aScalar->val.string.len,
+ bScalar->val.string.val,
+ bScalar->val.string.len,
+ DEFAULT_COLLATION_OID);
case jbvNumeric:
return DatumGetInt32(DirectFunctionCall2(numeric_cmp,
- PointerGetDatum(aScalar->val.numeric),
- PointerGetDatum(bScalar->val.numeric)));
+ PointerGetDatum(aScalar->val.numeric),
+ PointerGetDatum(bScalar->val.numeric)));
case jbvBool:
- if (aScalar->val.boolean != bScalar->val.boolean)
- return (aScalar->val.boolean > bScalar->val.boolean) ? 1 : -1;
- else
+ if (aScalar->val.boolean == bScalar->val.boolean)
return 0;
+ else if (aScalar->val.boolean > bScalar->val.boolean)
+ return 1;
+ else
+ return -1;
default:
elog(ERROR, "invalid jsonb scalar type");
}
return -1;
}
+
/*
- * Standard lexical qsort() comparator of jsonb strings.
- *
- * Sorts strings lexically, using the default database collation. Used by
- * B-Tree operators, where a lexical sort order is generally expected.
+ * Functions for manipulating the resizable buffer used by convertJsonb and
+ * its subroutines.
*/
-static int
-lexicalCompareJsonbStringValue(const void *a, const void *b)
-{
- const JsonbValue *va = (const JsonbValue *) a;
- const JsonbValue *vb = (const JsonbValue *) b;
-
- Assert(va->type == jbvString);
- Assert(vb->type == jbvString);
-
- return varstr_cmp(va->val.string.val, va->val.string.len, vb->val.string.val,
- 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
+ * Reserve 'len' bytes, at the end of the buffer, enlarging it if necessary.
+ * Returns the offset to the reserved area. The caller is expected to fill
+ * the reserved area later with copyToBuffer().
*/
-static Size
-convertJsonb(JsonbValue *val, Jsonb *buffer)
+static int
+reserveFromBuffer(StringInfo buffer, int len)
{
- convertState state;
- Size len;
+ int offset;
- /* Should not already have binary representation */
- Assert(val->type != jbvBinary);
+ /* Make more room if needed */
+ enlargeStringInfo(buffer, len);
- state.buffer = buffer;
- /* Start from superheader */
- state.ptr = VARDATA(state.buffer);
- state.levelSz = 8;
- state.allState = palloc(sizeof(convertLevel) * state.levelSz);
+ /* remember current offset */
+ offset = buffer->len;
- walkJsonbValueConversion(val, &state, 0);
+ /* reserve the space */
+ buffer->len += len;
- len = state.ptr - VARDATA(state.buffer);
+ /*
+ * Keep a trailing null in place, even though it's not useful for us; it
+ * seems best to preserve the invariants of StringInfos.
+ */
+ buffer->data[buffer->len] = '\0';
- 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(StringInfo buffer, int offset, const char *data, int len)
{
- int i;
-
- check_stack_depth();
-
- if (!val)
- return;
-
- 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);
-
- break;
- case jbvObject:
+ memcpy(buffer->data + offset, data, 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);
+/*
+ * A shorthand for reserveFromBuffer + copyToBuffer.
+ */
+static void
+appendToBuffer(StringInfo buffer, const char *data, int len)
+{
+ int offset;
- break;
- default:
- elog(ERROR, "unknown type of jsonb container");
- }
+ offset = reserveFromBuffer(buffer, len);
+ copyToBuffer(buffer, offset, data, len);
}
+
/*
- * walkJsonbValueConversion() worker. Add padding sufficient to int-align our
- * access to conversion buffer.
+ * Append padding, so that the length of the StringInfo is int-aligned.
+ * Returns the number of padding bytes appended.
*/
-static inline
-short
-addPaddingInt(convertState *cstate)
+static short
+padBufferToInt(StringInfo buffer)
{
- short padlen,
- p;
+ int padlen,
+ p,
+ offset;
- padlen = INTALIGN(cstate->ptr - VARDATA(cstate->buffer)) -
- (cstate->ptr - VARDATA(cstate->buffer));
+ padlen = INTALIGN(buffer->len) - buffer->len;
- for (p = padlen; p > 0; p--)
- {
- *cstate->ptr = '\0';
- cstate->ptr++;
- }
+ offset = reserveFromBuffer(buffer, padlen);
+
+ /* padlen must be small, so this is probably faster than a memset */
+ for (p = 0; p < padlen; p++)
+ buffer->data[offset + p] = '\0';
return padlen;
}
/*
- * walkJsonbValueConversion() worker.
- *
- * 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).
- *
- * 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).
+ * Given a JsonbValue, convert to Jsonb. The result is palloc'd.
*/
-static void
-putJsonbValueConversion(convertState *cstate, JsonbValue *val, uint32 flags,
- uint32 level)
+static Jsonb *
+convertToJsonb(JsonbValue *val)
{
- if (level == cstate->levelSz)
- {
- cstate->levelSz *= 2;
- cstate->allState = repalloc(cstate->allState,
- sizeof(convertLevel) * cstate->levelSz);
- }
+ StringInfoData buffer;
+ JEntry jentry;
+ Jsonb *res;
- cstate->contPtr = cstate->allState + level;
-
- if (flags & (WJB_BEGIN_ARRAY | WJB_BEGIN_OBJECT))
- {
- Assert(((flags & WJB_BEGIN_ARRAY) && val->type == jbvArray) ||
- ((flags & WJB_BEGIN_OBJECT) && val->type == jbvObject));
-
- /* Initialize pointer into conversion buffer at this level */
- cstate->contPtr->begin = cstate->ptr;
+ /* Should not already have binary representation */
+ Assert(val->type != jbvBinary);
- addPaddingInt(cstate);
+ /* Allocate an output buffer. It will be enlarged as needed */
+ initStringInfo(&buffer);
- /* 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;
+ /* Make room for the varlena header */
+ reserveFromBuffer(&buffer, VARHDRSZ);
- if (val->type == jbvArray)
- {
- *cstate->contPtr->header = val->val.array.nElems | JB_FARRAY;
- cstate->ptr += sizeof(JEntry) * val->val.array.nElems;
+ convertJsonbValue(&buffer, &jentry, val, 0);
- 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)
- {
- putScalarConversion(cstate, val, level, cstate->contPtr->i);
- cstate->contPtr->i++;
- }
- else if (flags & WJB_KEY)
- {
- Assert(val->type == jbvString);
+ /*
+ * Note: the JEntry of the root is discarded. Therefore the root
+ * JsonbContainer struct must contain enough information to tell what kind
+ * of value it is.
+ */
- putScalarConversion(cstate, val, level, cstate->contPtr->i * 2);
- }
- else if (flags & WJB_VALUE)
- {
- putScalarConversion(cstate, val, level, cstate->contPtr->i * 2 + 1);
- cstate->contPtr->i++;
- }
- else if (flags & (WJB_END_ARRAY | WJB_END_OBJECT))
- {
- convertLevel *prevPtr; /* Prev container pointer */
- uint32 len,
- i;
+ res = (Jsonb *) buffer.data;
- Assert(((flags & WJB_END_ARRAY) && val->type == jbvArray) ||
- ((flags & WJB_END_OBJECT) && val->type == jbvObject));
+ SET_VARSIZE(res, buffer.len);
- if (level == 0)
- return;
+ return res;
+}
- len = cstate->ptr - (char *) cstate->contPtr->begin;
+/*
+ * 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 and appropriate type bits. If we wish to
+ * store an end offset rather than a length, it is the caller's responsibility
+ * to adjust for that.
+ *
+ * If the value is an array or an object, this recurses. 'level' is only used
+ * for debugging purposes.
+ */
+static void
+convertJsonbValue(StringInfo buffer, JEntry *header, JsonbValue *val, int level)
+{
+ check_stack_depth();
- prevPtr = cstate->contPtr - 1;
+ if (!val)
+ return;
- if (*prevPtr->header & JB_FARRAY)
- {
- i = prevPtr->i;
+ /*
+ * A JsonbValue passed as val should never have a type of jbvBinary, and
+ * neither should any of its sub-components. Those values will be produced
+ * by convertJsonbArray and convertJsonbObject, the results of which will
+ * not be passed back to this function as an argument.
+ */
- prevPtr->meta[i].header = JENTRY_ISNEST;
+ if (IsAJsonbScalar(val))
+ 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 to convert");
+}
- 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 */
+static void
+convertJsonbArray(StringInfo buffer, JEntry *pheader, JsonbValue *val, int level)
+{
+ int base_offset;
+ int jentry_offset;
+ int i;
+ int totallen;
+ uint32 header;
+ int nElems = val->val.array.nElems;
- prevPtr->meta[i].header = JENTRY_ISNEST;
+ /* Remember where in the buffer this array starts. */
+ base_offset = buffer->len;
- prevPtr->meta[i].header |=
- (prevPtr->meta[i - 1].header & JENTRY_POSMASK) + len;
- }
- else
- {
- elog(ERROR, "invalid jsonb container type");
- }
+ /* Align to 4-byte boundary (any padding counts as part of my data) */
+ padBufferToInt(buffer);
- Assert(cstate->ptr - cstate->contPtr->begin <= val->estSize);
- prevPtr->i++;
- }
- else
+ /*
+ * Construct the header Jentry and store it in the beginning of the
+ * variable-length payload.
+ */
+ header = nElems | JB_FARRAY;
+ if (val->val.array.rawScalar)
{
- elog(ERROR, "unknown flag encountered during jsonb tree walk");
+ Assert(nElems == 1);
+ Assert(level == 0);
+ header |= JB_FSCALAR;
}
-}
-/*
- * 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)
-{
- int numlen;
- short padlen;
+ appendToBuffer(buffer, (char *) &header, sizeof(uint32));
- cstate->contPtr = cstate->allState + level;
+ /* Reserve space for the JEntries of the elements. */
+ jentry_offset = reserveFromBuffer(buffer, sizeof(JEntry) * nElems);
- if (i == 0)
- cstate->contPtr->meta[0].header = JENTRY_ISFIRST;
- else
- cstate->contPtr->meta[i].header = 0;
-
- switch (scalarVal->type)
+ totallen = 0;
+ for (i = 0; i < nElems; i++)
{
- case jbvNull:
- cstate->contPtr->meta[i].header |= JENTRY_ISNULL;
+ JsonbValue *elem = &val->val.array.elems[i];
+ int len;
+ JEntry meta;
- if (i > 0)
- cstate->contPtr->meta[i].header |=
- cstate->contPtr->meta[i - 1].header & JENTRY_POSMASK;
- break;
- case jbvString:
- memcpy(cstate->ptr, scalarVal->val.string.val, scalarVal->val.string.len);
- cstate->ptr += scalarVal->val.string.len;
+ /*
+ * Convert element, producing a JEntry and appending its
+ * variable-length data to buffer
+ */
+ convertJsonbValue(buffer, &meta, elem, level + 1);
- 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;
- break;
- case jbvNumeric:
- numlen = VARSIZE_ANY(scalarVal->val.numeric);
- padlen = addPaddingInt(cstate);
+ len = JBE_OFFLENFLD(meta);
+ totallen += len;
- memcpy(cstate->ptr, scalarVal->val.numeric, numlen);
- cstate->ptr += numlen;
+ /*
+ * Bail out if total variable-length data exceeds what will fit in a
+ * JEntry length field. We check this in each iteration, not just
+ * once at the end, to forestall possible integer overflow.
+ */
+ if (totallen > JENTRY_OFFLENMASK)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("total size of jsonb array elements exceeds the maximum of %u bytes",
+ JENTRY_OFFLENMASK)));
- 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;
- break;
- case jbvBool:
- cstate->contPtr->meta[i].header |= (scalarVal->val.boolean) ?
- JENTRY_ISTRUE : JENTRY_ISFALSE;
+ /*
+ * Convert each JB_OFFSET_STRIDE'th length to an offset.
+ */
+ if ((i % JB_OFFSET_STRIDE) == 0)
+ meta = (meta & JENTRY_TYPEMASK) | totallen | JENTRY_HAS_OFF;
- if (i > 0)
- cstate->contPtr->meta[i].header |=
- cstate->contPtr->meta[i - 1].header & JENTRY_POSMASK;
- break;
- default:
- elog(ERROR, "invalid jsonb scalar type");
+ copyToBuffer(buffer, jentry_offset, (char *) &meta, sizeof(JEntry));
+ jentry_offset += sizeof(JEntry);
}
+
+ /* Total data size is everything we've appended to buffer */
+ totallen = buffer->len - base_offset;
+
+ /* Check length again, since we didn't include the metadata above */
+ if (totallen > JENTRY_OFFLENMASK)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("total size of jsonb array elements exceeds the maximum of %u bytes",
+ JENTRY_OFFLENMASK)));
+
+ /* Initialize the header of this node in the container's JEntry array */
+ *pheader = JENTRY_ISCONTAINER | totallen;
}
-/*
- * Given superheader pointer into buffer, initialize iterator. Must be a
- * container type.
- */
static void
-iteratorFromContainerBuf(JsonbIterator *it, JsonbSuperHeader sheader)
+convertJsonbObject(StringInfo buffer, JEntry *pheader, JsonbValue *val, int level)
{
- uint32 superheader = *(uint32 *) sheader;
+ int base_offset;
+ int jentry_offset;
+ int i;
+ int totallen;
+ uint32 header;
+ int nPairs = val->val.object.nPairs;
- it->containerType = superheader & (JB_FARRAY | JB_FOBJECT);
- it->nElems = superheader & JB_CMASK;
- it->buffer = sheader;
+ /* Remember where in the buffer this object starts. */
+ base_offset = buffer->len;
- /* Array starts just after header */
- it->meta = (JEntry *) (sheader + sizeof(uint32));
- it->state = jbi_start;
+ /* Align to 4-byte boundary (any padding counts as part of my data) */
+ padBufferToInt(buffer);
- switch (it->containerType)
- {
- case JB_FARRAY:
- it->dataProper =
- (char *) it->meta + it->nElems * sizeof(JEntry);
- it->isScalar = (superheader & JB_FSCALAR) != 0;
- /* This is either a "raw scalar", or an array */
- Assert(!it->isScalar || it->nElems == 1);
- break;
- case JB_FOBJECT:
+ /*
+ * Construct the header Jentry and store it in the beginning of the
+ * variable-length payload.
+ */
+ header = nPairs | JB_FOBJECT;
+ appendToBuffer(buffer, (char *) &header, sizeof(uint32));
- /*
- * Offset reflects that nElems indicates JsonbPairs in an object.
- * Each key and each value contain Jentry metadata just the same.
- */
- it->dataProper =
- (char *) it->meta + it->nElems * sizeof(JEntry) * 2;
- break;
- default:
- elog(ERROR, "unknown type of jsonb container");
- }
-}
+ /* Reserve space for the JEntries of the keys and values. */
+ jentry_offset = reserveFromBuffer(buffer, sizeof(JEntry) * nPairs * 2);
-/*
- * JsonbIteratorNext() worker
- *
- * Returns bool indicating if v was a non-jbvBinary container, and thus if
- * further recursion is required by caller (according to its skipNested
- * preference). If it is required, we set the caller's iterator for further
- * recursion into the nested value. If we're going to skip nested items, just
- * set v to a jbvBinary value, but don't set caller's iterator.
- *
- * Unlike with containers (either in this function or in any
- * JsonbIteratorNext() infrastructure), we fully convert from what is
- * ultimately a Jsonb on-disk representation, to a JsonbValue in-memory
- * representation (for scalar values only). JsonbIteratorNext() initializes
- * container Jsonbvalues, but without a sane private buffer. For scalar values
- * it has to be done for real (even if we don't actually allocate more memory
- * to do this. The point is that our JsonbValues scalars can be passed around
- * anywhere).
- */
-static bool
-formIterIsContainer(JsonbIterator **it, JsonbValue *val, JEntry *ent,
- bool skipNested)
-{
- if (JBE_ISNULL(*ent))
+ /*
+ * Iterate over the keys, then over the values, since that is the ordering
+ * we want in the on-disk representation.
+ */
+ totallen = 0;
+ for (i = 0; i < nPairs; i++)
{
- val->type = jbvNull;
- val->estSize = sizeof(JEntry);
+ JsonbPair *pair = &val->val.object.pairs[i];
+ int len;
+ JEntry meta;
- 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;
+ /*
+ * Convert key, producing a JEntry and appending its variable-length
+ * data to buffer
+ */
+ convertJsonbScalar(buffer, &meta, &pair->key);
- return false;
- }
- else if (JBE_ISNUMERIC(*ent))
- {
- val->type = jbvNumeric;
- val->val.numeric = (Numeric) ((*it)->dataProper + INTALIGN(JBE_OFF(*ent)));
+ len = JBE_OFFLENFLD(meta);
+ totallen += len;
- val->estSize = 2 * sizeof(JEntry) + VARSIZE_ANY(val->val.numeric);
+ /*
+ * Bail out if total variable-length data exceeds what will fit in a
+ * JEntry length field. We check this in each iteration, not just
+ * once at the end, to forestall possible integer overflow.
+ */
+ if (totallen > JENTRY_OFFLENMASK)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("total size of jsonb object elements exceeds the maximum of %u bytes",
+ JENTRY_OFFLENMASK)));
- return false;
- }
- else if (JBE_ISBOOL(*ent))
- {
- val->type = jbvBool;
- val->val.boolean = JBE_ISBOOL_TRUE(*ent) != 0;
- val->estSize = sizeof(JEntry);
+ /*
+ * Convert each JB_OFFSET_STRIDE'th length to an offset.
+ */
+ if ((i % JB_OFFSET_STRIDE) == 0)
+ meta = (meta & JENTRY_TYPEMASK) | totallen | JENTRY_HAS_OFF;
- return false;
+ copyToBuffer(buffer, jentry_offset, (char *) &meta, sizeof(JEntry));
+ jentry_offset += sizeof(JEntry);
}
- else if (skipNested)
+ for (i = 0; i < nPairs; i++)
{
- 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);
+ JsonbPair *pair = &val->val.object.pairs[i];
+ int len;
+ JEntry meta;
- return false;
- }
- else
- {
/*
- * Must be container type, so setup caller's iterator to point to
- * that, and return indication of that.
- *
- * Get child iterator.
+ * Convert value, producing a JEntry and appending its variable-length
+ * data to buffer
*/
- JsonbIterator *child = palloc(sizeof(JsonbIterator));
+ convertJsonbValue(buffer, &meta, &pair->value, level + 1);
- iteratorFromContainerBuf(child,
- (*it)->dataProper + INTALIGN(JBE_OFF(*ent)));
+ len = JBE_OFFLENFLD(meta);
+ totallen += len;
- child->parent = *it;
- *it = child;
+ /*
+ * Bail out if total variable-length data exceeds what will fit in a
+ * JEntry length field. We check this in each iteration, not just
+ * once at the end, to forestall possible integer overflow.
+ */
+ if (totallen > JENTRY_OFFLENMASK)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("total size of jsonb object elements exceeds the maximum of %u bytes",
+ JENTRY_OFFLENMASK)));
- return true;
- }
-}
+ /*
+ * Convert each JB_OFFSET_STRIDE'th length to an offset.
+ */
+ if (((i + nPairs) % JB_OFFSET_STRIDE) == 0)
+ meta = (meta & JENTRY_TYPEMASK) | totallen | JENTRY_HAS_OFF;
-/*
- * JsonbIteratorNext() worker: Return parent, while freeing memory for current
- * iterator
- */
-static JsonbIterator *
-freeAndGetParent(JsonbIterator *it)
-{
- JsonbIterator *v = it->parent;
+ copyToBuffer(buffer, jentry_offset, (char *) &meta, sizeof(JEntry));
+ jentry_offset += sizeof(JEntry);
+ }
- pfree(it);
- return v;
-}
+ /* Total data size is everything we've appended to buffer */
+ totallen = buffer->len - base_offset;
-/*
- * pushJsonbValue() worker: Iteration-like forming of Jsonb
- */
-static JsonbParseState *
-pushState(JsonbParseState **pstate)
-{
- JsonbParseState *ns = palloc(sizeof(JsonbParseState));
+ /* Check length again, since we didn't include the metadata above */
+ if (totallen > JENTRY_OFFLENMASK)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("total size of jsonb object elements exceeds the maximum of %u bytes",
+ JENTRY_OFFLENMASK)));
- ns->next = *pstate;
- return ns;
+ /* Initialize the header of this node in the container's JEntry array */
+ *pheader = JENTRY_ISCONTAINER | totallen;
}
-/*
- * pushJsonbValue() worker: Append a pair key to state when generating a Jsonb
- */
static void
-appendKey(JsonbParseState *pstate, JsonbValue *string)
+convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal)
{
- JsonbValue *object = &pstate->contVal;
+ int numlen;
+ short padlen;
- Assert(object->type == jbvObject);
- Assert(string->type == jbvString);
+ switch (scalarVal->type)
+ {
+ case jbvNull:
+ *jentry = JENTRY_ISNULL;
+ break;
- if (object->val.object.nPairs >= JSONB_MAX_PAIRS)
- ereport(ERROR,
- (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
- errmsg("number of jsonb object pairs exceeds the maximum allowed (%zu)",
- JSONB_MAX_PAIRS)));
+ case jbvString:
+ appendToBuffer(buffer, scalarVal->val.string.val, scalarVal->val.string.len);
- if (object->val.object.nPairs >= pstate->size)
- {
- pstate->size *= 2;
- object->val.object.pairs = repalloc(object->val.object.pairs,
- sizeof(JsonbPair) * pstate->size);
- }
+ *jentry = scalarVal->val.string.len;
+ break;
- object->val.object.pairs[object->val.object.nPairs].key = *string;
- object->val.object.pairs[object->val.object.nPairs].order = object->val.object.nPairs;
+ case jbvNumeric:
+ /* replace numeric NaN with string "NaN" */
+ if (numeric_is_nan(scalarVal->val.numeric))
+ {
+ appendToBuffer(buffer, "NaN", 3);
+ *jentry = 3;
+ break;
+ }
- object->estSize += string->estSize;
-}
+ numlen = VARSIZE_ANY(scalarVal->val.numeric);
+ padlen = padBufferToInt(buffer);
-/*
- * pushJsonbValue() worker: Append a pair value to state when generating a
- * Jsonb
- */
-static void
-appendValue(JsonbParseState *pstate, JsonbValue *scalarVal)
-{
- JsonbValue *object = &pstate->contVal;
+ appendToBuffer(buffer, (char *) scalarVal->val.numeric, numlen);
- Assert(object->type == jbvObject);
+ *jentry = JENTRY_ISNUMERIC | (padlen + numlen);
+ break;
- object->val.object.pairs[object->val.object.nPairs++].value = *scalarVal;
- object->estSize += scalarVal->estSize;
-}
+ case jbvBool:
+ *jentry = (scalarVal->val.boolean) ?
+ JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE;
+ break;
-/*
- * pushJsonbValue() worker: Append an element to state when generating a Jsonb
- */
-static void
-appendElement(JsonbParseState *pstate, JsonbValue *scalarVal)
-{
- JsonbValue *array = &pstate->contVal;
+ case jbvDatetime:
+ {
+ char buf[MAXDATELEN + 1];
+ size_t len;
- Assert(array->type == jbvArray);
+ JsonEncodeDateTime(buf,
+ scalarVal->val.datetime.value,
+ scalarVal->val.datetime.typid,
+ &scalarVal->val.datetime.tz);
+ len = strlen(buf);
+ appendToBuffer(buffer, buf, len);
- if (array->val.array.nElems >= JSONB_MAX_ELEMS)
- ereport(ERROR,
- (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
- errmsg("number of jsonb array elements exceeds the maximum allowed (%zu)",
- JSONB_MAX_ELEMS)));
+ *jentry = len;
+ }
+ break;
- if (array->val.array.nElems >= pstate->size)
- {
- pstate->size *= 2;
- array->val.array.elems = repalloc(array->val.array.elems,
- sizeof(JsonbValue) * pstate->size);
+ default:
+ elog(ERROR, "invalid jsonb scalar type");
}
-
- array->val.array.elems[array->val.array.nElems++] = *scalarVal;
- array->estSize += scalarVal->estSize;
}
/*
* Compare two jbvString JsonbValue values, a and b.
*
- * This is a special qsort_arg() comparator used to sort strings in certain
+ * This is a special qsort() comparator used to sort strings in certain
* internal contexts where it is sufficient to have a well-defined sort order.
* In particular, object pair keys are sorted according to this criteria to
* facilitate cheap binary searches where we don't care about lexical sort
*
* a and b are first sorted based on their length. If a tie-breaker is
* required, only then do we consider string binary equality.
- *
- * Third argument 'binequal' may point to a bool. If it's set, *binequal is set
- * to true iff a and b have full binary equality, since some callers have an
- * interest in whether the two values are equal or merely equivalent.
*/
static int
-lengthCompareJsonbStringValue(const void *a, const void *b, void *binequal)
+lengthCompareJsonbStringValue(const void *a, const void *b)
{
const JsonbValue *va = (const JsonbValue *) a;
const JsonbValue *vb = (const JsonbValue *) b;
- int res;
Assert(va->type == jbvString);
Assert(vb->type == jbvString);
- if (va->val.string.len == vb->val.string.len)
- {
- res = memcmp(va->val.string.val, vb->val.string.val, va->val.string.len);
- if (res == 0 && binequal)
- *((bool *) binequal) = true;
- }
- else
- {
- res = (va->val.string.len > vb->val.string.len) ? 1 : -1;
- }
+ return lengthCompareJsonbString(va->val.string.val, va->val.string.len,
+ vb->val.string.val, vb->val.string.len);
+}
- return res;
+/*
+ * Subroutine for lengthCompareJsonbStringValue
+ *
+ * This is also useful separately to implement binary search on
+ * JsonbContainers.
+ */
+static int
+lengthCompareJsonbString(const char *val1, int len1, const char *val2, int len2)
+{
+ if (len1 == len2)
+ return memcmp(val1, val2, len1);
+ else
+ return len1 > len2 ? 1 : -1;
}
/*
* qsort_arg() comparator to compare JsonbPair values.
*
- * Function implemented in terms of lengthCompareJsonbStringValue(), and thus the
- * same "arg setting" hack will be applied here in respect of the pair's key
- * values.
+ * Third argument 'binequal' may point to a bool. If it's set, *binequal is set
+ * to true iff a and b have full binary equality, since some callers have an
+ * interest in whether the two values are equal or merely equivalent.
*
* N.B: String comparisons here are "length-wise"
*
const JsonbPair *pb = (const JsonbPair *) b;
int res;
- res = lengthCompareJsonbStringValue(&pa->key, &pb->key, binequal);
+ res = lengthCompareJsonbStringValue(&pa->key, &pb->key);
+ if (res == 0 && binequal)
+ *((bool *) binequal) = true;
/*
* Guarantee keeping order of equal pair. Unique algorithm will prefer
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) != 0)
{
res++;
if (ptr != res)
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;
- }
-}