1873 lines
52 KiB
C
1873 lines
52 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* jsonb_util.c
|
|
* Utilities for jsonb datatype
|
|
*
|
|
* Copyright (c) 2014, PostgreSQL Global Development Group
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/utils/adt/jsonb_util.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#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/jsonb.h"
|
|
#include "utils/memutils.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)
|
|
*
|
|
* 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
|
|
*/
|
|
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;
|
|
|
|
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 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 lengthCompareJsonbPair(const void *a, const void *b, void *arg);
|
|
static void uniqueifyJsonbObject(JsonbValue * object);
|
|
static void uniqueifyJsonbArray(JsonbValue * array);
|
|
|
|
/*
|
|
* Turn an in-memory JsonbValue into a Jsonb for on-disk storage.
|
|
*
|
|
* 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.
|
|
*/
|
|
Jsonb *
|
|
JsonbValueToJsonb(JsonbValue * val)
|
|
{
|
|
Jsonb *out;
|
|
Size sz;
|
|
|
|
if (IsAJsonbScalar(val))
|
|
{
|
|
/* Scalar value */
|
|
JsonbParseState *pstate = NULL;
|
|
JsonbValue *res;
|
|
JsonbValue scalarArray;
|
|
|
|
scalarArray.type = jbvArray;
|
|
scalarArray.array.rawScalar = true;
|
|
scalarArray.array.nElems = 1;
|
|
|
|
pushJsonbValue(&pstate, WJB_BEGIN_ARRAY, &scalarArray);
|
|
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);
|
|
}
|
|
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);
|
|
}
|
|
else
|
|
{
|
|
Assert(val->type == jbvBinary);
|
|
out = palloc(VARHDRSZ + val->binary.len);
|
|
SET_VARSIZE(out, VARHDRSZ + val->binary.len);
|
|
memcpy(VARDATA(out), val->binary.data, val->binary.len);
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
/*
|
|
* 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
|
|
* than b. Consistent with the requirements for a B-Tree operator class
|
|
*
|
|
* Strings are compared lexically, in contrast with other places where we use a
|
|
* much simpler comparator logic for searching through Strings. Since this is
|
|
* called from B-Tree support function 1, we're careful about not leaking
|
|
* memory here.
|
|
*/
|
|
int
|
|
compareJsonbSuperHeaderValue(JsonbSuperHeader a, JsonbSuperHeader b)
|
|
{
|
|
JsonbIterator *ita,
|
|
*itb;
|
|
int res = 0;
|
|
|
|
ita = JsonbIteratorInit(a);
|
|
itb = JsonbIteratorInit(b);
|
|
|
|
do
|
|
{
|
|
JsonbValue va,
|
|
vb;
|
|
int 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)
|
|
{
|
|
/* Decisively equal */
|
|
break;
|
|
}
|
|
|
|
if (va.type == vb.type)
|
|
{
|
|
switch (va.type)
|
|
{
|
|
case jbvString:
|
|
res = lexicalCompareJsonbStringValue(&va, &vb);
|
|
break;
|
|
case jbvNull:
|
|
case jbvNumeric:
|
|
case jbvBool:
|
|
res = compareJsonbScalarValue(&va, &vb);
|
|
break;
|
|
case jbvArray:
|
|
/*
|
|
* This could be a "raw scalar" pseudo array. That's a
|
|
* special case here though, since we still want the
|
|
* general type-based comparisons to apply, and as far
|
|
* as we're concerned a pseudo array is just a scalar.
|
|
*/
|
|
if (va.array.rawScalar != vb.array.rawScalar)
|
|
res = (va.array.rawScalar) ? -1 : 1;
|
|
if (va.array.nElems != vb.array.nElems)
|
|
res = (va.array.nElems > vb.array.nElems) ? 1 : -1;
|
|
break;
|
|
case jbvObject:
|
|
if (va.object.nPairs != vb.object.nPairs)
|
|
res = (va.object.nPairs > vb.object.nPairs) ? 1 : -1;
|
|
break;
|
|
case jbvBinary:
|
|
elog(ERROR, "unexpected jbvBinary value");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Type-defined order */
|
|
res = (va.type > vb.type) ? 1 : -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* It's safe to assume that the types differed.
|
|
*
|
|
* If the two values were 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.
|
|
*/
|
|
Assert(va.type != vb.type);
|
|
Assert(va.type == jbvArray || va.type == jbvObject);
|
|
Assert(vb.type == jbvArray || vb.type == jbvObject);
|
|
/* Type-defined order */
|
|
res = (va.type > vb.type) ? 1 : -1;
|
|
}
|
|
}
|
|
while (res == 0);
|
|
|
|
while (ita != NULL)
|
|
{
|
|
JsonbIterator *i = ita->parent;
|
|
pfree(ita);
|
|
ita = i;
|
|
}
|
|
while (itb != NULL)
|
|
{
|
|
JsonbIterator *i = itb->parent;
|
|
pfree(itb);
|
|
itb = i;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
* Find value in object (i.e. the "value" part of some key/value pair in an
|
|
* object), or find a matching element if we're looking through an array. Do
|
|
* so on the basis of equality of the object keys only, or alternatively
|
|
* element values only, with a caller-supplied value "key". The "flags"
|
|
* argument allows the caller to specify which container types are of interest.
|
|
*
|
|
* This exported utility function exists to facilitate various cases concerned
|
|
* with "containment". If asked to look through an object, the caller had
|
|
* better pass a Jsonb String, because their keys can only be strings.
|
|
* Otherwise, for an array, any type of JsonbValue will do.
|
|
*
|
|
* 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
|
|
* 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.)
|
|
*
|
|
* 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,
|
|
* 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)
|
|
{
|
|
uint32 superheader = *(uint32 *) sheader;
|
|
JEntry *array = (JEntry *) (sheader + sizeof(uint32));
|
|
int count = (superheader & JB_CMASK);
|
|
JsonbValue *result = palloc(sizeof(JsonbValue));
|
|
|
|
Assert((flags & ~(JB_FARRAY | JB_FOBJECT)) == 0);
|
|
|
|
if (flags & JB_FARRAY & superheader)
|
|
{
|
|
char *data = (char *) (array + (superheader & JB_CMASK));
|
|
int i;
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
JEntry *e = array + i;
|
|
|
|
if (JBE_ISNULL(*e) && key->type == jbvNull)
|
|
{
|
|
result->type = jbvNull;
|
|
result->estSize = sizeof(JEntry);
|
|
}
|
|
else if (JBE_ISSTRING(*e) && key->type == jbvString)
|
|
{
|
|
result->type = jbvString;
|
|
result->string.val = data + JBE_OFF(*e);
|
|
result->string.len = JBE_LEN(*e);
|
|
result->estSize = sizeof(JEntry) + result->string.len;
|
|
}
|
|
else if (JBE_ISNUMERIC(*e) && key->type == jbvNumeric)
|
|
{
|
|
result->type = jbvNumeric;
|
|
result->numeric = (Numeric) (data + INTALIGN(JBE_OFF(*e)));
|
|
result->estSize = 2 * sizeof(JEntry) +
|
|
VARSIZE_ANY(result->numeric);
|
|
}
|
|
else if (JBE_ISBOOL(*e) && key->type == jbvBool)
|
|
{
|
|
result->type = jbvBool;
|
|
result->boolean = JBE_ISBOOL_TRUE(*e) != 0;
|
|
result->estSize = sizeof(JEntry);
|
|
}
|
|
else
|
|
continue;
|
|
|
|
if (compareJsonbScalarValue(key, result) == 0)
|
|
return result;
|
|
}
|
|
}
|
|
else if (flags & JB_FOBJECT & superheader)
|
|
{
|
|
/* 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 */
|
|
Assert(key->type == jbvString);
|
|
|
|
/* Binary search on object/pair keys *only* */
|
|
while (stopLow < count)
|
|
{
|
|
JEntry *entry;
|
|
int difference;
|
|
JsonbValue candidate;
|
|
|
|
/*
|
|
* Note how we compensate for the fact that we're iterating through
|
|
* pairs (not entries) throughout.
|
|
*/
|
|
stopMiddle = stopLow + (count - stopLow) / 2;
|
|
|
|
entry = array + stopMiddle * 2;
|
|
|
|
candidate.type = jbvString;
|
|
candidate.string.val = data + JBE_OFF(*entry);
|
|
candidate.string.len = JBE_LEN(*entry);
|
|
candidate.estSize = sizeof(JEntry) + candidate.string.len;
|
|
|
|
difference = lengthCompareJsonbStringValue(&candidate, key, NULL);
|
|
|
|
if (difference == 0)
|
|
{
|
|
/* Found our value (from key/value pair) */
|
|
JEntry *v = entry + 1;
|
|
|
|
if (lowbound)
|
|
*lowbound = stopMiddle + 1;
|
|
|
|
if (JBE_ISNULL(*v))
|
|
{
|
|
result->type = jbvNull;
|
|
result->estSize = sizeof(JEntry);
|
|
}
|
|
else if (JBE_ISSTRING(*v))
|
|
{
|
|
result->type = jbvString;
|
|
result->string.val = data + JBE_OFF(*v);
|
|
result->string.len = JBE_LEN(*v);
|
|
result->estSize = sizeof(JEntry) + result->string.len;
|
|
}
|
|
else if (JBE_ISNUMERIC(*v))
|
|
{
|
|
result->type = jbvNumeric;
|
|
result->numeric = (Numeric) (data + INTALIGN(JBE_OFF(*v)));
|
|
result->estSize = 2 * sizeof(JEntry) +
|
|
VARSIZE_ANY(result->numeric);
|
|
}
|
|
else if (JBE_ISBOOL(*v))
|
|
{
|
|
result->type = jbvBool;
|
|
result->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->binary.data = data + INTALIGN(JBE_OFF(*v));
|
|
result->binary.len = JBE_LEN(*v) -
|
|
(INTALIGN(JBE_OFF(*v)) - JBE_OFF(*v));
|
|
result->estSize = 2 * sizeof(JEntry) + result->binary.len;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
else
|
|
{
|
|
if (difference < 0)
|
|
stopLow = stopMiddle + 1;
|
|
else
|
|
count = stopMiddle;
|
|
}
|
|
}
|
|
|
|
if (lowbound)
|
|
*lowbound = stopLow;
|
|
}
|
|
|
|
/* Not found */
|
|
pfree(result);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Get i-th value of Jsonb array from superheader.
|
|
*
|
|
* Returns palloc()'d copy of value.
|
|
*/
|
|
JsonbValue *
|
|
getIthJsonbValueFromSuperHeader(JsonbSuperHeader sheader, uint32 i)
|
|
{
|
|
uint32 superheader = *(uint32 *) sheader;
|
|
JsonbValue *result;
|
|
JEntry *array,
|
|
*e;
|
|
char *data;
|
|
|
|
result = palloc(sizeof(JsonbValue));
|
|
|
|
if (i >= (superheader & JB_CMASK))
|
|
return NULL;
|
|
|
|
array = (JEntry *) (sheader + sizeof(uint32));
|
|
|
|
if (superheader & JB_FARRAY)
|
|
{
|
|
e = array + i;
|
|
data = (char *) (array + (superheader & JB_CMASK));
|
|
}
|
|
else
|
|
{
|
|
elog(ERROR, "not a jsonb array");
|
|
}
|
|
|
|
if (JBE_ISNULL(*e))
|
|
{
|
|
result->type = jbvNull;
|
|
result->estSize = sizeof(JEntry);
|
|
}
|
|
else if (JBE_ISSTRING(*e))
|
|
{
|
|
result->type = jbvString;
|
|
result->string.val = data + JBE_OFF(*e);
|
|
result->string.len = JBE_LEN(*e);
|
|
result->estSize = sizeof(JEntry) + result->string.len;
|
|
}
|
|
else if (JBE_ISNUMERIC(*e))
|
|
{
|
|
result->type = jbvNumeric;
|
|
result->numeric = (Numeric) (data + INTALIGN(JBE_OFF(*e)));
|
|
result->estSize = 2 * sizeof(JEntry) + VARSIZE_ANY(result->numeric);
|
|
}
|
|
else if (JBE_ISBOOL(*e))
|
|
{
|
|
result->type = jbvBool;
|
|
result->boolean = JBE_ISBOOL_TRUE(*e) != 0;
|
|
result->estSize = sizeof(JEntry);
|
|
}
|
|
else
|
|
{
|
|
result->type = jbvBinary;
|
|
result->binary.data = data + INTALIGN(JBE_OFF(*e));
|
|
result->binary.len = JBE_LEN(*e) - (INTALIGN(JBE_OFF(*e)) - JBE_OFF(*e));
|
|
result->estSize = result->binary.len + 2 * sizeof(JEntry);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Push JsonbValue into JsonbParseState.
|
|
*
|
|
* Used when parsing JSON tokens to form Jsonb, or when converting an in-memory
|
|
* JsonbValue to a Jsonb.
|
|
*
|
|
* Initial state of *JsonbParseState is NULL, since it'll be allocated here
|
|
* originally (caller will get JsonbParseState back by reference).
|
|
*
|
|
* 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.
|
|
*/
|
|
JsonbValue *
|
|
pushJsonbValue(JsonbParseState ** pstate, int seq, JsonbValue * scalarVal)
|
|
{
|
|
JsonbValue *result = NULL;
|
|
|
|
switch (seq)
|
|
{
|
|
case WJB_BEGIN_ARRAY:
|
|
Assert(!scalarVal || scalarVal->array.rawScalar);
|
|
*pstate = pushState(pstate);
|
|
result = &(*pstate)->contVal;
|
|
(*pstate)->contVal.type = jbvArray;
|
|
(*pstate)->contVal.estSize = 3 * sizeof(JEntry);
|
|
(*pstate)->contVal.array.nElems = 0;
|
|
(*pstate)->contVal.array.rawScalar = (scalarVal &&
|
|
scalarVal->array.rawScalar);
|
|
if (scalarVal && scalarVal->array.nElems > 0)
|
|
{
|
|
/* Assume that this array is still really a scalar */
|
|
Assert(scalarVal->type == jbvArray);
|
|
(*pstate)->size = scalarVal->array.nElems;
|
|
}
|
|
else
|
|
{
|
|
(*pstate)->size = 4;
|
|
}
|
|
(*pstate)->contVal.array.elems = palloc(sizeof(JsonbValue) *
|
|
(*pstate)->size);
|
|
break;
|
|
case WJB_BEGIN_OBJECT:
|
|
Assert(!scalarVal);
|
|
*pstate = pushState(pstate);
|
|
result = &(*pstate)->contVal;
|
|
(*pstate)->contVal.type = jbvObject;
|
|
(*pstate)->contVal.estSize = 3 * sizeof(JEntry);
|
|
(*pstate)->contVal.object.nPairs = 0;
|
|
(*pstate)->size = 4;
|
|
(*pstate)->contVal.object.pairs = palloc(sizeof(JsonbPair) *
|
|
(*pstate)->size);
|
|
break;
|
|
case WJB_KEY:
|
|
Assert(scalarVal->type == jbvString);
|
|
appendKey(*pstate, scalarVal);
|
|
break;
|
|
case WJB_VALUE:
|
|
Assert(IsAJsonbScalar(scalarVal) ||
|
|
scalarVal->type == jbvBinary);
|
|
appendValue(*pstate, scalarVal);
|
|
break;
|
|
case WJB_ELEM:
|
|
Assert(IsAJsonbScalar(scalarVal) ||
|
|
scalarVal->type == jbvBinary);
|
|
appendElement(*pstate, scalarVal);
|
|
break;
|
|
case WJB_END_OBJECT:
|
|
uniqueifyJsonbObject(&(*pstate)->contVal);
|
|
case WJB_END_ARRAY:
|
|
/* Steps here common to WJB_END_OBJECT case */
|
|
Assert(!scalarVal);
|
|
result = &(*pstate)->contVal;
|
|
|
|
/*
|
|
* Pop stack and push current array/object as value in parent
|
|
* array/object
|
|
*/
|
|
*pstate = (*pstate)->next;
|
|
if (*pstate)
|
|
{
|
|
switch ((*pstate)->contVal.type)
|
|
{
|
|
case jbvArray:
|
|
appendElement(*pstate, result);
|
|
break;
|
|
case jbvObject:
|
|
appendValue(*pstate, result);
|
|
break;
|
|
default:
|
|
elog(ERROR, "invalid jsonb container type");
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
elog(ERROR, "unrecognized jsonb sequential processing token");
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Given a Jsonb superheader, 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)
|
|
{
|
|
JsonbIterator *it = palloc(sizeof(JsonbIterator));
|
|
|
|
iteratorFromContainerBuf(it, sheader);
|
|
it->parent = NULL;
|
|
|
|
return it;
|
|
}
|
|
|
|
/*
|
|
* Get next JsonbValue while iterating
|
|
*
|
|
* Caller should initially pass their own, original iterator. They may get
|
|
* back a child iterator palloc()'d here instead. The function can be relied
|
|
* on to free those child iterators, lest the memory allocated for highly
|
|
* nested objects become unreasonable, but only if callers don't end iteration
|
|
* early (by breaking upon having found something in a search, for example).
|
|
*
|
|
* Callers in such a scenario, that are particularly sensitive to leaking
|
|
* memory in a long-lived context may walk the ancestral tree from the final
|
|
* iterator we left them with to its oldest ancestor, pfree()ing as they go.
|
|
* They do not have to free any other memory previously allocated for iterators
|
|
* but not accessible as direct ancestors of the iterator they're last passed
|
|
* back.
|
|
*
|
|
* Returns "Jsonb sequential processing" token value. Iterator "state"
|
|
* reflects the current stage of the process in a less granular fashion, and is
|
|
* mostly used here to track things internally with respect to particular
|
|
* iterators.
|
|
*
|
|
* Clients of this function should not have to handle any jbvBinary values
|
|
* (since recursive calls will deal with this), provided skipNested is false.
|
|
* 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.
|
|
*/
|
|
int
|
|
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)
|
|
{
|
|
if (state == jbi_start)
|
|
{
|
|
/* Set v to array on first array call */
|
|
val->type = jbvArray;
|
|
val->array.nElems = (*it)->nElems;
|
|
/*
|
|
* v->array.elems is not actually set, because we aren't doing a
|
|
* full conversion
|
|
*/
|
|
val->array.rawScalar = (*it)->isScalar;
|
|
(*it)->i = 0;
|
|
/* Set state for next call */
|
|
(*it)->state = jbi_elem;
|
|
return WJB_BEGIN_ARRAY;
|
|
}
|
|
else if (state == jbi_elem)
|
|
{
|
|
if ((*it)->i >= (*it)->nElems)
|
|
{
|
|
/*
|
|
* All elements within array already processed. Report this to
|
|
* caller, and give it back original parent iterator (which
|
|
* independently tracks iteration progress at its level of
|
|
* nesting).
|
|
*/
|
|
*it = freeAndGetParent(*it);
|
|
return WJB_END_ARRAY;
|
|
}
|
|
else if (formIterIsContainer(it, val, &(*it)->meta[(*it)->i++],
|
|
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);
|
|
}
|
|
else
|
|
{
|
|
/* Scalar item in array */
|
|
return WJB_ELEM;
|
|
}
|
|
}
|
|
}
|
|
else if ((*it)->containerType == JB_FOBJECT)
|
|
{
|
|
if (state == jbi_start)
|
|
{
|
|
/* Set v to object on first object call */
|
|
val->type = jbvObject;
|
|
val->object.nPairs = (*it)->nElems;
|
|
/*
|
|
* v->object.pairs is not actually set, because we aren't doing a
|
|
* full conversion
|
|
*/
|
|
(*it)->i = 0;
|
|
/* Set state for next call */
|
|
(*it)->state = jbi_key;
|
|
return WJB_BEGIN_OBJECT;
|
|
}
|
|
else if (state == jbi_key)
|
|
{
|
|
if ((*it)->i >= (*it)->nElems)
|
|
{
|
|
/*
|
|
* All pairs within object already processed. Report this to
|
|
* caller, and give it back original containing iterator (which
|
|
* independently tracks iteration progress at its level of
|
|
* nesting).
|
|
*/
|
|
*it = freeAndGetParent(*it);
|
|
return WJB_END_OBJECT;
|
|
}
|
|
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");
|
|
|
|
Assert(val->type == jbvString);
|
|
/* Set state for next call */
|
|
(*it)->state = jbi_value;
|
|
return WJB_KEY;
|
|
}
|
|
}
|
|
else if (state == jbi_value)
|
|
{
|
|
/* Set state for next call */
|
|
(*it)->state = jbi_key;
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
if (formIterIsContainer(it, val, &(*it)->meta[((*it)->i++) * 2 + 1],
|
|
skipNested))
|
|
return JsonbIteratorNext(it, val, skipNested);
|
|
else
|
|
return WJB_VALUE;
|
|
}
|
|
}
|
|
|
|
elog(ERROR, "invalid iterator state");
|
|
}
|
|
|
|
/*
|
|
* Worker for "contains" operator's function
|
|
*
|
|
* Formally speaking, containment is top-down, unordered subtree isomorphism.
|
|
*
|
|
* Takes iterators that belong to some container type. These iterators
|
|
* "belong" to those values in the sense that they've just been initialized in
|
|
* respect of them by the caller (perhaps in a nested fashion).
|
|
*
|
|
* "val" is lhs Jsonb, and mContained is rhs Jsonb when called from top level.
|
|
* We determine if mContained is contained within val.
|
|
*/
|
|
bool
|
|
JsonbDeepContains(JsonbIterator ** val, JsonbIterator ** mContained)
|
|
{
|
|
uint32 rval,
|
|
rcont;
|
|
JsonbValue vval,
|
|
vcontained;
|
|
/*
|
|
* Guard against stack overflow due to overly complex Jsonb.
|
|
*
|
|
* Functions called here independently take this precaution, but that might
|
|
* not be sufficient since this is also a recursive function.
|
|
*/
|
|
check_stack_depth();
|
|
|
|
rval = JsonbIteratorNext(val, &vval, false);
|
|
rcont = JsonbIteratorNext(mContained, &vcontained, false);
|
|
|
|
if (rval != rcont)
|
|
{
|
|
/*
|
|
* The differing return values can immediately be taken as indicating
|
|
* two differing container types at this nesting level, which is
|
|
* sufficient reason to give up entirely (but it should be the case
|
|
* that they're both some container type).
|
|
*/
|
|
Assert(rval == WJB_BEGIN_OBJECT || rval == WJB_BEGIN_ARRAY);
|
|
Assert(rcont == WJB_BEGIN_OBJECT || rcont == WJB_BEGIN_ARRAY);
|
|
return false;
|
|
}
|
|
else if (rcont == WJB_BEGIN_OBJECT)
|
|
{
|
|
JsonbValue *lhsVal; /* lhsVal is from pair in lhs object */
|
|
|
|
Assert(vcontained.type == jbvObject);
|
|
|
|
/* Work through rhs "is it contained within?" object */
|
|
for (;;)
|
|
{
|
|
rcont = JsonbIteratorNext(mContained, &vcontained, false);
|
|
|
|
/*
|
|
* When we get through caller's rhs "is it contained within?"
|
|
* object without failing to find one of its values, it's
|
|
* contained.
|
|
*/
|
|
if (rcont == WJB_END_OBJECT)
|
|
return true;
|
|
|
|
Assert(rcont == WJB_KEY);
|
|
|
|
/* First, find value by key... */
|
|
lhsVal = findJsonbValueFromSuperHeader((*val)->buffer,
|
|
JB_FOBJECT,
|
|
NULL,
|
|
&vcontained);
|
|
|
|
if (!lhsVal)
|
|
return false;
|
|
|
|
/*
|
|
* ...at this stage it is apparent that there is at least a key
|
|
* match for this rhs pair.
|
|
*/
|
|
rcont = JsonbIteratorNext(mContained, &vcontained, true);
|
|
|
|
Assert(rcont == WJB_VALUE);
|
|
|
|
/*
|
|
* Compare rhs pair's value with lhs pair's value just found using
|
|
* key
|
|
*/
|
|
if (lhsVal->type != vcontained.type)
|
|
{
|
|
return false;
|
|
}
|
|
else if (IsAJsonbScalar(lhsVal))
|
|
{
|
|
if (compareJsonbScalarValue(lhsVal, &vcontained) != 0)
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
/* Nested container value (object or array) */
|
|
JsonbIterator *nestval, *nestContained;
|
|
|
|
Assert(lhsVal->type == jbvBinary);
|
|
Assert(vcontained.type == jbvBinary);
|
|
|
|
nestval = JsonbIteratorInit(lhsVal->binary.data);
|
|
nestContained = JsonbIteratorInit(vcontained.binary.data);
|
|
|
|
/*
|
|
* Match "value" side of rhs datum object's pair recursively.
|
|
* It's a nested structure.
|
|
*
|
|
* Note that nesting still has to "match up" at the right
|
|
* nesting sub-levels. However, there need only be zero or
|
|
* more matching pairs (or elements) at each nesting level
|
|
* (provided the *rhs* pairs/elements *all* match on each
|
|
* level), which enables searching nested structures for a
|
|
* single String or other primitive type sub-datum quite
|
|
* effectively (provided the user constructed the rhs nested
|
|
* structure such that we "know where to look").
|
|
*
|
|
* In other words, the mapping of container nodes in the rhs
|
|
* "vcontained" Jsonb to internal nodes on the lhs is
|
|
* injective, and parent-child edges on the rhs must be mapped
|
|
* to parent-child edges on the lhs to satisfy the condition of
|
|
* containment (plus of course the mapped nodes must be equal).
|
|
*/
|
|
if (!JsonbDeepContains(&nestval, &nestContained))
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else if (rcont == WJB_BEGIN_ARRAY)
|
|
{
|
|
JsonbValue *lhsConts = NULL;
|
|
uint32 nLhsElems = vval.array.nElems;
|
|
|
|
Assert(vcontained.type == jbvArray);
|
|
|
|
/*
|
|
* Handle distinction between "raw scalar" pseudo arrays, and real
|
|
* arrays.
|
|
*
|
|
* A raw scalar may contain another raw scalar, and an array may
|
|
* contain a raw scalar, but a raw scalar may not contain an array. We
|
|
* don't do something like this for the object case, since objects can
|
|
* only contain pairs, never raw scalars (a pair is represented by an
|
|
* rhs object argument with a single contained pair).
|
|
*/
|
|
if (vval.array.rawScalar && !vcontained.array.rawScalar)
|
|
return false;
|
|
|
|
/* Work through rhs "is it contained within?" array */
|
|
for (;;)
|
|
{
|
|
rcont = JsonbIteratorNext(mContained, &vcontained, true);
|
|
|
|
/*
|
|
* When we get through caller's rhs "is it contained within?" array
|
|
* without failing to find one of its values, it's contained.
|
|
*/
|
|
if (rcont == WJB_END_ARRAY)
|
|
return true;
|
|
|
|
Assert(rcont == WJB_ELEM);
|
|
|
|
if (IsAJsonbScalar(&vcontained))
|
|
{
|
|
if (!findJsonbValueFromSuperHeader((*val)->buffer,
|
|
JB_FARRAY,
|
|
NULL,
|
|
&vcontained))
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
uint32 i;
|
|
|
|
/*
|
|
* If this is first container found in rhs array (at this
|
|
* depth), initialize temp lhs array of containers
|
|
*/
|
|
if (lhsConts == NULL)
|
|
{
|
|
uint32 j = 0;
|
|
|
|
/* Make room for all possible values */
|
|
lhsConts = palloc(sizeof(JsonbValue) * nLhsElems);
|
|
|
|
for (i = 0; i < nLhsElems; i++)
|
|
{
|
|
/* Store all lhs elements in temp array*/
|
|
rcont = JsonbIteratorNext(val, &vval, true);
|
|
Assert(rcont == WJB_ELEM);
|
|
|
|
if (vval.type == jbvBinary)
|
|
lhsConts[j++] = vval;
|
|
}
|
|
|
|
/* No container elements in temp array, so give up now */
|
|
if (j == 0)
|
|
return false;
|
|
|
|
/* We may have only partially filled array */
|
|
nLhsElems = j;
|
|
}
|
|
|
|
/* XXX: Nested array containment is O(N^2) */
|
|
for (i = 0; i < nLhsElems; i++)
|
|
{
|
|
/* Nested container value (object or array) */
|
|
JsonbIterator *nestval, *nestContained;
|
|
bool contains;
|
|
|
|
nestval = JsonbIteratorInit(lhsConts[i].binary.data);
|
|
nestContained = JsonbIteratorInit(vcontained.binary.data);
|
|
|
|
contains = JsonbDeepContains(&nestval, &nestContained);
|
|
|
|
if (nestval)
|
|
pfree(nestval);
|
|
if (nestContained)
|
|
pfree(nestContained);
|
|
if (contains)
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Report rhs container value is not contained if couldn't
|
|
* match rhs container to *some* lhs cont
|
|
*/
|
|
if (i == nLhsElems)
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
elog(ERROR, "invalid jsonb container type");
|
|
}
|
|
|
|
elog(ERROR, "unexpectedly fell off end of jsonb container");
|
|
}
|
|
|
|
/*
|
|
* 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->array.rawScalar = false;
|
|
result->array.elems = palloc(sizeof(JsonbPair) * elem_count);
|
|
|
|
for (i = 0, j = 0; i < elem_count; i++)
|
|
{
|
|
if (!key_nulls[i])
|
|
{
|
|
result->array.elems[j].type = jbvString;
|
|
result->array.elems[j].string.val = VARDATA(key_datums[i]);
|
|
result->array.elems[j].string.len = VARSIZE(key_datums[i]) - VARHDRSZ;
|
|
j++;
|
|
}
|
|
}
|
|
result->array.nElems = j;
|
|
|
|
uniqueifyJsonbArray(result);
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Hash a JsonbValue scalar value, mixing in the hash value with an existing
|
|
* hash provided by the caller.
|
|
*
|
|
* Some callers may wish to independently XOR in JB_FOBJECT and JB_FARRAY
|
|
* flags.
|
|
*/
|
|
void
|
|
JsonbHashScalarValue(const JsonbValue * scalarVal, uint32 * hash)
|
|
{
|
|
int tmp;
|
|
|
|
/*
|
|
* Combine hash values of successive keys, values and elements by rotating
|
|
* the previous value left 1 bit, then XOR'ing in the new
|
|
* key/value/element's hash value.
|
|
*/
|
|
*hash = (*hash << 1) | (*hash >> 31);
|
|
switch (scalarVal->type)
|
|
{
|
|
case jbvNull:
|
|
*hash ^= 0x01;
|
|
return;
|
|
case jbvString:
|
|
tmp = hash_any((unsigned char *) scalarVal->string.val,
|
|
scalarVal->string.len);
|
|
*hash ^= tmp;
|
|
return;
|
|
case jbvNumeric:
|
|
/* Must be unaffected by trailing zeroes */
|
|
tmp = DatumGetInt32(DirectFunctionCall1(hash_numeric,
|
|
NumericGetDatum(scalarVal->numeric)));
|
|
*hash ^= tmp;
|
|
return;
|
|
case jbvBool:
|
|
*hash ^= scalarVal->boolean? 0x02:0x04;
|
|
return;
|
|
default:
|
|
elog(ERROR, "invalid jsonb scalar type");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
if (aScalar->type == bScalar->type)
|
|
{
|
|
switch (aScalar->type)
|
|
{
|
|
case jbvNull:
|
|
return 0;
|
|
case jbvString:
|
|
return lengthCompareJsonbStringValue(aScalar, bScalar, NULL);
|
|
case jbvNumeric:
|
|
return DatumGetInt32(DirectFunctionCall2(numeric_cmp,
|
|
PointerGetDatum(aScalar->numeric),
|
|
PointerGetDatum(bScalar->numeric)));
|
|
case jbvBool:
|
|
if (aScalar->boolean != bScalar->boolean)
|
|
return (aScalar->boolean > bScalar->boolean) ? 1 : -1;
|
|
else
|
|
return 0;
|
|
default:
|
|
elog(ERROR, "invalid jsonb scalar type");
|
|
}
|
|
}
|
|
elog(ERROR, "jsonb scalar type mismatch");
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
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->string.val, va->string.len, vb->string.val,
|
|
vb->string.len, DEFAULT_COLLATION_OID);
|
|
}
|
|
|
|
/*
|
|
* Given a JsonbValue, convert to Jsonb and store in preallocated Jsonb buffer
|
|
* sufficiently large to fit the value
|
|
*/
|
|
static Size
|
|
convertJsonb(JsonbValue * val, Jsonb *buffer)
|
|
{
|
|
convertState state;
|
|
Size len;
|
|
|
|
/* Should not already have binary representation */
|
|
Assert(val->type != jbvBinary);
|
|
|
|
state.buffer = buffer;
|
|
/* Start from superheader */
|
|
state.ptr = VARDATA(state.buffer);
|
|
state.levelSz = 8;
|
|
state.allState = palloc(sizeof(convertLevel) * state.levelSz);
|
|
|
|
walkJsonbValueConversion(val, &state, 0);
|
|
|
|
len = state.ptr - VARDATA(state.buffer);
|
|
|
|
Assert(len <= val->estSize);
|
|
return len;
|
|
}
|
|
|
|
/*
|
|
* 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).
|
|
*/
|
|
static void
|
|
walkJsonbValueConversion(JsonbValue * val, convertState * cstate,
|
|
uint32 nestlevel)
|
|
{
|
|
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->array.nElems; i++)
|
|
{
|
|
if (IsAJsonbScalar(&val->array.elems[i]) ||
|
|
val->array.elems[i].type == jbvBinary)
|
|
putJsonbValueConversion(cstate, val->array.elems + i,
|
|
WJB_ELEM, nestlevel);
|
|
else
|
|
walkJsonbValueConversion(val->array.elems + i, cstate,
|
|
nestlevel + 1);
|
|
}
|
|
putJsonbValueConversion(cstate, val, WJB_END_ARRAY, nestlevel);
|
|
|
|
break;
|
|
case jbvObject:
|
|
|
|
putJsonbValueConversion(cstate, val, WJB_BEGIN_OBJECT, nestlevel);
|
|
for (i = 0; i < val->object.nPairs; i++)
|
|
{
|
|
putJsonbValueConversion(cstate, &val->object.pairs[i].key,
|
|
WJB_KEY, nestlevel);
|
|
|
|
if (IsAJsonbScalar(&val->object.pairs[i].value) ||
|
|
val->object.pairs[i].value.type == jbvBinary)
|
|
putJsonbValueConversion(cstate,
|
|
&val->object.pairs[i].value,
|
|
WJB_VALUE, nestlevel);
|
|
else
|
|
walkJsonbValueConversion(&val->object.pairs[i].value,
|
|
cstate, nestlevel + 1);
|
|
}
|
|
putJsonbValueConversion(cstate, val, WJB_END_OBJECT, nestlevel);
|
|
|
|
break;
|
|
default:
|
|
elog(ERROR, "unknown type of jsonb container");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* walkJsonbValueConversion() worker. Add padding sufficient to int-align our
|
|
* access to conversion buffer.
|
|
*/
|
|
static inline
|
|
short addPaddingInt(convertState * cstate)
|
|
{
|
|
short padlen, p;
|
|
|
|
padlen = INTALIGN(cstate->ptr - VARDATA(cstate->buffer)) -
|
|
(cstate->ptr - VARDATA(cstate->buffer));
|
|
|
|
for (p = padlen; p > 0; p--)
|
|
{
|
|
*cstate->ptr = '\0';
|
|
cstate->ptr++;
|
|
}
|
|
|
|
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).
|
|
*/
|
|
static void
|
|
putJsonbValueConversion(convertState * cstate, JsonbValue * val, uint32 flags,
|
|
uint32 level)
|
|
{
|
|
if (level == cstate->levelSz)
|
|
{
|
|
cstate->levelSz *= 2;
|
|
cstate->allState = repalloc(cstate->allState,
|
|
sizeof(convertLevel) * cstate->levelSz);
|
|
}
|
|
|
|
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;
|
|
|
|
addPaddingInt(cstate);
|
|
|
|
/* 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;
|
|
|
|
if (val->type == jbvArray)
|
|
{
|
|
*cstate->contPtr->header = val->array.nElems | JB_FARRAY;
|
|
cstate->ptr += sizeof(JEntry) * val->array.nElems;
|
|
|
|
if (val->array.rawScalar)
|
|
{
|
|
Assert(val->array.nElems == 1);
|
|
Assert(level == 0);
|
|
*cstate->contPtr->header |= JB_FSCALAR;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*cstate->contPtr->header = val->object.nPairs | JB_FOBJECT;
|
|
cstate->ptr += sizeof(JEntry) * 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);
|
|
|
|
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;
|
|
|
|
Assert(((flags & WJB_END_ARRAY) && val->type == jbvArray) ||
|
|
((flags & WJB_END_OBJECT) && val->type == jbvObject));
|
|
|
|
if (level == 0)
|
|
return;
|
|
|
|
len = cstate->ptr - (char *) cstate->contPtr->begin;
|
|
|
|
prevPtr = cstate->contPtr - 1;
|
|
|
|
if (*prevPtr->header & JB_FARRAY)
|
|
{
|
|
i = prevPtr->i;
|
|
|
|
prevPtr->meta[i].header = JENTRY_ISNEST;
|
|
|
|
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 */
|
|
|
|
prevPtr->meta[i].header = JENTRY_ISNEST;
|
|
|
|
prevPtr->meta[i].header |=
|
|
(prevPtr->meta[i - 1].header & JENTRY_POSMASK) + len;
|
|
}
|
|
else
|
|
{
|
|
elog(ERROR, "invalid jsonb container type");
|
|
}
|
|
|
|
Assert(cstate->ptr - cstate->contPtr->begin <= val->estSize);
|
|
prevPtr->i++;
|
|
}
|
|
else
|
|
{
|
|
elog(ERROR, "unknown flag encountered during jsonb tree walk");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
|
|
cstate->contPtr = cstate->allState + level;
|
|
|
|
if (i == 0)
|
|
cstate->contPtr->meta[0].header = JENTRY_ISFIRST;
|
|
else
|
|
cstate->contPtr->meta[i].header = 0;
|
|
|
|
switch (scalarVal->type)
|
|
{
|
|
case jbvNull:
|
|
cstate->contPtr->meta[i].header |= JENTRY_ISNULL;
|
|
|
|
if (i > 0)
|
|
cstate->contPtr->meta[i].header |=
|
|
cstate->contPtr->meta[i - 1].header & JENTRY_POSMASK;
|
|
break;
|
|
case jbvString:
|
|
memcpy(cstate->ptr, scalarVal->string.val, scalarVal->string.len);
|
|
cstate->ptr += scalarVal->string.len;
|
|
|
|
if (i == 0)
|
|
cstate->contPtr->meta[0].header |= scalarVal->string.len;
|
|
else
|
|
cstate->contPtr->meta[i].header |=
|
|
(cstate->contPtr->meta[i - 1].header & JENTRY_POSMASK) +
|
|
scalarVal->string.len;
|
|
break;
|
|
case jbvNumeric:
|
|
numlen = VARSIZE_ANY(scalarVal->numeric);
|
|
padlen = addPaddingInt(cstate);
|
|
|
|
memcpy(cstate->ptr, scalarVal->numeric, numlen);
|
|
cstate->ptr += numlen;
|
|
|
|
cstate->contPtr->meta[i].header |= JENTRY_ISNUMERIC;
|
|
if (i == 0)
|
|
cstate->contPtr->meta[0].header |= padlen + numlen;
|
|
else
|
|
cstate->contPtr->meta[i].header |=
|
|
(cstate->contPtr->meta[i - 1].header & JENTRY_POSMASK)
|
|
+ padlen + numlen;
|
|
break;
|
|
case jbvBool:
|
|
cstate->contPtr->meta[i].header |= (scalarVal->boolean) ?
|
|
JENTRY_ISTRUE : JENTRY_ISFALSE;
|
|
|
|
if (i > 0)
|
|
cstate->contPtr->meta[i].header |=
|
|
cstate->contPtr->meta[i - 1].header & JENTRY_POSMASK;
|
|
break;
|
|
default:
|
|
elog(ERROR, "invalid jsonb scalar type");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Given superheader pointer into buffer, initialize iterator. Must be a
|
|
* container type.
|
|
*/
|
|
static void
|
|
iteratorFromContainerBuf(JsonbIterator * it, JsonbSuperHeader sheader)
|
|
{
|
|
uint32 superheader = *(uint32 *) sheader;
|
|
|
|
it->containerType = superheader & (JB_FARRAY | JB_FOBJECT);
|
|
it->nElems = superheader & JB_CMASK;
|
|
it->buffer = sheader;
|
|
|
|
/* Array starts just after header */
|
|
it->meta = (JEntry *) (sheader + sizeof(uint32));
|
|
it->state = jbi_start;
|
|
|
|
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:
|
|
/*
|
|
* 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");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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))
|
|
{
|
|
val->type = jbvNull;
|
|
val->estSize = sizeof(JEntry);
|
|
|
|
return false;
|
|
}
|
|
else if (JBE_ISSTRING(*ent))
|
|
{
|
|
val->type = jbvString;
|
|
val->string.val = (*it)->dataProper + JBE_OFF(*ent);
|
|
val->string.len = JBE_LEN(*ent);
|
|
val->estSize = sizeof(JEntry) + val->string.len;
|
|
|
|
return false;
|
|
}
|
|
else if (JBE_ISNUMERIC(*ent))
|
|
{
|
|
val->type = jbvNumeric;
|
|
val->numeric = (Numeric) ((*it)->dataProper + INTALIGN(JBE_OFF(*ent)));
|
|
val->estSize = 2 * sizeof(JEntry) + VARSIZE_ANY(val->numeric);
|
|
|
|
return false;
|
|
}
|
|
else if (JBE_ISBOOL(*ent))
|
|
{
|
|
val->type = jbvBool;
|
|
val->boolean = JBE_ISBOOL_TRUE(*ent) != 0;
|
|
val->estSize = sizeof(JEntry);
|
|
|
|
return false;
|
|
}
|
|
else if (skipNested)
|
|
{
|
|
val->type = jbvBinary;
|
|
val->binary.data = (*it)->dataProper + INTALIGN(JBE_OFF(*ent));
|
|
val->binary.len = JBE_LEN(*ent) - (INTALIGN(JBE_OFF(*ent)) - JBE_OFF(*ent));
|
|
val->estSize = val->binary.len + 2 * sizeof(JEntry);
|
|
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Must be container type, so setup caller's iterator to point to that,
|
|
* and return indication of that.
|
|
*
|
|
* Get child iterator.
|
|
*/
|
|
JsonbIterator *child = palloc(sizeof(JsonbIterator));
|
|
|
|
iteratorFromContainerBuf(child,
|
|
(*it)->dataProper + INTALIGN(JBE_OFF(*ent)));
|
|
|
|
child->parent = *it;
|
|
*it = child;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* JsonbIteratorNext() worker: Return parent, while freeing memory for current
|
|
* iterator
|
|
*/
|
|
static JsonbIterator *
|
|
freeAndGetParent(JsonbIterator * it)
|
|
{
|
|
JsonbIterator *v = it->parent;
|
|
|
|
pfree(it);
|
|
return v;
|
|
}
|
|
|
|
/*
|
|
* 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->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->object.nPairs >= pstate->size)
|
|
{
|
|
pstate->size *= 2;
|
|
object->object.pairs = repalloc(object->object.pairs,
|
|
sizeof(JsonbPair) * pstate->size);
|
|
}
|
|
|
|
object->object.pairs[object->object.nPairs].key = *string;
|
|
object->object.pairs[object->object.nPairs].order = object->object.nPairs;
|
|
|
|
object->estSize += string->estSize;
|
|
}
|
|
|
|
/*
|
|
* 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->object.pairs[object->object.nPairs++].value = *scalarVal;
|
|
object->estSize += scalarVal->estSize;
|
|
}
|
|
|
|
/*
|
|
* 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->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->array.nElems >= pstate->size)
|
|
{
|
|
pstate->size *= 2;
|
|
array->array.elems = repalloc(array->array.elems,
|
|
sizeof(JsonbValue) * pstate->size);
|
|
}
|
|
|
|
array->array.elems[array->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
|
|
* 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
|
|
* order.
|
|
*
|
|
* 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)
|
|
{
|
|
const JsonbValue *va = (const JsonbValue *) a;
|
|
const JsonbValue *vb = (const JsonbValue *) b;
|
|
int res;
|
|
|
|
Assert(va->type == jbvString);
|
|
Assert(vb->type == jbvString);
|
|
|
|
if (va->string.len == vb->string.len)
|
|
{
|
|
res = memcmp(va->string.val, vb->string.val, va->string.len);
|
|
if (res == 0 && binequal)
|
|
*((bool *) binequal) = true;
|
|
}
|
|
else
|
|
{
|
|
res = (va->string.len > vb->string.len) ? 1 : -1;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*
|
|
* N.B: String comparisons here are "length-wise"
|
|
*
|
|
* Pairs with equals keys are ordered such that the order field is respected.
|
|
*/
|
|
static int
|
|
lengthCompareJsonbPair(const void *a, const void *b, void *binequal)
|
|
{
|
|
const JsonbPair *pa = (const JsonbPair *) a;
|
|
const JsonbPair *pb = (const JsonbPair *) b;
|
|
int res;
|
|
|
|
res = lengthCompareJsonbStringValue(&pa->key, &pb->key, binequal);
|
|
|
|
/*
|
|
* Guarantee keeping order of equal pair. Unique algorithm will prefer
|
|
* first element as value.
|
|
*/
|
|
if (res == 0)
|
|
res = (pa->order > pb->order) ? -1 : 1;
|
|
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
* Sort and unique-ify pairs in JsonbValue object
|
|
*/
|
|
static void
|
|
uniqueifyJsonbObject(JsonbValue * object)
|
|
{
|
|
bool hasNonUniq = false;
|
|
|
|
Assert(object->type == jbvObject);
|
|
|
|
if (object->object.nPairs > 1)
|
|
qsort_arg(object->object.pairs, object->object.nPairs, sizeof(JsonbPair),
|
|
lengthCompareJsonbPair, &hasNonUniq);
|
|
|
|
if (hasNonUniq)
|
|
{
|
|
JsonbPair *ptr = object->object.pairs + 1,
|
|
*res = object->object.pairs;
|
|
|
|
while (ptr - object->object.pairs < object->object.nPairs)
|
|
{
|
|
/* Avoid copying over duplicate */
|
|
if (lengthCompareJsonbStringValue(ptr, res, NULL) == 0)
|
|
{
|
|
object->estSize -= ptr->key.estSize + ptr->value.estSize;
|
|
}
|
|
else
|
|
{
|
|
res++;
|
|
if (ptr != res)
|
|
memcpy(res, ptr, sizeof(JsonbPair));
|
|
}
|
|
ptr++;
|
|
}
|
|
|
|
object->object.nPairs = res + 1 - object->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->array.nElems > 1)
|
|
qsort_arg(array->array.elems, array->array.nElems,
|
|
sizeof(JsonbValue), lengthCompareJsonbStringValue,
|
|
&hasNonUniq);
|
|
|
|
if (hasNonUniq)
|
|
{
|
|
JsonbValue *ptr = array->array.elems + 1,
|
|
*res = array->array.elems;
|
|
|
|
while (ptr - array->array.elems < array->array.nElems)
|
|
{
|
|
/* Avoid copying over duplicate */
|
|
if (lengthCompareJsonbStringValue(ptr, res, NULL) != 0)
|
|
{
|
|
res++;
|
|
*res = *ptr;
|
|
}
|
|
|
|
ptr++;
|
|
}
|
|
|
|
array->array.nElems = res + 1 - array->array.elems;
|
|
}
|
|
}
|