* jsonb_util.c
* 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"
/*
* Maximum number of elements in an array (or key/value pairs in an object).
* in MaxAllocSize, and the number of elements (or pairs) must fit in the bits
* reserved for that in the JsonbContainer.header field.
*
- * (the total size of an array's elements is also limited by JENTRY_POSMASK,
- * but we're not concerned about that here)
+ * (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.)
*/
#define JSONB_MAX_ELEMS (Min(MaxAllocSize / sizeof(JsonbValue), JB_CMASK))
#define JSONB_MAX_PAIRS (Min(MaxAllocSize / sizeof(JsonbPair), JB_CMASK))
-/*
- * convertState: a resizeable buffer used when constructing a Jsonb datum
- */
-typedef struct
-{
- char *buffer;
- int len;
- int allocatedsz;
-} convertState;
-
-static void fillJsonbValue(JEntry *array, int index, char *base_addr,
- JsonbValue *result);
+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 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 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(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 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 void appendValue(JsonbParseState *pstate, JsonbValue *scalarVal);
static void appendElement(JsonbParseState *pstate, JsonbValue *scalarVal);
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 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)
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
{
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;
}
JsonbValue *key)
{
JEntry *children = container->children;
- int count = (container->header & JB_CMASK);
- JsonbValue *result = palloc(sizeof(JsonbValue));
+ int count = JsonContainerSize(container);
Assert((flags & ~(JB_FARRAY | JB_FOBJECT)) == 0);
- if (flags & JB_FARRAY & container->header)
+ /* Quick out without a palloc cycle if object/array is empty */
+ if (count <= 0)
+ return NULL;
+
+ if ((flags & JB_FARRAY) && JsonContainerIsArray(container))
{
+ JsonbValue *result = palloc(sizeof(JsonbValue));
char *base_addr = (char *) (children + count);
+ uint32 offset = 0;
int i;
for (i = 0; i < count; i++)
{
- fillJsonbValue(children, i, base_addr, result);
+ fillJsonbValue(container, i, base_addr, offset, result);
if (key->type == result->type)
{
- if (compareJsonbScalarValue(key, result) == 0)
+ if (equalsJsonbScalarValue(key, result))
return result;
}
+
+ JBE_ADVANCE_OFFSET(offset, children[i]);
}
+
+ pfree(result);
}
- else if (flags & JB_FOBJECT & container->header)
+ else if ((flags & JB_FOBJECT) && JsonContainerIsObject(container))
{
- /* Since this is an object, account for *Pairs* of Jentrys */
- char *base_addr = (char *) (children + count * 2);
- uint32 stopLow = 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)
- {
- int index;
- 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;
+}
- index = 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 = base_addr + JBE_OFF(children, index);
- candidate.val.string.len = JBE_LEN(children, index);
+ Assert(JsonContainerIsObject(container));
- difference = lengthCompareJsonbStringValue(&candidate, key);
+ /* Quick out without a palloc cycle if object is empty */
+ if (count <= 0)
+ return NULL;
- if (difference == 0)
- {
- /* Found our key, return value */
- fillJsonbValue(children, index + 1, base_addr, result);
+ /*
+ * 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;
- return result;
- }
+ stopMiddle = stopLow + (stopHigh - stopLow) / 2;
+
+ candidateVal = baseAddr + getJsonbOffset(container, stopMiddle);
+ candidateLen = getJsonbLength(container, stopMiddle);
+
+ difference = lengthCompareJsonbString(candidateVal, candidateLen,
+ keyVal, keyLen);
+
+ 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;
}
}
/* Not found */
- pfree(result);
return NULL;
}
char *base_addr;
uint32 nelements;
- if ((container->header & JB_FARRAY) == 0)
+ if (!JsonContainerIsArray(container))
elog(ERROR, "not a jsonb array");
- nelements = container->header & JB_CMASK;
+ nelements = JsonContainerSize(container);
base_addr = (char *) &container->children[nelements];
if (i >= nelements)
result = palloc(sizeof(JsonbValue));
- fillJsonbValue(container->children, i, base_addr, result);
+ 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(JEntry *children, int index, char *base_addr, JsonbValue *result)
+fillJsonbValue(JsonbContainer *container, int index,
+ char *base_addr, uint32 offset,
+ JsonbValue *result)
{
- JEntry entry = children[index];
+ JEntry entry = container->children[index];
if (JBE_ISNULL(entry))
{
else if (JBE_ISSTRING(entry))
{
result->type = jbvString;
- result->val.string.val = base_addr + JBE_OFF(children, index);
- result->val.string.len = JBE_LEN(children, index);
+ result->val.string.val = base_addr + offset;
+ result->val.string.len = getJsonbLength(container, index);
Assert(result->val.string.len >= 0);
}
else if (JBE_ISNUMERIC(entry))
{
result->type = jbvNumeric;
- result->val.numeric = (Numeric) (base_addr + INTALIGN(JBE_OFF(children, index)));
+ result->val.numeric = (Numeric) (base_addr + INTALIGN(offset));
}
else if (JBE_ISBOOL_TRUE(entry))
{
{
Assert(JBE_ISCONTAINER(entry));
result->type = jbvBinary;
- result->val.binary.data = (JsonbContainer *) (base_addr + INTALIGN(JBE_OFF(children, index)));
- result->val.binary.len = JBE_LEN(children, index) - (INTALIGN(JBE_OFF(children, index)) - JBE_OFF(children, index));
+ /* 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);
}
}
*
* 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, JsonbIteratorToken seq,
- JsonbValue *scalarVal)
+ 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)->contVal.type = jbvArray;
(*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 */
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:
* 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.
*/
JsonbIteratorToken
JsonbIteratorNext(JsonbIterator **it, JsonbValue *val, bool skipNested)
* 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_ARRAY_ELEM;
return WJB_BEGIN_ARRAY;
case JBI_ARRAY_ELEM:
- if ((*it)->i >= (*it)->nElems)
+ if ((*it)->curIndex >= (*it)->nElems)
{
/*
* All elements within array already processed. Report this
return WJB_END_ARRAY;
}
- fillJsonbValue((*it)->children, (*it)->i++, (*it)->dataProper, val);
+ 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)
{
else
{
/*
- * Scalar item in array (or a container and caller didn't
- * want us to recurse into it.
+ * Scalar item in array, or a container and caller didn't want
+ * us to recurse into it.
*/
return WJB_ELEM;
}
* 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_OBJECT_KEY;
return WJB_BEGIN_OBJECT;
case JBI_OBJECT_KEY:
- if ((*it)->i >= (*it)->nElems)
+ if ((*it)->curIndex >= (*it)->nElems)
{
/*
* All pairs within object already processed. Report this to
else
{
/* Return key of a key/value pair. */
- fillJsonbValue((*it)->children, (*it)->i * 2, (*it)->dataProper, val);
+ fillJsonbValue((*it)->container, (*it)->curIndex,
+ (*it)->dataProper, (*it)->curDataOffset,
+ val);
if (val->type != jbvString)
elog(ERROR, "unexpected jsonb type as object key");
/* Set state for next call */
(*it)->state = JBI_OBJECT_KEY;
- fillJsonbValue((*it)->children, ((*it)->i++) * 2 + 1,
- (*it)->dataProper, val);
+ 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,
{
JsonbIterator *it;
- it = palloc(sizeof(JsonbIterator));
+ it = palloc0(sizeof(JsonbIterator));
it->container = container;
it->parent = parent;
- it->nElems = container->header & JB_CMASK;
+ it->nElems = JsonContainerSize(container);
/* Array starts just after header */
it->children = container->children;
case JB_FARRAY:
it->dataProper =
(char *) it->children + it->nElems * sizeof(JEntry);
- it->isScalar = (container->header & JB_FSCALAR) != 0;
+ it->isScalar = JsonContainerIsScalar(container);
/* This is either a "raw scalar", or an array */
Assert(!it->isScalar || it->nElems == 1);
break;
case JB_FOBJECT:
-
- /*
- * Offset reflects that nElems indicates JsonbPairs in an object.
- * Each key and each value contain Jentry metadata just the same.
- */
it->dataProper =
(char *) it->children + it->nElems * sizeof(JEntry) * 2;
it->state = JBI_OBJECT_START;
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 = findJsonbValueFromContainer((*val)->container,
- JB_FOBJECT,
- &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);
/*
case jbvNumeric:
/* Must hash equal numerics to equal hash codes */
tmp = DatumGetUInt32(DirectFunctionCall1(hash_numeric,
- NumericGetDatum(scalarVal->val.numeric)));
+ NumericGetDatum(scalarVal->val.numeric)));
break;
case jbvBool:
tmp = scalarVal->val.boolean ? 0x02 : 0x04;
+
break;
default:
elog(ERROR, "invalid jsonb scalar type");
*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:
+ tmp = seed + 0x01;
+ break;
+ case jbvString:
+ tmp = DatumGetUInt64(hash_any_extended((const unsigned char *) scalarVal->val.string.val,
+ scalarVal->val.string.len,
+ seed));
+ break;
+ case jbvNumeric:
+ tmp = DatumGetUInt64(DirectFunctionCall2(hash_numeric_extended,
+ NumericGetDatum(scalarVal->val.numeric),
+ UInt64GetDatum(seed)));
+ break;
+ case jbvBool:
+ 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?
- *
- * 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.
*/
-static int
-compareJsonbScalarValue(JsonbValue *aScalar, JsonbValue *bScalar)
+static bool
+equalsJsonbScalarValue(JsonbValue *aScalar, JsonbValue *bScalar)
{
if (aScalar->type == bScalar->type)
{
switch (aScalar->type)
{
case jbvNull:
- return 0;
+ return true;
case jbvString:
- return lengthCompareJsonbStringValue(aScalar, bScalar);
+ return lengthCompareJsonbStringValue(aScalar, bScalar) == 0;
case jbvNumeric:
- return DatumGetInt32(DirectFunctionCall2(numeric_cmp,
- PointerGetDatum(aScalar->val.numeric),
- PointerGetDatum(bScalar->val.numeric)));
+ return DatumGetBool(DirectFunctionCall2(numeric_eq,
+ 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
- return 0;
+ return aScalar->val.boolean == bScalar->val.boolean;
+
default:
elog(ERROR, "invalid jsonb scalar type");
}
}
elog(ERROR, "jsonb scalar type mismatch");
- return -1;
+ return false;
}
/*
- * Standard lexical qsort() comparator of jsonb strings.
+ * Compare two scalar JsonbValues, returning -1, 0, or 1.
*
- * Sorts strings lexically, using the default database collation. Used by
- * B-Tree operators, where a lexical sort order is generally expected.
+ * Strings are compared using the default collation. Used by B-tree
+ * operators, where a lexical sort order is generally expected.
*/
static int
-lexicalCompareJsonbStringValue(const void *a, const void *b)
+compareJsonbScalarValue(JsonbValue *aScalar, JsonbValue *bScalar)
{
- 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);
+ if (aScalar->type == bScalar->type)
+ {
+ switch (aScalar->type)
+ {
+ case jbvNull:
+ return 0;
+ case jbvString:
+ 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)));
+ case jbvBool:
+ 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");
+ }
+ }
+ elog(ERROR, "jsonb scalar type mismatch");
+ return -1;
}
/*
- * Functions for manipulating the resizeable buffer used by convertJsonb and
+ * Functions for manipulating the resizable buffer used by convertJsonb and
* its subroutines.
*/
/*
* 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 copy
- * the data to the reserved area later with copyToBuffer()
+ * Returns the offset to the reserved area. The caller is expected to fill
+ * the reserved area later with copyToBuffer().
*/
static int
-reserveFromBuffer(convertState *buffer, int len)
+reserveFromBuffer(StringInfo buffer, int len)
{
int offset;
/* Make more room if needed */
- if (buffer->len + len > buffer->allocatedsz)
- {
- buffer->allocatedsz *= 2;
- buffer->buffer = repalloc(buffer->buffer, buffer->allocatedsz);
- }
+ enlargeStringInfo(buffer, len);
/* remember current offset */
offset = buffer->len;
/* reserve the space */
buffer->len += len;
+ /*
+ * 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';
+
return offset;
}
* Copy 'len' bytes to a previously reserved area in buffer.
*/
static void
-copyToBuffer(convertState *buffer, int offset, char *data, int len)
+copyToBuffer(StringInfo buffer, int offset, const char *data, int len)
{
- memcpy(buffer->buffer + offset, data, len);
+ memcpy(buffer->data + offset, data, len);
}
/*
* A shorthand for reserveFromBuffer + copyToBuffer.
*/
static void
-appendToBuffer(convertState *buffer, char *data, int len)
+appendToBuffer(StringInfo buffer, const char *data, int len)
{
int offset;
* Returns the number of padding bytes appended.
*/
static short
-padBufferToInt(convertState *buffer)
+padBufferToInt(StringInfo buffer)
{
- short padlen,
- p;
- int offset;
+ int padlen,
+ p,
+ offset;
padlen = INTALIGN(buffer->len) - buffer->len;
offset = reserveFromBuffer(buffer, padlen);
+
+ /* padlen must be small, so this is probably faster than a memset */
for (p = 0; p < padlen; p++)
- buffer->buffer[offset + p] = 0;
+ buffer->data[offset + p] = '\0';
return padlen;
}
static Jsonb *
convertToJsonb(JsonbValue *val)
{
- convertState buffer;
+ StringInfoData buffer;
JEntry jentry;
Jsonb *res;
Assert(val->type != jbvBinary);
/* Allocate an output buffer. It will be enlarged as needed */
- buffer.buffer = palloc(128);
- buffer.len = 0;
- buffer.allocatedsz = 128;
+ initStringInfo(&buffer);
/* Make room for the varlena header */
- reserveFromBuffer(&buffer, sizeof(VARHDRSZ));
+ reserveFromBuffer(&buffer, VARHDRSZ);
convertJsonbValue(&buffer, &jentry, val, 0);
/*
* 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.
+ * JsonbContainer struct must contain enough information to tell what kind
+ * of value it is.
*/
- res = (Jsonb *) buffer.buffer;
+ res = (Jsonb *) buffer.data;
SET_VARSIZE(res, buffer.len);
/*
* 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.
+ * 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(convertState *buffer, JEntry *header, JsonbValue *val, int level)
+convertJsonbValue(StringInfo buffer, JEntry *header, JsonbValue *val, int level)
{
check_stack_depth();
if (!val)
return;
- if (IsAJsonbScalar(val) || val->type == jbvBinary)
+ /*
+ * 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.
+ */
+
+ 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");
+ elog(ERROR, "unknown type of jsonb container to convert");
}
static void
-convertJsonbArray(convertState *buffer, JEntry *pheader, JsonbValue *val, int level)
+convertJsonbArray(StringInfo buffer, JEntry *pheader, JsonbValue *val, int level)
{
- int offset;
- int metaoffset;
+ int base_offset;
+ int jentry_offset;
int i;
int totallen;
uint32 header;
+ int nElems = val->val.array.nElems;
- /* Initialize pointer into conversion buffer at this level */
- offset = buffer->len;
+ /* Remember where in the buffer this array starts. */
+ base_offset = buffer->len;
+ /* Align to 4-byte boundary (any padding counts as part of my data) */
padBufferToInt(buffer);
/*
- * Construct the header Jentry, stored in the beginning of the variable-
- * length payload.
+ * Construct the header Jentry and store it in the beginning of the
+ * variable-length payload.
*/
- header = val->val.array.nElems | JB_FARRAY;
+ header = nElems | JB_FARRAY;
if (val->val.array.rawScalar)
{
- Assert(val->val.array.nElems == 1);
+ Assert(nElems == 1);
Assert(level == 0);
header |= JB_FSCALAR;
}
appendToBuffer(buffer, (char *) &header, sizeof(uint32));
- /* reserve space for the JEntries of the elements. */
- metaoffset = reserveFromBuffer(buffer, sizeof(JEntry) * val->val.array.nElems);
+
+ /* Reserve space for the JEntries of the elements. */
+ jentry_offset = reserveFromBuffer(buffer, sizeof(JEntry) * nElems);
totallen = 0;
- for (i = 0; i < val->val.array.nElems; i++)
+ for (i = 0; i < nElems; i++)
{
JsonbValue *elem = &val->val.array.elems[i];
int len;
JEntry meta;
+ /*
+ * Convert element, producing a JEntry and appending its
+ * variable-length data to buffer
+ */
convertJsonbValue(buffer, &meta, elem, level + 1);
- len = meta & JENTRY_POSMASK;
+
+ len = JBE_OFFLENFLD(meta);
totallen += len;
- if (totallen > JENTRY_POSMASK)
+ /*
+ * 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_POSMASK)));
+ JENTRY_OFFLENMASK)));
+
+ /*
+ * 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)
- meta = (meta & ~JENTRY_POSMASK) | totallen;
- copyToBuffer(buffer, metaoffset, (char *) &meta, sizeof(JEntry));
- metaoffset += sizeof(JEntry);
+ copyToBuffer(buffer, jentry_offset, (char *) &meta, sizeof(JEntry));
+ jentry_offset += sizeof(JEntry);
}
- totallen = buffer->len - offset;
+ /* 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 */
+ /* Initialize the header of this node in the container's JEntry array */
*pheader = JENTRY_ISCONTAINER | totallen;
}
static void
-convertJsonbObject(convertState *buffer, JEntry *pheader, JsonbValue *val, int level)
+convertJsonbObject(StringInfo buffer, JEntry *pheader, JsonbValue *val, int level)
{
- uint32 header;
- int offset;
- int metaoffset;
+ int base_offset;
+ int jentry_offset;
int i;
int totallen;
+ uint32 header;
+ int nPairs = val->val.object.nPairs;
- /* Initialize pointer into conversion buffer at this level */
- offset = buffer->len;
+ /* Remember where in the buffer this object starts. */
+ base_offset = buffer->len;
+ /* Align to 4-byte boundary (any padding counts as part of my data) */
padBufferToInt(buffer);
- /* Initialize header */
- header = val->val.object.nPairs | 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));
- /* reserve space for the JEntries of the keys and values */
- metaoffset = reserveFromBuffer(buffer, sizeof(JEntry) * val->val.object.nPairs * 2);
+ /* Reserve space for the JEntries of the keys and values. */
+ jentry_offset = reserveFromBuffer(buffer, sizeof(JEntry) * nPairs * 2);
+ /*
+ * 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 < val->val.object.nPairs; i++)
+ for (i = 0; i < nPairs; i++)
{
- JsonbPair *pair = &val->val.object.pairs[i];
- int len;
- JEntry meta;
+ JsonbPair *pair = &val->val.object.pairs[i];
+ int len;
+ JEntry meta;
- /* put key */
+ /*
+ * Convert key, producing a JEntry and appending its variable-length
+ * data to buffer
+ */
convertJsonbScalar(buffer, &meta, &pair->key);
- len = meta & JENTRY_POSMASK;
+ len = JBE_OFFLENFLD(meta);
totallen += len;
- if (totallen > JENTRY_POSMASK)
+ /*
+ * 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_POSMASK)));
+ errmsg("total size of jsonb object elements exceeds the maximum of %u bytes",
+ JENTRY_OFFLENMASK)));
+
+ /*
+ * Convert each JB_OFFSET_STRIDE'th length to an offset.
+ */
+ if ((i % JB_OFFSET_STRIDE) == 0)
+ meta = (meta & JENTRY_TYPEMASK) | totallen | JENTRY_HAS_OFF;
+
+ copyToBuffer(buffer, jentry_offset, (char *) &meta, sizeof(JEntry));
+ jentry_offset += sizeof(JEntry);
+ }
+ for (i = 0; i < nPairs; i++)
+ {
+ JsonbPair *pair = &val->val.object.pairs[i];
+ int len;
+ JEntry meta;
- if (i > 0)
- meta = (meta & ~JENTRY_POSMASK) | totallen;
- copyToBuffer(buffer, metaoffset, (char *) &meta, sizeof(JEntry));
- metaoffset += sizeof(JEntry);
+ /*
+ * Convert value, producing a JEntry and appending its variable-length
+ * data to buffer
+ */
+ convertJsonbValue(buffer, &meta, &pair->value, level + 1);
- convertJsonbValue(buffer, &meta, &pair->value, level);
- len = meta & JENTRY_POSMASK;
+ len = JBE_OFFLENFLD(meta);
totallen += len;
- if (totallen > JENTRY_POSMASK)
+ /*
+ * 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_POSMASK)));
+ errmsg("total size of jsonb object elements exceeds the maximum of %u bytes",
+ JENTRY_OFFLENMASK)));
+
+ /*
+ * 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;
- meta = (meta & ~JENTRY_POSMASK) | totallen;
- copyToBuffer(buffer, metaoffset, (char *) &meta, sizeof(JEntry));
- metaoffset += sizeof(JEntry);
+ copyToBuffer(buffer, jentry_offset, (char *) &meta, sizeof(JEntry));
+ jentry_offset += sizeof(JEntry);
}
- totallen = buffer->len - offset;
+ /* 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 object 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;
}
static void
-convertJsonbScalar(convertState *buffer, JEntry *jentry, JsonbValue *scalarVal)
+convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal)
{
int numlen;
short padlen;
break;
case jbvNumeric:
+ /* replace numeric NaN with string "NaN" */
+ if (numeric_is_nan(scalarVal->val.numeric))
+ {
+ appendToBuffer(buffer, "NaN", 3);
+ *jentry = 3;
+ break;
+ }
+
numlen = VARSIZE_ANY(scalarVal->val.numeric);
padlen = padBufferToInt(buffer);
JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE;
break;
+ case jbvDatetime:
+ {
+ char buf[MAXDATELEN + 1];
+ size_t len;
+
+ JsonEncodeDateTime(buf,
+ scalarVal->val.datetime.value,
+ scalarVal->val.datetime.typid,
+ &scalarVal->val.datetime.tz);
+ len = strlen(buf);
+ appendToBuffer(buffer, buf, len);
+
+ *jentry = len;
+ }
+ break;
+
default:
elog(ERROR, "invalid jsonb scalar type");
}
/*
* 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
{
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);
- }
- 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;
}
/*