* jsonb_util.c
* converting between Jsonb and JsonbValues, and iterating.
*
- * Copyright (c) 2014-2016, 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).
#define JSONB_MAX_PAIRS (Min(MaxAllocSize / sizeof(JsonbPair), JB_CMASK))
static void fillJsonbValue(JsonbContainer *container, int index,
- char *base_addr, uint32 offset,
- JsonbValue *result);
+ char *base_addr, uint32 offset,
+ JsonbValue *result);
static bool equalsJsonbScalarValue(JsonbValue *a, JsonbValue *b);
static int compareJsonbScalarValue(JsonbValue *a, JsonbValue *b);
static Jsonb *convertToJsonb(JsonbValue *val);
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);
+ JsonbIteratorToken seq,
+ JsonbValue *scalarVal);
/*
* Turn an in-memory JsonbValue into a Jsonb for on-disk storage.
break;
case jbvBinary:
elog(ERROR, "unexpected jbvBinary value");
+ break;
+ case jbvDatetime:
+ elog(ERROR, "unexpected jbvDatetime value");
+ break;
}
}
else
JsonbValue *key)
{
JEntry *children = container->children;
- int count = (container->header & JB_CMASK);
- JsonbValue *result;
+ int count = JsonContainerSize(container);
Assert((flags & ~(JB_FARRAY | JB_FOBJECT)) == 0);
if (count <= 0)
return NULL;
- result = palloc(sizeof(JsonbValue));
-
- if (flags & JB_FARRAY & container->header)
+ if ((flags & JB_FARRAY) && JsonContainerIsArray(container))
{
+ JsonbValue *result = palloc(sizeof(JsonbValue));
char *base_addr = (char *) (children + count);
uint32 offset = 0;
int i;
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,
- stopHigh = count;
-
/* Object key passed by caller must be a string */
Assert(key->type == jbvString);
- /* Binary search on object/pair keys *only* */
- while (stopLow < stopHigh)
- {
- uint32 stopMiddle;
- int difference;
- JsonbValue candidate;
+ return getKeyJsonValueFromContainer(container, key->val.string.val,
+ key->val.string.len, NULL);
+ }
- stopMiddle = stopLow + (stopHigh - stopLow) / 2;
+ /* Not found */
+ return NULL;
+}
- candidate.type = jbvString;
- candidate.val.string.val =
- base_addr + getJsonbOffset(container, stopMiddle);
- candidate.val.string.len = getJsonbLength(container, stopMiddle);
+/*
+ * 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;
- difference = lengthCompareJsonbStringValue(&candidate, key);
+ Assert(JsonContainerIsObject(container));
- if (difference == 0)
- {
- /* Found our key, return corresponding value */
- int index = stopMiddle + count;
+ /* Quick out without a palloc cycle if object is empty */
+ if (count <= 0)
+ return NULL;
+
+ /*
+ * 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;
- fillJsonbValue(container, index, base_addr,
- getJsonbOffset(container, index),
- result);
+ stopMiddle = stopLow + (stopHigh - stopLow) / 2;
- return result;
- }
+ 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
- stopHigh = 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)
* "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 jvbBinary, which are rolled up arrays and objects,
+ * Values of type jbvBinary, which are rolled up arrays and objects,
* are unpacked before being added to the result.
*/
JsonbValue *
(*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 */
JBE_ADVANCE_OFFSET((*it)->curDataOffset,
(*it)->children[(*it)->curIndex]);
JBE_ADVANCE_OFFSET((*it)->curValueOffset,
- (*it)->children[(*it)->curIndex + (*it)->nElems]);
+ (*it)->children[(*it)->curIndex + (*it)->nElems]);
(*it)->curIndex++;
/*
{
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);
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;
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;
*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?
*/
return lengthCompareJsonbStringValue(aScalar, bScalar) == 0;
case jbvNumeric:
return DatumGetBool(DirectFunctionCall2(numeric_eq,
- PointerGetDatum(aScalar->val.numeric),
- PointerGetDatum(bScalar->val.numeric)));
+ PointerGetDatum(aScalar->val.numeric),
+ PointerGetDatum(bScalar->val.numeric)));
case jbvBool:
return aScalar->val.boolean == bScalar->val.boolean;
}
}
elog(ERROR, "jsonb scalar type mismatch");
- return -1;
+ return false;
}
/*
DEFAULT_COLLATION_OID);
case jbvNumeric:
return DatumGetInt32(DirectFunctionCall2(numeric_cmp,
- PointerGetDatum(aScalar->val.numeric),
- PointerGetDatum(bScalar->val.numeric)));
+ PointerGetDatum(aScalar->val.numeric),
+ PointerGetDatum(bScalar->val.numeric)));
case jbvBool:
if (aScalar->val.boolean == bScalar->val.boolean)
return 0;
- else if (aScalar->val.boolean >bScalar->val.boolean)
+ else if (aScalar->val.boolean > bScalar->val.boolean)
return 1;
else
return -1;
/*
- * Functions for manipulating the resizeable buffer used by convertJsonb and
+ * Functions for manipulating the resizable buffer used by convertJsonb and
* its subroutines.
*/
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");
}
{
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;
}
/*