diff options
-rw-r--r-- | src/backend/utils/adt/jsonb.c | 16 | ||||
-rw-r--r-- | src/backend/utils/adt/jsonb_gin.c | 4 | ||||
-rw-r--r-- | src/backend/utils/adt/jsonb_op.c | 106 | ||||
-rw-r--r-- | src/backend/utils/adt/jsonb_util.c | 929 | ||||
-rw-r--r-- | src/backend/utils/adt/jsonfuncs.c | 64 | ||||
-rw-r--r-- | src/include/utils/jsonb.h | 213 |
6 files changed, 556 insertions, 776 deletions
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index cf5d6f23264..e1fe45f7128 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -33,7 +33,6 @@ static void jsonb_in_array_end(void *pstate); static void jsonb_in_object_field_start(void *pstate, char *fname, bool isnull); static void jsonb_put_escaped_value(StringInfo out, JsonbValue *scalarVal); static void jsonb_in_scalar(void *pstate, char *token, JsonTokenType tokentype); -char *JsonbToCString(StringInfo out, char *in, int estimated_len); /* * jsonb type input function @@ -65,7 +64,7 @@ jsonb_recv(PG_FUNCTION_ARGS) if (version == 1) str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes); else - elog(ERROR, "Unsupported jsonb version number %d", version); + elog(ERROR, "unsupported jsonb version number %d", version); return jsonb_from_cstring(str, nbytes); } @@ -79,7 +78,7 @@ jsonb_out(PG_FUNCTION_ARGS) Jsonb *jb = PG_GETARG_JSONB(0); char *out; - out = JsonbToCString(NULL, VARDATA(jb), VARSIZE(jb)); + out = JsonbToCString(NULL, &jb->root, VARSIZE(jb)); PG_RETURN_CSTRING(out); } @@ -97,7 +96,7 @@ jsonb_send(PG_FUNCTION_ARGS) StringInfo jtext = makeStringInfo(); int version = 1; - (void) JsonbToCString(jtext, VARDATA(jb), VARSIZE(jb)); + (void) JsonbToCString(jtext, &jb->root, VARSIZE(jb)); pq_begintypsend(&buf); pq_sendint(&buf, version, 1); @@ -130,7 +129,7 @@ jsonb_typeof(PG_FUNCTION_ARGS) { Assert(JB_ROOT_IS_SCALAR(in)); - it = JsonbIteratorInit(VARDATA_ANY(in)); + it = JsonbIteratorInit(&in->root); /* * A root scalar is stored as an array of one element, so we get the @@ -249,7 +248,6 @@ jsonb_in_object_field_start(void *pstate, char *fname, bool isnull) v.type = jbvString; v.val.string.len = checkStringLen(strlen(fname)); v.val.string.val = pnstrdup(fname, v.val.string.len); - v.estSize = sizeof(JEntry) + v.val.string.len; _state->res = pushJsonbValue(&_state->parseState, WJB_KEY, &v); } @@ -290,8 +288,6 @@ jsonb_in_scalar(void *pstate, char *token, JsonTokenType tokentype) JsonbInState *_state = (JsonbInState *) pstate; JsonbValue v; - v.estSize = sizeof(JEntry); - switch (tokentype) { @@ -300,7 +296,6 @@ jsonb_in_scalar(void *pstate, char *token, JsonTokenType tokentype) v.type = jbvString; v.val.string.len = checkStringLen(strlen(token)); v.val.string.val = pnstrdup(token, v.val.string.len); - v.estSize += v.val.string.len; break; case JSON_TOKEN_NUMBER: @@ -312,7 +307,6 @@ jsonb_in_scalar(void *pstate, char *token, JsonTokenType tokentype) v.type = jbvNumeric; v.val.numeric = DatumGetNumeric(DirectFunctionCall3(numeric_in, CStringGetDatum(token), 0, -1)); - v.estSize += VARSIZE_ANY(v.val.numeric) +sizeof(JEntry) /* alignment */ ; break; case JSON_TOKEN_TRUE: v.type = jbvBool; @@ -374,7 +368,7 @@ jsonb_in_scalar(void *pstate, char *token, JsonTokenType tokentype) * if they are converting it to a text* object. */ char * -JsonbToCString(StringInfo out, JsonbSuperHeader in, int estimated_len) +JsonbToCString(StringInfo out, JsonbContainer *in, int estimated_len) { bool first = true; JsonbIterator *it; diff --git a/src/backend/utils/adt/jsonb_gin.c b/src/backend/utils/adt/jsonb_gin.c index 67f940ac979..592036ac585 100644 --- a/src/backend/utils/adt/jsonb_gin.c +++ b/src/backend/utils/adt/jsonb_gin.c @@ -80,7 +80,7 @@ gin_extract_jsonb(PG_FUNCTION_ARGS) entries = (Datum *) palloc(sizeof(Datum) * total); - it = JsonbIteratorInit(VARDATA(jb)); + it = JsonbIteratorInit(&jb->root); while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) { @@ -487,7 +487,7 @@ gin_extract_jsonb_hash(PG_FUNCTION_ARGS) entries = (Datum *) palloc(sizeof(Datum) * total); - it = JsonbIteratorInit(VARDATA(jb)); + it = JsonbIteratorInit(&jb->root); tail.parent = NULL; tail.hash = 0; diff --git a/src/backend/utils/adt/jsonb_op.c b/src/backend/utils/adt/jsonb_op.c index 38bd5676739..2d071b2523b 100644 --- a/src/backend/utils/adt/jsonb_op.c +++ b/src/backend/utils/adt/jsonb_op.c @@ -13,6 +13,7 @@ */ #include "postgres.h" +#include "catalog/pg_type.h" #include "miscadmin.h" #include "utils/jsonb.h" @@ -34,10 +35,9 @@ jsonb_exists(PG_FUNCTION_ARGS) kval.val.string.val = VARDATA_ANY(key); kval.val.string.len = VARSIZE_ANY_EXHDR(key); - v = findJsonbValueFromSuperHeader(VARDATA(jb), - JB_FOBJECT | JB_FARRAY, - NULL, - &kval); + v = findJsonbValueFromContainer(&jb->root, + JB_FOBJECT | JB_FARRAY, + &kval); PG_RETURN_BOOL(v != NULL); } @@ -47,29 +47,28 @@ jsonb_exists_any(PG_FUNCTION_ARGS) { Jsonb *jb = PG_GETARG_JSONB(0); ArrayType *keys = PG_GETARG_ARRAYTYPE_P(1); - JsonbValue *arrKey = arrayToJsonbSortedArray(keys); - uint32 *plowbound = NULL, - lowbound = 0; int i; + Datum *key_datums; + bool *key_nulls; + int elem_count; - if (arrKey == NULL || arrKey->val.object.nPairs == 0) - PG_RETURN_BOOL(false); - - if (JB_ROOT_IS_OBJECT(jb)) - plowbound = &lowbound; + deconstruct_array(keys, TEXTOID, -1, false, 'i', &key_datums, &key_nulls, + &elem_count); - /* - * We exploit the fact that the pairs list is already sorted into strictly - * increasing order to narrow the findJsonbValueFromSuperHeader search; - * each search can start one entry past the previous "found" entry, or at - * the lower bound of the last search. - */ - for (i = 0; i < arrKey->val.array.nElems; i++) + for (i = 0; i < elem_count; i++) { - if (findJsonbValueFromSuperHeader(VARDATA(jb), - JB_FOBJECT | JB_FARRAY, - plowbound, - arrKey->val.array.elems + i) != NULL) + JsonbValue strVal; + + if (key_nulls[i]) + continue; + + strVal.type = jbvString; + strVal.val.string.val = VARDATA(key_datums[i]); + strVal.val.string.len = VARSIZE(key_datums[i]) - VARHDRSZ; + + if (findJsonbValueFromContainer(&jb->root, + JB_FOBJECT | JB_FARRAY, + &strVal) != NULL) PG_RETURN_BOOL(true); } @@ -81,29 +80,28 @@ jsonb_exists_all(PG_FUNCTION_ARGS) { Jsonb *jb = PG_GETARG_JSONB(0); ArrayType *keys = PG_GETARG_ARRAYTYPE_P(1); - JsonbValue *arrKey = arrayToJsonbSortedArray(keys); - uint32 *plowbound = NULL; - uint32 lowbound = 0; int i; + Datum *key_datums; + bool *key_nulls; + int elem_count; - if (arrKey == NULL || arrKey->val.array.nElems == 0) - PG_RETURN_BOOL(true); + deconstruct_array(keys, TEXTOID, -1, false, 'i', &key_datums, &key_nulls, + &elem_count); - if (JB_ROOT_IS_OBJECT(jb)) - plowbound = &lowbound; - - /* - * We exploit the fact that the pairs list is already sorted into strictly - * increasing order to narrow the findJsonbValueFromSuperHeader search; - * each search can start one entry past the previous "found" entry, or at - * the lower bound of the last search. - */ - for (i = 0; i < arrKey->val.array.nElems; i++) + for (i = 0; i < elem_count; i++) { - if (findJsonbValueFromSuperHeader(VARDATA(jb), - JB_FOBJECT | JB_FARRAY, - plowbound, - arrKey->val.array.elems + i) == NULL) + JsonbValue strVal; + + if (key_nulls[i]) + continue; + + strVal.type = jbvString; + strVal.val.string.val = VARDATA(key_datums[i]); + strVal.val.string.len = VARSIZE(key_datums[i]) - VARHDRSZ; + + if (findJsonbValueFromContainer(&jb->root, + JB_FOBJECT | JB_FARRAY, + &strVal) == NULL) PG_RETURN_BOOL(false); } @@ -123,8 +121,8 @@ jsonb_contains(PG_FUNCTION_ARGS) JB_ROOT_IS_OBJECT(val) != JB_ROOT_IS_OBJECT(tmpl)) PG_RETURN_BOOL(false); - it1 = JsonbIteratorInit(VARDATA(val)); - it2 = JsonbIteratorInit(VARDATA(tmpl)); + it1 = JsonbIteratorInit(&val->root); + it2 = JsonbIteratorInit(&tmpl->root); PG_RETURN_BOOL(JsonbDeepContains(&it1, &it2)); } @@ -143,8 +141,8 @@ jsonb_contained(PG_FUNCTION_ARGS) JB_ROOT_IS_OBJECT(val) != JB_ROOT_IS_OBJECT(tmpl)) PG_RETURN_BOOL(false); - it1 = JsonbIteratorInit(VARDATA(val)); - it2 = JsonbIteratorInit(VARDATA(tmpl)); + it1 = JsonbIteratorInit(&val->root); + it2 = JsonbIteratorInit(&tmpl->root); PG_RETURN_BOOL(JsonbDeepContains(&it1, &it2)); } @@ -156,7 +154,7 @@ jsonb_ne(PG_FUNCTION_ARGS) Jsonb *jbb = PG_GETARG_JSONB(1); bool res; - res = (compareJsonbSuperHeaderValue(VARDATA(jba), VARDATA(jbb)) != 0); + res = (compareJsonbContainers(&jba->root, &jbb->root) != 0); PG_FREE_IF_COPY(jba, 0); PG_FREE_IF_COPY(jbb, 1); @@ -173,7 +171,7 @@ jsonb_lt(PG_FUNCTION_ARGS) Jsonb *jbb = PG_GETARG_JSONB(1); bool res; - res = (compareJsonbSuperHeaderValue(VARDATA(jba), VARDATA(jbb)) < 0); + res = (compareJsonbContainers(&jba->root, &jbb->root) < 0); PG_FREE_IF_COPY(jba, 0); PG_FREE_IF_COPY(jbb, 1); @@ -187,7 +185,7 @@ jsonb_gt(PG_FUNCTION_ARGS) Jsonb *jbb = PG_GETARG_JSONB(1); bool res; - res = (compareJsonbSuperHeaderValue(VARDATA(jba), VARDATA(jbb)) > 0); + res = (compareJsonbContainers(&jba->root, &jbb->root) > 0); PG_FREE_IF_COPY(jba, 0); PG_FREE_IF_COPY(jbb, 1); @@ -201,7 +199,7 @@ jsonb_le(PG_FUNCTION_ARGS) Jsonb *jbb = PG_GETARG_JSONB(1); bool res; - res = (compareJsonbSuperHeaderValue(VARDATA(jba), VARDATA(jbb)) <= 0); + res = (compareJsonbContainers(&jba->root, &jbb->root) <= 0); PG_FREE_IF_COPY(jba, 0); PG_FREE_IF_COPY(jbb, 1); @@ -215,7 +213,7 @@ jsonb_ge(PG_FUNCTION_ARGS) Jsonb *jbb = PG_GETARG_JSONB(1); bool res; - res = (compareJsonbSuperHeaderValue(VARDATA(jba), VARDATA(jbb)) >= 0); + res = (compareJsonbContainers(&jba->root, &jbb->root) >= 0); PG_FREE_IF_COPY(jba, 0); PG_FREE_IF_COPY(jbb, 1); @@ -229,7 +227,7 @@ jsonb_eq(PG_FUNCTION_ARGS) Jsonb *jbb = PG_GETARG_JSONB(1); bool res; - res = (compareJsonbSuperHeaderValue(VARDATA(jba), VARDATA(jbb)) == 0); + res = (compareJsonbContainers(&jba->root, &jbb->root) == 0); PG_FREE_IF_COPY(jba, 0); PG_FREE_IF_COPY(jbb, 1); @@ -243,7 +241,7 @@ jsonb_cmp(PG_FUNCTION_ARGS) Jsonb *jbb = PG_GETARG_JSONB(1); int res; - res = compareJsonbSuperHeaderValue(VARDATA(jba), VARDATA(jbb)); + res = compareJsonbContainers(&jba->root, &jbb->root); PG_FREE_IF_COPY(jba, 0); PG_FREE_IF_COPY(jbb, 1); @@ -265,7 +263,7 @@ jsonb_hash(PG_FUNCTION_ARGS) if (JB_ROOT_COUNT(jb) == 0) PG_RETURN_INT32(0); - it = JsonbIteratorInit(VARDATA(jb)); + it = JsonbIteratorInit(&jb->root); while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) { diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c index 411144618d6..f6d6fab74e8 100644 --- a/src/backend/utils/adt/jsonb_util.c +++ b/src/backend/utils/adt/jsonb_util.c @@ -1,7 +1,7 @@ /*------------------------------------------------------------------------- * * jsonb_util.c - * Utilities for jsonb datatype + * converting between Jsonb and JsonbValues, and iterating. * * Copyright (c) 2014, PostgreSQL Global Development Group * @@ -15,7 +15,6 @@ #include "access/hash.h" #include "catalog/pg_collation.h" -#include "catalog/pg_type.h" #include "miscadmin.h" #include "utils/builtins.h" #include "utils/jsonb.h" @@ -38,49 +37,30 @@ JENTRY_POSMASK)) /* - * State used while converting an arbitrary JsonbValue into a Jsonb value - * (4-byte varlena uncompressed representation of a Jsonb) - * - * ConvertLevel: Bookkeeping around particular level when converting. - */ -typedef struct convertLevel -{ - uint32 i; /* Iterates once per element, or once per pair */ - uint32 *header; /* Pointer to current container header */ - JEntry *meta; /* This level's metadata */ - char *begin; /* Pointer into convertState.buffer */ -} convertLevel; - -/* - * convertState: Overall bookkeeping state for conversion + * convertState: a resizeable buffer used when constructing a Jsonb datum */ -typedef struct convertState +typedef struct { - /* Preallocated buffer in which to form varlena/Jsonb value */ - Jsonb *buffer; - /* Pointer into buffer */ - char *ptr; - - /* State for */ - convertLevel *allState, /* Overall state array */ - *contPtr; /* Cur container pointer (in allState) */ - - /* Current size of buffer containing allState array */ - Size levelSz; - + char *buffer; + int len; + int allocatedsz; } convertState; +static void fillJsonbValue(JEntry *entry, char *payload_base, JsonbValue *result); static int compareJsonbScalarValue(JsonbValue *a, JsonbValue *b); static int lexicalCompareJsonbStringValue(const void *a, const void *b); -static Size convertJsonb(JsonbValue *val, Jsonb *buffer); -static inline short addPaddingInt(convertState *cstate); -static void walkJsonbValueConversion(JsonbValue *val, convertState *cstate, - uint32 nestlevel); -static void putJsonbValueConversion(convertState *cstate, JsonbValue *val, - uint32 flags, uint32 level); -static void putScalarConversion(convertState *cstate, JsonbValue *scalarVal, - uint32 level, uint32 i); -static void iteratorFromContainerBuf(JsonbIterator *it, char *buffer); +static Jsonb *convertToJsonb(JsonbValue *val); +static void convertJsonbValue(convertState *buffer, JEntry *header, JsonbValue *val, int level); +static void convertJsonbArray(convertState *buffer, JEntry *header, JsonbValue *val, int level); +static void convertJsonbObject(convertState *buffer, JEntry *header, JsonbValue *val, int level); +static void convertJsonbScalar(convertState *buffer, JEntry *header, JsonbValue *scalarVal); + +static int reserveFromBuffer(convertState *buffer, int len); +static void appendToBuffer(convertState *buffer, char *data, int len); +static void copyToBuffer(convertState *buffer, int offset, char *data, int len); +static short padBufferToInt(convertState *buffer); + +static void iteratorFromContainer(JsonbIterator *it, JsonbContainer *container); static bool formIterIsContainer(JsonbIterator **it, JsonbValue *val, JEntry *ent, bool skipNested); static JsonbIterator *freeAndGetParent(JsonbIterator *it); @@ -91,7 +71,6 @@ static void appendElement(JsonbParseState *pstate, JsonbValue *scalarVal); static int lengthCompareJsonbStringValue(const void *a, const void *b, void *arg); static int lengthCompareJsonbPair(const void *a, const void *b, void *arg); static void uniqueifyJsonbObject(JsonbValue *object); -static void uniqueifyJsonbArray(JsonbValue *array); /* * Turn an in-memory JsonbValue into a Jsonb for on-disk storage. @@ -110,7 +89,6 @@ Jsonb * JsonbValueToJsonb(JsonbValue *val) { Jsonb *out; - Size sz; if (IsAJsonbScalar(val)) { @@ -127,17 +105,11 @@ JsonbValueToJsonb(JsonbValue *val) pushJsonbValue(&pstate, WJB_ELEM, val); res = pushJsonbValue(&pstate, WJB_END_ARRAY, NULL); - out = palloc(VARHDRSZ + res->estSize); - sz = convertJsonb(res, out); - Assert(sz <= res->estSize); - SET_VARSIZE(out, sz + VARHDRSZ); + out = convertToJsonb(res); } else if (val->type == jbvObject || val->type == jbvArray) { - out = palloc(VARHDRSZ + val->estSize); - sz = convertJsonb(val, out); - Assert(sz <= val->estSize); - SET_VARSIZE(out, VARHDRSZ + sz); + out = convertToJsonb(val); } else { @@ -161,7 +133,7 @@ JsonbValueToJsonb(JsonbValue *val) * memory here. */ int -compareJsonbSuperHeaderValue(JsonbSuperHeader a, JsonbSuperHeader b) +compareJsonbContainers(JsonbContainer *a, JsonbContainer *b) { JsonbIterator *ita, *itb; @@ -288,90 +260,51 @@ compareJsonbSuperHeaderValue(JsonbSuperHeader a, JsonbSuperHeader b) * * In order to proceed with the search, it is necessary for callers to have * both specified an interest in exactly one particular container type with an - * appropriate flag, as well as having the pointed-to Jsonb superheader be of + * appropriate flag, as well as having the pointed-to Jsonb container be of * one of those same container types at the top level. (Actually, we just do * whichever makes sense to save callers the trouble of figuring it out - at - * most one can make sense, because the super header either points to an array - * (possible a "raw scalar" pseudo array) or an object.) + * most one can make sense, because the container either points to an array + * (possibly a "raw scalar" pseudo array) or an object.) * * Note that we can return a jbvBinary JsonbValue if this is called on an * object, but we never do so on an array. If the caller asks to look through - * a container type that is not of the type pointed to by the superheader, + * a container type that is not of the type pointed to by the container, * immediately fall through and return NULL. If we cannot find the value, * return NULL. Otherwise, return palloc()'d copy of value. - * - * lowbound can be NULL, but if not it's used to establish a point at which to - * start searching. If the value searched for is found, then lowbound is then - * set to an offset into the array or object. Typically, this is used to - * exploit the ordering of objects to avoid redundant work, by also sorting a - * list of items to be checked using the internal sort criteria for objects - * (object pair keys), and then, when searching for the second or subsequent - * item, picking it up where we left off knowing that the second or subsequent - * item can not be at a point below the low bound set when the first was found. - * This is only useful for objects, not arrays (which have a user-defined - * order), so array superheader Jsonbs should just pass NULL. Moreover, it's - * only useful because we only match object pairs on the basis of their key, so - * presumably anyone exploiting this is only interested in matching Object keys - * with a String. lowbound is given in units of pairs, not underlying values. */ JsonbValue * -findJsonbValueFromSuperHeader(JsonbSuperHeader sheader, uint32 flags, - uint32 *lowbound, JsonbValue *key) +findJsonbValueFromContainer(JsonbContainer *container, uint32 flags, + JsonbValue *key) { - uint32 superheader = *(uint32 *) sheader; - JEntry *array = (JEntry *) (sheader + sizeof(uint32)); - int count = (superheader & JB_CMASK); + JEntry *array = container->children; + int count = (container->header & JB_CMASK); JsonbValue *result = palloc(sizeof(JsonbValue)); Assert((flags & ~(JB_FARRAY | JB_FOBJECT)) == 0); - if (flags & JB_FARRAY & superheader) + if (flags & JB_FARRAY & container->header) { - char *data = (char *) (array + (superheader & JB_CMASK)); + char *data = (char *) (array + (container->header & JB_CMASK)); int i; for (i = 0; i < count; i++) { JEntry *e = array + i; - if (JBE_ISNULL(*e) && key->type == jbvNull) - { - result->type = jbvNull; - result->estSize = sizeof(JEntry); - } - else if (JBE_ISSTRING(*e) && key->type == jbvString) - { - result->type = jbvString; - result->val.string.val = data + JBE_OFF(*e); - result->val.string.len = JBE_LEN(*e); - result->estSize = sizeof(JEntry) + result->val.string.len; - } - else if (JBE_ISNUMERIC(*e) && key->type == jbvNumeric) - { - result->type = jbvNumeric; - result->val.numeric = (Numeric) (data + INTALIGN(JBE_OFF(*e))); + fillJsonbValue(e, data, result); - result->estSize = 2 * sizeof(JEntry) + - VARSIZE_ANY(result->val.numeric); - } - else if (JBE_ISBOOL(*e) && key->type == jbvBool) + if (key->type == result->type) { - result->type = jbvBool; - result->val.boolean = JBE_ISBOOL_TRUE(*e) != 0; - result->estSize = sizeof(JEntry); + if (compareJsonbScalarValue(key, result) == 0) + return result; } - else - continue; - - if (compareJsonbScalarValue(key, result) == 0) - return result; } } - else if (flags & JB_FOBJECT & superheader) + else if (flags & JB_FOBJECT & container->header) { /* Since this is an object, account for *Pairs* of Jentrys */ - char *data = (char *) (array + (superheader & JB_CMASK) * 2); - uint32 stopLow = lowbound ? *lowbound : 0, + char *data = (char *) (array + (container->header & JB_CMASK) * 2); + uint32 stopLow = 0, stopMiddle; /* Object key past by caller must be a string */ @@ -395,7 +328,6 @@ findJsonbValueFromSuperHeader(JsonbSuperHeader sheader, uint32 flags, candidate.type = jbvString; candidate.val.string.val = data + JBE_OFF(*entry); candidate.val.string.len = JBE_LEN(*entry); - candidate.estSize = sizeof(JEntry) + candidate.val.string.len; difference = lengthCompareJsonbStringValue(&candidate, key, NULL); @@ -404,47 +336,7 @@ findJsonbValueFromSuperHeader(JsonbSuperHeader sheader, uint32 flags, /* Found our value (from key/value pair) */ JEntry *v = entry + 1; - if (lowbound) - *lowbound = stopMiddle + 1; - - if (JBE_ISNULL(*v)) - { - result->type = jbvNull; - result->estSize = sizeof(JEntry); - } - else if (JBE_ISSTRING(*v)) - { - result->type = jbvString; - result->val.string.val = data + JBE_OFF(*v); - result->val.string.len = JBE_LEN(*v); - result->estSize = sizeof(JEntry) + result->val.string.len; - } - else if (JBE_ISNUMERIC(*v)) - { - result->type = jbvNumeric; - result->val.numeric = (Numeric) (data + INTALIGN(JBE_OFF(*v))); - - result->estSize = 2 * sizeof(JEntry) + - VARSIZE_ANY(result->val.numeric); - } - else if (JBE_ISBOOL(*v)) - { - result->type = jbvBool; - result->val.boolean = JBE_ISBOOL_TRUE(*v) != 0; - result->estSize = sizeof(JEntry); - } - else - { - /* - * See header comments to understand why this never - * happens with arrays - */ - result->type = jbvBinary; - result->val.binary.data = data + INTALIGN(JBE_OFF(*v)); - result->val.binary.len = JBE_LEN(*v) - - (INTALIGN(JBE_OFF(*v)) - JBE_OFF(*v)); - result->estSize = 2 * sizeof(JEntry) + result->val.binary.len; - } + fillJsonbValue(v, data, result); return result; } @@ -456,9 +348,6 @@ findJsonbValueFromSuperHeader(JsonbSuperHeader sheader, uint32 flags, count = stopMiddle; } } - - if (lowbound) - *lowbound = stopLow; } /* Not found */ @@ -467,70 +356,80 @@ findJsonbValueFromSuperHeader(JsonbSuperHeader sheader, uint32 flags, } /* - * Get i-th value of Jsonb array from superheader. + * Get i-th value of a Jsonb array. * - * Returns palloc()'d copy of value. + * Returns palloc()'d copy of the value, or NULL if it does not exist. */ JsonbValue * -getIthJsonbValueFromSuperHeader(JsonbSuperHeader sheader, uint32 i) +getIthJsonbValueFromContainer(JsonbContainer *container, uint32 i) { - uint32 superheader = *(uint32 *) sheader; JsonbValue *result; - JEntry *array, - *e; + JEntry *e; char *data; + uint32 nelements; - result = palloc(sizeof(JsonbValue)); + if ((container->header & JB_FARRAY) == 0) + elog(ERROR, "not a jsonb array"); + + nelements = container->header & JB_CMASK; - if (i >= (superheader & JB_CMASK)) + if (i >= nelements) return NULL; - array = (JEntry *) (sheader + sizeof(uint32)); + e = &container->children[i]; - if (superheader & JB_FARRAY) - { - e = array + i; - data = (char *) (array + (superheader & JB_CMASK)); - } - else - { - elog(ERROR, "not a jsonb array"); - } + data = (char *) &container->children[nelements]; - if (JBE_ISNULL(*e)) + result = palloc(sizeof(JsonbValue)); + + fillJsonbValue(e, data, result); + + return result; +} + +/* + * Given the JEntry header, and the base address of the data that the offset + * in the JEntry refers to, fill a JsonbValue. + * + * An array or object will be returned as jbvBinary, ie. it won't be + * expanded. + */ +static void +fillJsonbValue(JEntry *entry, char *payload_base, JsonbValue *result) +{ + if (JBE_ISNULL(*entry)) { result->type = jbvNull; - result->estSize = sizeof(JEntry); } - else if (JBE_ISSTRING(*e)) + else if (JBE_ISSTRING(*entry)) { result->type = jbvString; - result->val.string.val = data + JBE_OFF(*e); - result->val.string.len = JBE_LEN(*e); - result->estSize = sizeof(JEntry) + result->val.string.len; + result->val.string.val = payload_base + JBE_OFF(*entry); + result->val.string.len = JBE_LEN(*entry); + Assert(result->val.string.len >= 0); } - else if (JBE_ISNUMERIC(*e)) + else if (JBE_ISNUMERIC(*entry)) { result->type = jbvNumeric; - result->val.numeric = (Numeric) (data + INTALIGN(JBE_OFF(*e))); - - result->estSize = 2 * sizeof(JEntry) + VARSIZE_ANY(result->val.numeric); + result->val.numeric = (Numeric) (payload_base + INTALIGN(JBE_OFF(*entry))); + } + else if (JBE_ISBOOL_TRUE(*entry)) + { + result->type = jbvBool; + result->val.boolean = true; } - else if (JBE_ISBOOL(*e)) + else if (JBE_ISBOOL_FALSE(*entry)) { result->type = jbvBool; - result->val.boolean = JBE_ISBOOL_TRUE(*e) != 0; - result->estSize = sizeof(JEntry); + result->val.boolean = false; } else { + Assert(JBE_ISCONTAINER(*entry)); result->type = jbvBinary; - result->val.binary.data = data + INTALIGN(JBE_OFF(*e)); - result->val.binary.len = JBE_LEN(*e) - (INTALIGN(JBE_OFF(*e)) - JBE_OFF(*e)); - result->estSize = result->val.binary.len + 2 * sizeof(JEntry); + result->val.binary.data = (JsonbContainer *) (payload_base + INTALIGN(JBE_OFF(*entry))); + result->val.binary.len = JBE_LEN(*entry) - (INTALIGN(JBE_OFF(*entry)) - JBE_OFF(*entry)); } - - return result; } /* @@ -547,7 +446,8 @@ getIthJsonbValueFromSuperHeader(JsonbSuperHeader sheader, uint32 i) * "raw scalar" pseudo array to append that. */ JsonbValue * -pushJsonbValue(JsonbParseState **pstate, int seq, JsonbValue *scalarVal) +pushJsonbValue(JsonbParseState **pstate, JsonbIteratorToken seq, + JsonbValue *scalarVal) { JsonbValue *result = NULL; @@ -558,7 +458,6 @@ pushJsonbValue(JsonbParseState **pstate, int seq, JsonbValue *scalarVal) *pstate = pushState(pstate); result = &(*pstate)->contVal; (*pstate)->contVal.type = jbvArray; - (*pstate)->contVal.estSize = 3 * sizeof(JEntry); (*pstate)->contVal.val.array.nElems = 0; (*pstate)->contVal.val.array.rawScalar = (scalarVal && scalarVal->val.array.rawScalar); @@ -580,7 +479,6 @@ pushJsonbValue(JsonbParseState **pstate, int seq, JsonbValue *scalarVal) *pstate = pushState(pstate); result = &(*pstate)->contVal; (*pstate)->contVal.type = jbvObject; - (*pstate)->contVal.estSize = 3 * sizeof(JEntry); (*pstate)->contVal.val.object.nPairs = 0; (*pstate)->size = 4; (*pstate)->contVal.val.object.pairs = palloc(sizeof(JsonbPair) * @@ -602,6 +500,7 @@ pushJsonbValue(JsonbParseState **pstate, int seq, JsonbValue *scalarVal) break; case WJB_END_OBJECT: uniqueifyJsonbObject(&(*pstate)->contVal); + /* fall through! */ case WJB_END_ARRAY: /* Steps here common to WJB_END_OBJECT case */ Assert(!scalarVal); @@ -635,17 +534,17 @@ pushJsonbValue(JsonbParseState **pstate, int seq, JsonbValue *scalarVal) } /* - * Given a Jsonb superheader, expand to JsonbIterator to iterate over items + * Given a JsonbContainer, expand to JsonbIterator to iterate over items * fully expanded to in-memory representation for manipulation. * * See JsonbIteratorNext() for notes on memory management. */ JsonbIterator * -JsonbIteratorInit(JsonbSuperHeader sheader) +JsonbIteratorInit(JsonbContainer *container) { JsonbIterator *it = palloc(sizeof(JsonbIterator)); - iteratorFromContainerBuf(it, sheader); + iteratorFromContainer(it, container); it->parent = NULL; return it; @@ -679,7 +578,7 @@ JsonbIteratorInit(JsonbSuperHeader sheader) * or Object element/pair buffers, since their element/pair pointers are * garbage. */ -int +JsonbIteratorToken JsonbIteratorNext(JsonbIterator **it, JsonbValue *val, bool skipNested) { JsonbIterState state; @@ -875,10 +774,9 @@ JsonbDeepContains(JsonbIterator **val, JsonbIterator **mContained) Assert(rcont == WJB_KEY); /* First, find value by key... */ - lhsVal = findJsonbValueFromSuperHeader((*val)->buffer, - JB_FOBJECT, - NULL, - &vcontained); + lhsVal = findJsonbValueFromContainer((JsonbContainer *) (*val)->buffer, + JB_FOBJECT, + &vcontained); if (!lhsVal) return false; @@ -978,10 +876,9 @@ JsonbDeepContains(JsonbIterator **val, JsonbIterator **mContained) if (IsAJsonbScalar(&vcontained)) { - if (!findJsonbValueFromSuperHeader((*val)->buffer, - JB_FARRAY, - NULL, - &vcontained)) + if (!findJsonbValueFromContainer((JsonbContainer *) (*val)->buffer, + JB_FARRAY, + &vcontained)) return false; } else @@ -1057,63 +954,6 @@ JsonbDeepContains(JsonbIterator **val, JsonbIterator **mContained) } /* - * Convert a Postgres text array to a Jsonb array, sorted and with - * de-duplicated key elements. This is used for searching an object for items - * in the array, so we enforce that the number of strings cannot exceed - * JSONB_MAX_PAIRS. - */ -JsonbValue * -arrayToJsonbSortedArray(ArrayType *array) -{ - Datum *key_datums; - bool *key_nulls; - int elem_count; - JsonbValue *result; - int i, - j; - - /* Extract data for sorting */ - deconstruct_array(array, TEXTOID, -1, false, 'i', &key_datums, &key_nulls, - &elem_count); - - if (elem_count == 0) - return NULL; - - /* - * A text array uses at least eight bytes per element, so any overflow in - * "key_count * sizeof(JsonbPair)" is small enough for palloc() to catch. - * However, credible improvements to the array format could invalidate - * that assumption. Therefore, use an explicit check rather than relying - * on palloc() to complain. - */ - if (elem_count > JSONB_MAX_PAIRS) - ereport(ERROR, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("number of array elements (%d) exceeds maximum allowed Jsonb pairs (%zu)", - elem_count, JSONB_MAX_PAIRS))); - - result = palloc(sizeof(JsonbValue)); - result->type = jbvArray; - result->val.array.rawScalar = false; - result->val.array.elems = palloc(sizeof(JsonbPair) * elem_count); - - for (i = 0, j = 0; i < elem_count; i++) - { - if (!key_nulls[i]) - { - result->val.array.elems[j].type = jbvString; - result->val.array.elems[j].val.string.val = VARDATA(key_datums[i]); - result->val.array.elems[j].val.string.len = VARSIZE(key_datums[i]) - VARHDRSZ; - j++; - } - } - result->val.array.nElems = j; - - uniqueifyJsonbArray(result); - return result; -} - -/* * Hash a JsonbValue scalar value, mixing the hash value into an existing * hash provided by the caller. * @@ -1212,331 +1052,333 @@ lexicalCompareJsonbStringValue(const void *a, const void *b) vb->val.string.len, DEFAULT_COLLATION_OID); } + /* - * Given a JsonbValue, convert to Jsonb and store in preallocated Jsonb buffer - * sufficiently large to fit the value + * Functions for manipulating the resizeable buffer used by convertJsonb and + * its subroutines. */ -static Size -convertJsonb(JsonbValue *val, Jsonb *buffer) -{ - convertState state; - Size len; - /* Should not already have binary representation */ - Assert(val->type != jbvBinary); +/* + * Rervere 'len' bytes, at the end of the buffer, enlarging it if necessary. + * Returns the offset to the reserved area. The caller is expected to copy + * the data to the reserved area later with copyToBuffer() + */ +static int +reserveFromBuffer(convertState *buffer, int len) +{ + int offset; - state.buffer = buffer; - /* Start from superheader */ - state.ptr = VARDATA(state.buffer); - state.levelSz = 8; - state.allState = palloc(sizeof(convertLevel) * state.levelSz); + /* Make more room if needed */ + if (buffer->len + len > buffer->allocatedsz) + { + buffer->allocatedsz *= 2; + buffer->buffer = repalloc(buffer->buffer, buffer->allocatedsz); + } - walkJsonbValueConversion(val, &state, 0); + /* remember current offset */ + offset = buffer->len; - len = state.ptr - VARDATA(state.buffer); + /* reserve the space */ + buffer->len += len; - Assert(len <= val->estSize); - return len; + return offset; } /* - * Walk the tree representation of Jsonb, as part of the process of converting - * a JsonbValue to a Jsonb. - * - * This high-level function takes care of recursion into sub-containers, but at - * the top level calls putJsonbValueConversion once per sequential processing - * token (in a manner similar to generic iteration). + * Copy 'len' bytes to a previously reserved area in buffer. */ static void -walkJsonbValueConversion(JsonbValue *val, convertState *cstate, - uint32 nestlevel) +copyToBuffer(convertState *buffer, int offset, char *data, int len) { - int i; + memcpy(buffer->buffer + offset, data, len); +} - check_stack_depth(); +/* + * A shorthand for reserveFromBuffer + copyToBuffer. + */ +static void +appendToBuffer(convertState *buffer, char *data, int len) +{ + int offset; - if (!val) - return; + offset = reserveFromBuffer(buffer, len); + copyToBuffer(buffer, offset, data, len); +} - switch (val->type) - { - case jbvArray: - putJsonbValueConversion(cstate, val, WJB_BEGIN_ARRAY, nestlevel); - for (i = 0; i < val->val.array.nElems; i++) - { - if (IsAJsonbScalar(&val->val.array.elems[i]) || - val->val.array.elems[i].type == jbvBinary) - putJsonbValueConversion(cstate, val->val.array.elems + i, - WJB_ELEM, nestlevel); - else - walkJsonbValueConversion(val->val.array.elems + i, cstate, - nestlevel + 1); - } - putJsonbValueConversion(cstate, val, WJB_END_ARRAY, nestlevel); +/* + * Append padding, so that the length of the StringInfo is int-aligned. + * Returns the number of padding bytes appended. + */ +static short +padBufferToInt(convertState *buffer) +{ + short padlen, + p; + int offset; - break; - case jbvObject: + padlen = INTALIGN(buffer->len) - buffer->len; - putJsonbValueConversion(cstate, val, WJB_BEGIN_OBJECT, nestlevel); - for (i = 0; i < val->val.object.nPairs; i++) - { - putJsonbValueConversion(cstate, &val->val.object.pairs[i].key, - WJB_KEY, nestlevel); - - if (IsAJsonbScalar(&val->val.object.pairs[i].value) || - val->val.object.pairs[i].value.type == jbvBinary) - putJsonbValueConversion(cstate, - &val->val.object.pairs[i].value, - WJB_VALUE, nestlevel); - else - walkJsonbValueConversion(&val->val.object.pairs[i].value, - cstate, nestlevel + 1); - } - putJsonbValueConversion(cstate, val, WJB_END_OBJECT, nestlevel); + offset = reserveFromBuffer(buffer, padlen); + for (p = 0; p < padlen; p++) + buffer->buffer[offset + p] = 0; - break; - default: - elog(ERROR, "unknown type of jsonb container"); - } + return padlen; } /* - * walkJsonbValueConversion() worker. Add padding sufficient to int-align our - * access to conversion buffer. + * Given a JsonbValue, convert to Jsonb. The result is palloc'd. */ -static inline -short -addPaddingInt(convertState *cstate) +static Jsonb * +convertToJsonb(JsonbValue *val) { - short padlen, - p; + convertState buffer; + JEntry jentry; + Jsonb *res; - padlen = INTALIGN(cstate->ptr - VARDATA(cstate->buffer)) - - (cstate->ptr - VARDATA(cstate->buffer)); + /* Should not already have binary representation */ + Assert(val->type != jbvBinary); - for (p = padlen; p > 0; p--) - { - *cstate->ptr = '\0'; - cstate->ptr++; - } + /* Allocate an output buffer. It will be enlarged as needed */ + buffer.buffer = palloc(128); + buffer.len = 0; + buffer.allocatedsz = 128; - return padlen; + /* Make room for the varlena header */ + reserveFromBuffer(&buffer, sizeof(VARHDRSZ)); + + convertJsonbValue(&buffer, &jentry, val, 0); + + /* + * Note: the JEntry of the root is not discarded. Therefore the root + * JsonbContainer struct must contain enough information to tell what + * kind of value it is. + */ + + res = (Jsonb *) buffer.buffer; + + SET_VARSIZE(res, buffer.len); + + return res; } /* - * walkJsonbValueConversion() worker. + * Subroutine of convertJsonb: serialize a single JsonbValue into buffer. * + * The JEntry header for this node is returned in *header. It is filled in + * with the length of this value, but if + * it is stored in an array or an object (which is always, except for the root + * node), it is the caller's responsibility to adjust it with the offset + * within the container. + * + * If the value is an array or an object, this recurses. 'level' is only used + * for debugging purposes. + * As part of the process of converting an arbitrary JsonbValue to a Jsonb, - * copy over an arbitrary individual JsonbValue. This function may copy any - * type of value, even containers (Objects/arrays). However, it is not - * responsible for recursive aspects of walking the tree (so only top-level - * Object/array details are handled). + * serialize and copy a scalar value into buffer. * - * No details about their keys/values/elements are handled recursively - - * rather, the function is called as required for the start of an Object/Array, - * and the end (i.e. there is one call per sequential processing WJB_* token). + * This is a worker function for putJsonbValueConversion() (itself a worker for + * walkJsonbValueConversion()). It handles the details with regard to Jentry + * metadata peculiar to each scalar type. + * + * It is the callers responsibility to shift the offset if this is stored + * in an array or object. */ static void -putJsonbValueConversion(convertState *cstate, JsonbValue *val, uint32 flags, - uint32 level) +convertJsonbValue(convertState *buffer, JEntry *header, JsonbValue *val, int level) { - if (level == cstate->levelSz) - { - cstate->levelSz *= 2; - cstate->allState = repalloc(cstate->allState, - sizeof(convertLevel) * cstate->levelSz); - } - - cstate->contPtr = cstate->allState + level; + check_stack_depth(); - if (flags & (WJB_BEGIN_ARRAY | WJB_BEGIN_OBJECT)) - { - Assert(((flags & WJB_BEGIN_ARRAY) && val->type == jbvArray) || - ((flags & WJB_BEGIN_OBJECT) && val->type == jbvObject)); + if (!val) + return; - /* Initialize pointer into conversion buffer at this level */ - cstate->contPtr->begin = cstate->ptr; + if (IsAJsonbScalar(val) || val->type == jbvBinary) + convertJsonbScalar(buffer, header, val); + else if (val->type == jbvArray) + convertJsonbArray(buffer, header, val, level); + else if (val->type == jbvObject) + convertJsonbObject(buffer, header, val, level); + else + elog(ERROR, "unknown type of jsonb container"); +} - addPaddingInt(cstate); +static void +convertJsonbArray(convertState *buffer, JEntry *pheader, JsonbValue *val, int level) +{ + int offset; + int metaoffset; + int i; + int totallen; + uint32 header; - /* Initialize everything else at this level */ - cstate->contPtr->header = (uint32 *) cstate->ptr; - /* Advance past header */ - cstate->ptr += sizeof(uint32); - cstate->contPtr->meta = (JEntry *) cstate->ptr; - cstate->contPtr->i = 0; + /* Initialize pointer into conversion buffer at this level */ + offset = buffer->len; - if (val->type == jbvArray) - { - *cstate->contPtr->header = val->val.array.nElems | JB_FARRAY; - cstate->ptr += sizeof(JEntry) * val->val.array.nElems; + padBufferToInt(buffer); - if (val->val.array.rawScalar) - { - Assert(val->val.array.nElems == 1); - Assert(level == 0); - *cstate->contPtr->header |= JB_FSCALAR; - } - } - else - { - *cstate->contPtr->header = val->val.object.nPairs | JB_FOBJECT; - cstate->ptr += sizeof(JEntry) * val->val.object.nPairs * 2; - } - } - else if (flags & WJB_ELEM) + /* + * Construct the header Jentry, stored in the beginning of the variable- + * length payload. + */ + header = val->val.array.nElems | JB_FARRAY; + if (val->val.array.rawScalar) { - putScalarConversion(cstate, val, level, cstate->contPtr->i); - cstate->contPtr->i++; + Assert(val->val.array.nElems == 1); + Assert(level == 0); + header |= JB_FSCALAR; } - else if (flags & WJB_KEY) - { - Assert(val->type == jbvString); - putScalarConversion(cstate, val, level, cstate->contPtr->i * 2); - } - else if (flags & WJB_VALUE) + appendToBuffer(buffer, (char *) &header, sizeof(uint32)); + /* reserve space for the JEntries of the elements. */ + metaoffset = reserveFromBuffer(buffer, sizeof(JEntry) * val->val.array.nElems); + + totallen = 0; + for (i = 0; i < val->val.array.nElems; i++) { - putScalarConversion(cstate, val, level, cstate->contPtr->i * 2 + 1); - cstate->contPtr->i++; + JsonbValue *elem = &val->val.array.elems[i]; + int len; + JEntry meta; + + convertJsonbValue(buffer, &meta, elem, level + 1); + len = meta & JENTRY_POSMASK; + totallen += len; + + if (totallen > JENTRY_POSMASK) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("total size of jsonb array elements exceeds the maximum of %u bytes", + JENTRY_POSMASK))); + + if (i == 0) + meta |= JENTRY_ISFIRST; + else + meta = (meta & ~JENTRY_POSMASK) | totallen; + copyToBuffer(buffer, metaoffset, (char *) &meta, sizeof(JEntry)); + metaoffset += sizeof(JEntry); } - else if (flags & (WJB_END_ARRAY | WJB_END_OBJECT)) - { - convertLevel *prevPtr; /* Prev container pointer */ - uint32 len, - i; - Assert(((flags & WJB_END_ARRAY) && val->type == jbvArray) || - ((flags & WJB_END_OBJECT) && val->type == jbvObject)); + totallen = buffer->len - offset; - if (level == 0) - return; + /* Initialize the header of this node, in the container's JEntry array */ + *pheader = JENTRY_ISCONTAINER | totallen; +} - len = cstate->ptr - (char *) cstate->contPtr->begin; +static void +convertJsonbObject(convertState *buffer, JEntry *pheader, JsonbValue *val, int level) +{ + uint32 header; + int offset; + int metaoffset; + int i; + int totallen; - prevPtr = cstate->contPtr - 1; + /* Initialize pointer into conversion buffer at this level */ + offset = buffer->len; - if (*prevPtr->header & JB_FARRAY) - { - i = prevPtr->i; + padBufferToInt(buffer); - prevPtr->meta[i].header = JENTRY_ISNEST; + /* Initialize header */ + header = val->val.object.nPairs | JB_FOBJECT; + appendToBuffer(buffer, (char *) &header, sizeof(uint32)); - if (i == 0) - prevPtr->meta[0].header |= JENTRY_ISFIRST | len; - else - prevPtr->meta[i].header |= - (prevPtr->meta[i - 1].header & JENTRY_POSMASK) + len; - } - else if (*prevPtr->header & JB_FOBJECT) - { - i = 2 * prevPtr->i + 1; /* Value, not key */ + /* reserve space for the JEntries of the keys and values */ + metaoffset = reserveFromBuffer(buffer, sizeof(JEntry) * val->val.object.nPairs * 2); - prevPtr->meta[i].header = JENTRY_ISNEST; + totallen = 0; + for (i = 0; i < val->val.object.nPairs; i++) + { + JsonbPair *pair = &val->val.object.pairs[i]; + int len; + JEntry meta; - prevPtr->meta[i].header |= - (prevPtr->meta[i - 1].header & JENTRY_POSMASK) + len; - } - else - { - elog(ERROR, "invalid jsonb container type"); - } + /* put key */ + convertJsonbScalar(buffer, &meta, &pair->key); - Assert(cstate->ptr - cstate->contPtr->begin <= val->estSize); - prevPtr->i++; - } - else - { - elog(ERROR, "unknown flag encountered during jsonb tree walk"); + len = meta & JENTRY_POSMASK; + totallen += len; + + if (totallen > JENTRY_POSMASK) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("total size of jsonb array elements exceeds the maximum of %u bytes", + JENTRY_POSMASK))); + + if (i == 0) + meta |= JENTRY_ISFIRST; + else + meta = (meta & ~JENTRY_POSMASK) | totallen; + copyToBuffer(buffer, metaoffset, (char *) &meta, sizeof(JEntry)); + metaoffset += sizeof(JEntry); + + convertJsonbValue(buffer, &meta, &pair->value, level); + len = meta & JENTRY_POSMASK; + totallen += len; + + if (totallen > JENTRY_POSMASK) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("total size of jsonb array elements exceeds the maximum of %u bytes", + JENTRY_POSMASK))); + + meta = (meta & ~JENTRY_POSMASK) | totallen; + copyToBuffer(buffer, metaoffset, (char *) &meta, sizeof(JEntry)); + metaoffset += sizeof(JEntry); } + + totallen = buffer->len - offset; + + *pheader = JENTRY_ISCONTAINER | totallen; } -/* - * As part of the process of converting an arbitrary JsonbValue to a Jsonb, - * serialize and copy a scalar value into buffer. - * - * This is a worker function for putJsonbValueConversion() (itself a worker for - * walkJsonbValueConversion()). It handles the details with regard to Jentry - * metadata peculiar to each scalar type. - */ static void -putScalarConversion(convertState *cstate, JsonbValue *scalarVal, uint32 level, - uint32 i) +convertJsonbScalar(convertState *buffer, JEntry *jentry, JsonbValue *scalarVal) { int numlen; short padlen; - cstate->contPtr = cstate->allState + level; - - if (i == 0) - cstate->contPtr->meta[0].header = JENTRY_ISFIRST; - else - cstate->contPtr->meta[i].header = 0; - switch (scalarVal->type) { case jbvNull: - cstate->contPtr->meta[i].header |= JENTRY_ISNULL; - - if (i > 0) - cstate->contPtr->meta[i].header |= - cstate->contPtr->meta[i - 1].header & JENTRY_POSMASK; + *jentry = JENTRY_ISNULL; break; + case jbvString: - memcpy(cstate->ptr, scalarVal->val.string.val, scalarVal->val.string.len); - cstate->ptr += scalarVal->val.string.len; + appendToBuffer(buffer, scalarVal->val.string.val, scalarVal->val.string.len); - if (i == 0) - cstate->contPtr->meta[0].header |= scalarVal->val.string.len; - else - cstate->contPtr->meta[i].header |= - (cstate->contPtr->meta[i - 1].header & JENTRY_POSMASK) + - scalarVal->val.string.len; + *jentry = scalarVal->val.string.len; break; + case jbvNumeric: numlen = VARSIZE_ANY(scalarVal->val.numeric); - padlen = addPaddingInt(cstate); + padlen = padBufferToInt(buffer); - memcpy(cstate->ptr, scalarVal->val.numeric, numlen); - cstate->ptr += numlen; + appendToBuffer(buffer, (char *) scalarVal->val.numeric, numlen); - cstate->contPtr->meta[i].header |= JENTRY_ISNUMERIC; - if (i == 0) - cstate->contPtr->meta[0].header |= padlen + numlen; - else - cstate->contPtr->meta[i].header |= - (cstate->contPtr->meta[i - 1].header & JENTRY_POSMASK) - + padlen + numlen; + *jentry = JENTRY_ISNUMERIC | (padlen + numlen); break; - case jbvBool: - cstate->contPtr->meta[i].header |= (scalarVal->val.boolean) ? - JENTRY_ISTRUE : JENTRY_ISFALSE; - if (i > 0) - cstate->contPtr->meta[i].header |= - cstate->contPtr->meta[i - 1].header & JENTRY_POSMASK; + case jbvBool: + *jentry = (scalarVal->val.boolean) ? + JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE; break; + default: elog(ERROR, "invalid jsonb scalar type"); } } /* - * Given superheader pointer into buffer, initialize iterator. Must be a - * container type. + * Initialize an iterator for iterating all elements in a container. */ static void -iteratorFromContainerBuf(JsonbIterator *it, JsonbSuperHeader sheader) +iteratorFromContainer(JsonbIterator *it, JsonbContainer *container) { - uint32 superheader = *(uint32 *) sheader; - - it->containerType = superheader & (JB_FARRAY | JB_FOBJECT); - it->nElems = superheader & JB_CMASK; - it->buffer = sheader; + it->containerType = container->header & (JB_FARRAY | JB_FOBJECT); + it->nElems = container->header & JB_CMASK; + it->buffer = (char *) container; /* Array starts just after header */ - it->meta = (JEntry *) (sheader + sizeof(uint32)); + it->meta = container->children; it->state = jbi_start; switch (it->containerType) @@ -1544,7 +1386,7 @@ iteratorFromContainerBuf(JsonbIterator *it, JsonbSuperHeader sheader) case JB_FARRAY: it->dataProper = (char *) it->meta + it->nElems * sizeof(JEntry); - it->isScalar = (superheader & JB_FSCALAR) != 0; + it->isScalar = (container->header & JB_FSCALAR) != 0; /* This is either a "raw scalar", or an array */ Assert(!it->isScalar || it->nElems == 1); break; @@ -1584,60 +1426,21 @@ static bool formIterIsContainer(JsonbIterator **it, JsonbValue *val, JEntry *ent, bool skipNested) { - if (JBE_ISNULL(*ent)) - { - val->type = jbvNull; - val->estSize = sizeof(JEntry); + fillJsonbValue(ent, (*it)->dataProper, val); + if (IsAJsonbScalar(val) || skipNested) return false; - } - else if (JBE_ISSTRING(*ent)) - { - val->type = jbvString; - val->val.string.val = (*it)->dataProper + JBE_OFF(*ent); - val->val.string.len = JBE_LEN(*ent); - val->estSize = sizeof(JEntry) + val->val.string.len; - - return false; - } - else if (JBE_ISNUMERIC(*ent)) - { - val->type = jbvNumeric; - val->val.numeric = (Numeric) ((*it)->dataProper + INTALIGN(JBE_OFF(*ent))); - - val->estSize = 2 * sizeof(JEntry) + VARSIZE_ANY(val->val.numeric); - - return false; - } - else if (JBE_ISBOOL(*ent)) - { - val->type = jbvBool; - val->val.boolean = JBE_ISBOOL_TRUE(*ent) != 0; - val->estSize = sizeof(JEntry); - - return false; - } - else if (skipNested) - { - val->type = jbvBinary; - val->val.binary.data = (*it)->dataProper + INTALIGN(JBE_OFF(*ent)); - val->val.binary.len = JBE_LEN(*ent) - (INTALIGN(JBE_OFF(*ent)) - JBE_OFF(*ent)); - val->estSize = val->val.binary.len + 2 * sizeof(JEntry); - - return false; - } else { /* - * Must be container type, so setup caller's iterator to point to + * It's a container type, so setup caller's iterator to point to * that, and return indication of that. * * Get child iterator. */ JsonbIterator *child = palloc(sizeof(JsonbIterator)); - iteratorFromContainerBuf(child, - (*it)->dataProper + INTALIGN(JBE_OFF(*ent))); + iteratorFromContainer(child, val->val.binary.data); child->parent = *it; *it = child; @@ -1697,8 +1500,6 @@ appendKey(JsonbParseState *pstate, JsonbValue *string) object->val.object.pairs[object->val.object.nPairs].key = *string; object->val.object.pairs[object->val.object.nPairs].order = object->val.object.nPairs; - - object->estSize += string->estSize; } /* @@ -1713,7 +1514,6 @@ appendValue(JsonbParseState *pstate, JsonbValue *scalarVal) Assert(object->type == jbvObject); object->val.object.pairs[object->val.object.nPairs++].value = *scalarVal; - object->estSize += scalarVal->estSize; } /* @@ -1740,7 +1540,6 @@ appendElement(JsonbParseState *pstate, JsonbValue *scalarVal) } array->val.array.elems[array->val.array.nElems++] = *scalarVal; - array->estSize += scalarVal->estSize; } /* @@ -1835,11 +1634,7 @@ uniqueifyJsonbObject(JsonbValue *object) while (ptr - object->val.object.pairs < object->val.object.nPairs) { /* Avoid copying over duplicate */ - if (lengthCompareJsonbStringValue(ptr, res, NULL) == 0) - { - object->estSize -= ptr->key.estSize + ptr->value.estSize; - } - else + if (lengthCompareJsonbStringValue(ptr, res, NULL) != 0) { res++; if (ptr != res) @@ -1851,45 +1646,3 @@ uniqueifyJsonbObject(JsonbValue *object) object->val.object.nPairs = res + 1 - object->val.object.pairs; } } - -/* - * Sort and unique-ify JsonbArray. - * - * Sorting uses internal ordering. - */ -static void -uniqueifyJsonbArray(JsonbValue *array) -{ - bool hasNonUniq = false; - - Assert(array->type == jbvArray); - - /* - * Actually sort values, determining if any were equal on the basis of - * full binary equality (rather than just having the same string length). - */ - if (array->val.array.nElems > 1) - qsort_arg(array->val.array.elems, array->val.array.nElems, - sizeof(JsonbValue), lengthCompareJsonbStringValue, - &hasNonUniq); - - if (hasNonUniq) - { - JsonbValue *ptr = array->val.array.elems + 1, - *res = array->val.array.elems; - - while (ptr - array->val.array.elems < array->val.array.nElems) - { - /* Avoid copying over duplicate */ - if (lengthCompareJsonbStringValue(ptr, res, NULL) != 0) - { - res++; - *res = *ptr; - } - - ptr++; - } - - array->val.array.nElems = res + 1 - array->val.array.elems; - } -} diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c index 6b1ce9b3a9f..b67eb6555c1 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -106,7 +106,7 @@ static inline Datum populate_recordset_worker(FunctionCallInfo fcinfo, bool have_record_arg); /* Worker that takes care of common setup for us */ -static JsonbValue *findJsonbValueFromSuperHeaderLen(JsonbSuperHeader sheader, +static JsonbValue *findJsonbValueFromContainerLen(JsonbContainer *container, uint32 flags, char *key, uint32 keylen); @@ -286,7 +286,7 @@ jsonb_object_keys(PG_FUNCTION_ARGS) state->sent_count = 0; state->result = palloc(state->result_size * sizeof(char *)); - it = JsonbIteratorInit(VARDATA_ANY(jb)); + it = JsonbIteratorInit(&jb->root); while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE) { @@ -484,7 +484,7 @@ jsonb_object_field(PG_FUNCTION_ARGS) Assert(JB_ROOT_IS_OBJECT(jb)); - it = JsonbIteratorInit(VARDATA_ANY(jb)); + it = JsonbIteratorInit(&jb->root); while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE) { @@ -545,7 +545,7 @@ jsonb_object_field_text(PG_FUNCTION_ARGS) Assert(JB_ROOT_IS_OBJECT(jb)); - it = JsonbIteratorInit(VARDATA_ANY(jb)); + it = JsonbIteratorInit(&jb->root); while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE) { @@ -580,7 +580,7 @@ jsonb_object_field_text(PG_FUNCTION_ARGS) StringInfo jtext = makeStringInfo(); Jsonb *tjb = JsonbValueToJsonb(&v); - (void) JsonbToCString(jtext, VARDATA(tjb), -1); + (void) JsonbToCString(jtext, &tjb->root , -1); result = cstring_to_text_with_len(jtext->data, jtext->len); } PG_RETURN_TEXT_P(result); @@ -628,7 +628,7 @@ jsonb_array_element(PG_FUNCTION_ARGS) Assert(JB_ROOT_IS_ARRAY(jb)); - it = JsonbIteratorInit(VARDATA_ANY(jb)); + it = JsonbIteratorInit(&jb->root); while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE) { @@ -682,7 +682,7 @@ jsonb_array_element_text(PG_FUNCTION_ARGS) Assert(JB_ROOT_IS_ARRAY(jb)); - it = JsonbIteratorInit(VARDATA_ANY(jb)); + it = JsonbIteratorInit(&jb->root); while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE) { @@ -711,7 +711,7 @@ jsonb_array_element_text(PG_FUNCTION_ARGS) StringInfo jtext = makeStringInfo(); Jsonb *tjb = JsonbValueToJsonb(&v); - (void) JsonbToCString(jtext, VARDATA(tjb), -1); + (void) JsonbToCString(jtext, &tjb->root, -1); result = cstring_to_text_with_len(jtext->data, jtext->len); } PG_RETURN_TEXT_P(result); @@ -1155,7 +1155,7 @@ get_jsonb_path_all(FunctionCallInfo fcinfo, bool as_text) have_array = false; JsonbValue *jbvp = NULL; JsonbValue tv; - JsonbSuperHeader superHeader; + JsonbContainer *container; if (array_contains_nulls(path)) ereport(ERROR, @@ -1170,15 +1170,15 @@ get_jsonb_path_all(FunctionCallInfo fcinfo, bool as_text) else if (JB_ROOT_IS_ARRAY(jb) && !JB_ROOT_IS_SCALAR(jb)) have_array = true; - superHeader = (JsonbSuperHeader) VARDATA(jb); + container = &jb->root; for (i = 0; i < npath; i++) { if (have_object) { - jbvp = findJsonbValueFromSuperHeaderLen(superHeader, - JB_FOBJECT, - VARDATA_ANY(pathtext[i]), + jbvp = findJsonbValueFromContainerLen(container, + JB_FOBJECT, + VARDATA_ANY(pathtext[i]), VARSIZE_ANY_EXHDR(pathtext[i])); } else if (have_array) @@ -1192,7 +1192,7 @@ get_jsonb_path_all(FunctionCallInfo fcinfo, bool as_text) if (*endptr != '\0' || lindex > INT_MAX || lindex < 0) PG_RETURN_NULL(); index = (uint32) lindex; - jbvp = getIthJsonbValueFromSuperHeader(superHeader, index); + jbvp = getIthJsonbValueFromContainer(container, index); } else { @@ -1210,11 +1210,11 @@ get_jsonb_path_all(FunctionCallInfo fcinfo, bool as_text) if (jbvp->type == jbvBinary) { - JsonbIterator *it = JsonbIteratorInit(jbvp->val.binary.data); + JsonbIterator *it = JsonbIteratorInit((JsonbContainer *) jbvp->val.binary.data); int r; r = JsonbIteratorNext(&it, &tv, true); - superHeader = (JsonbSuperHeader) jbvp->val.binary.data; + container = (JsonbContainer *) jbvp->val.binary.data; have_object = r == WJB_BEGIN_OBJECT; have_array = r == WJB_BEGIN_ARRAY; } @@ -1238,7 +1238,7 @@ get_jsonb_path_all(FunctionCallInfo fcinfo, bool as_text) if (as_text) { PG_RETURN_TEXT_P(cstring_to_text(JsonbToCString(NULL, - VARDATA(res), + &res->root, VARSIZE(res)))); } else @@ -1428,7 +1428,7 @@ each_worker_jsonb(FunctionCallInfo fcinfo, bool as_text) ALLOCSET_DEFAULT_MAXSIZE); - it = JsonbIteratorInit(VARDATA_ANY(jb)); + it = JsonbIteratorInit(&jb->root); while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE) { @@ -1477,7 +1477,7 @@ each_worker_jsonb(FunctionCallInfo fcinfo, bool as_text) StringInfo jtext = makeStringInfo(); Jsonb *jb = JsonbValueToJsonb(&v); - (void) JsonbToCString(jtext, VARDATA(jb), 2 * v.estSize); + (void) JsonbToCString(jtext, &jb->root, 0); sv = cstring_to_text_with_len(jtext->data, jtext->len); } @@ -1753,7 +1753,7 @@ elements_worker_jsonb(FunctionCallInfo fcinfo, bool as_text) ALLOCSET_DEFAULT_MAXSIZE); - it = JsonbIteratorInit(VARDATA_ANY(jb)); + it = JsonbIteratorInit(&jb->root); while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE) { @@ -1797,7 +1797,7 @@ elements_worker_jsonb(FunctionCallInfo fcinfo, bool as_text) StringInfo jtext = makeStringInfo(); Jsonb *jb = JsonbValueToJsonb(&v); - (void) JsonbToCString(jtext, VARDATA(jb), 2 * v.estSize); + (void) JsonbToCString(jtext, &jb->root, 0); sv = cstring_to_text_with_len(jtext->data, jtext->len); } @@ -2219,8 +2219,8 @@ populate_record_worker(FunctionCallInfo fcinfo, bool have_record_arg) { char *key = NameStr(tupdesc->attrs[i]->attname); - v = findJsonbValueFromSuperHeaderLen(VARDATA(jb), JB_FOBJECT, key, - strlen(key)); + v = findJsonbValueFromContainerLen(&jb->root, JB_FOBJECT, key, + strlen(key)); } /* @@ -2282,7 +2282,7 @@ populate_record_worker(FunctionCallInfo fcinfo, bool have_record_arg) (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("cannot populate with a nested object unless use_json_as_text is true"))); else if (v->type == jbvBinary) - s = JsonbToCString(NULL, v->val.binary.data, v->val.binary.len); + s = JsonbToCString(NULL, (JsonbContainer *) v->val.binary.data, v->val.binary.len); else elog(ERROR, "invalid jsonb type"); } @@ -2529,8 +2529,8 @@ make_row_from_rec_and_jsonb(Jsonb *element, PopulateRecordsetState *state) key = NameStr(tupdesc->attrs[i]->attname); - v = findJsonbValueFromSuperHeaderLen(VARDATA(element), JB_FOBJECT, - key, strlen(key)); + v = findJsonbValueFromContainerLen(&element->root, JB_FOBJECT, + key, strlen(key)); /* * We can't just skip here if the key wasn't found since we might have @@ -2582,7 +2582,7 @@ make_row_from_rec_and_jsonb(Jsonb *element, PopulateRecordsetState *state) (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("cannot populate with a nested object unless use_json_as_text is true"))); else if (v->type == jbvBinary) - s = JsonbToCString(NULL, v->val.binary.data, v->val.binary.len); + s = JsonbToCString(NULL, (JsonbContainer *) v->val.binary.data, v->val.binary.len); else elog(ERROR, "invalid jsonb type"); @@ -2750,7 +2750,7 @@ populate_recordset_worker(FunctionCallInfo fcinfo, bool have_record_arg) (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("cannot call jsonb_populate_recordset on non-array"))); - it = JsonbIteratorInit(VARDATA_ANY(jb)); + it = JsonbIteratorInit(&jb->root); while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE) { @@ -3019,11 +3019,11 @@ populate_recordset_object_field_end(void *state, char *fname, bool isnull) } /* - * findJsonbValueFromSuperHeader() wrapper that sets up JsonbValue key string. + * findJsonbValueFromContainer() wrapper that sets up JsonbValue key string. */ static JsonbValue * -findJsonbValueFromSuperHeaderLen(JsonbSuperHeader sheader, uint32 flags, - char *key, uint32 keylen) +findJsonbValueFromContainerLen(JsonbContainer *container, uint32 flags, + char *key, uint32 keylen) { JsonbValue k; @@ -3031,5 +3031,5 @@ findJsonbValueFromSuperHeaderLen(JsonbSuperHeader sheader, uint32 flags, k.val.string.val = key; k.val.string.len = keylen; - return findJsonbValueFromSuperHeader(sheader, flags, NULL, &k); + return findJsonbValueFromContainer(container, flags, &k); } diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h index dea64ad7805..fc746c8b742 100644 --- a/src/include/utils/jsonb.h +++ b/src/include/utils/jsonb.h @@ -16,60 +16,18 @@ #include "utils/array.h" #include "utils/numeric.h" -/* - * JB_CMASK is used to extract count of items - * - * It's not possible to get more than 2^28 items into an Jsonb. - */ -#define JB_CMASK 0x0FFFFFFF - -#define JB_FSCALAR 0x10000000 -#define JB_FOBJECT 0x20000000 -#define JB_FARRAY 0x40000000 - -/* Get information on varlena Jsonb */ -#define JB_ROOT_COUNT(jbp_) ( *(uint32*) VARDATA(jbp_) & JB_CMASK) -#define JB_ROOT_IS_SCALAR(jbp_) ( *(uint32*) VARDATA(jbp_) & JB_FSCALAR) -#define JB_ROOT_IS_OBJECT(jbp_) ( *(uint32*) VARDATA(jbp_) & JB_FOBJECT) -#define JB_ROOT_IS_ARRAY(jbp_) ( *(uint32*) VARDATA(jbp_) & JB_FARRAY) - -/* Jentry macros */ -#define JENTRY_POSMASK 0x0FFFFFFF -#define JENTRY_ISFIRST 0x80000000 -#define JENTRY_TYPEMASK (~(JENTRY_POSMASK | JENTRY_ISFIRST)) -#define JENTRY_ISSTRING 0x00000000 -#define JENTRY_ISNUMERIC 0x10000000 -#define JENTRY_ISNEST 0x20000000 -#define JENTRY_ISNULL 0x40000000 -#define JENTRY_ISBOOL (JENTRY_ISNUMERIC | JENTRY_ISNEST) -#define JENTRY_ISFALSE JENTRY_ISBOOL -#define JENTRY_ISTRUE (JENTRY_ISBOOL | 0x40000000) -/* Note possible multiple evaluations, also access to prior array element */ -#define JBE_ISFIRST(je_) (((je_).header & JENTRY_ISFIRST) != 0) -#define JBE_ISSTRING(je_) (((je_).header & JENTRY_TYPEMASK) == JENTRY_ISSTRING) -#define JBE_ISNUMERIC(je_) (((je_).header & JENTRY_TYPEMASK) == JENTRY_ISNUMERIC) -#define JBE_ISNEST(je_) (((je_).header & JENTRY_TYPEMASK) == JENTRY_ISNEST) -#define JBE_ISNULL(je_) (((je_).header & JENTRY_TYPEMASK) == JENTRY_ISNULL) -#define JBE_ISBOOL(je_) (((je_).header & JENTRY_TYPEMASK & JENTRY_ISBOOL) == JENTRY_ISBOOL) -#define JBE_ISBOOL_TRUE(je_) (((je_).header & JENTRY_TYPEMASK) == JENTRY_ISTRUE) -#define JBE_ISBOOL_FALSE(je_) (JBE_ISBOOL(je_) && !JBE_ISBOOL_TRUE(je_)) - -/* Get offset for Jentry */ -#define JBE_ENDPOS(je_) ((je_).header & JENTRY_POSMASK) -#define JBE_OFF(je_) (JBE_ISFIRST(je_) ? 0 : JBE_ENDPOS((&(je_))[-1])) -#define JBE_LEN(je_) (JBE_ISFIRST(je_) ? \ - JBE_ENDPOS(je_) \ - : JBE_ENDPOS(je_) - JBE_ENDPOS((&(je_))[-1])) - -/* Flags indicating a stage of sequential Jsonb processing */ -#define WJB_DONE 0x000 -#define WJB_KEY 0x001 -#define WJB_VALUE 0x002 -#define WJB_ELEM 0x004 -#define WJB_BEGIN_ARRAY 0x008 -#define WJB_END_ARRAY 0x010 -#define WJB_BEGIN_OBJECT 0x020 -#define WJB_END_OBJECT 0x040 +/* Tokens used when sequentially processing a jsonb value */ +typedef enum +{ + WJB_DONE, + WJB_KEY, + WJB_VALUE, + WJB_ELEM, + WJB_BEGIN_ARRAY, + WJB_END_ARRAY, + WJB_BEGIN_OBJECT, + WJB_END_OBJECT +} JsonbIteratorToken; /* * When using a GIN index for jsonb, we choose to index both keys and values. @@ -98,7 +56,6 @@ typedef struct JsonbPair JsonbPair; typedef struct JsonbValue JsonbValue; -typedef char *JsonbSuperHeader; /* * Jsonbs are varlena objects, so must meet the varlena convention that the @@ -109,35 +66,115 @@ typedef char *JsonbSuperHeader; * representation. Often, JsonbValues are just shims through which a Jsonb * buffer is accessed, but they can also be deep copied and passed around. * - * We have an abstraction called a "superheader". This is a pointer that - * conventionally points to the first item after our 4-byte uncompressed - * varlena header, from which we can read flags using bitwise operations. + * Jsonb is a tree structure. Each node in the tree consists of a JEntry + * header, and a variable-length content. The JEntry header indicates what + * kind of a node it is, e.g. a string or an array, and the offset and length + * of its variable-length portion within the container. * - * Frequently, we pass a superheader reference to a function, and it doesn't - * matter if it points to just after the start of a Jsonb, or to a temp buffer. + * The JEntry and the content of a node are not stored physically together. + * Instead, the container array or object has an array that holds the JEntrys + * of all the child nodes, followed by their variable-length portions. + * + * The root node is an exception; it has no parent array or object that could + * hold its JEntry. Hence, no JEntry header is stored for the root node. It + * is implicitly known that the the root node must be an array or an object, + * so we can get away without the type indicator as long as we can distinguish + * the two. For that purpose, both an array and an object begins with a uint32 + * header field, which contains an JB_FOBJECT or JB_FARRAY flag. When a naked + * scalar value needs to be stored as a Jsonb value, what we actually store is + * an array with one element, with the flags in the array's header field set + * to JB_FSCALAR | JB_FARRAY. + * + * To encode the length and offset of the variable-length portion of each + * node in a compact way, the JEntry stores only the end offset within the + * variable-length portion of the container node. For the first JEntry in the + * container's JEntry array, that equals to the length of the node data. For + * convenience, the JENTRY_ISFIRST flag is set. The begin offset and length + * of the rest of the entries can be calculated using the end offset of the + * previous JEntry in the array. + * + * Overall, the Jsonb struct requires 4-bytes alignment. Within the struct, + * the variable-length portion of some node types is aligned to a 4-byte + * boundary, while others are not. When alignment is needed, the padding is + * in the beginning of the node that requires it. For example, if a numeric + * node is stored after a string node, so that the numeric node begins at + * offset 3, the variable-length portion of the numeric node will begin with + * one padding byte. */ -typedef struct -{ - int32 vl_len_; /* varlena header (do not touch directly!) */ - uint32 superheader; - /* (array of JEntry follows, size determined using uint32 superheader) */ -} Jsonb; /* - * JEntry: there is one of these for each key _and_ value for objects. Arrays - * have one per element. + * Jentry format. + * + * The least significant 28 bits store the end offset of the entry (see + * JBE_ENDPOS, JBE_OFF, JBE_LEN macros below). The next three bits + * are used to store the type of the entry. The most significant bit + * is set on the first entry in an array of JEntrys. + */ +typedef uint32 JEntry; + +#define JENTRY_POSMASK 0x0FFFFFFF +#define JENTRY_TYPEMASK 0x70000000 +#define JENTRY_ISFIRST 0x80000000 + +/* values stored in the type bits */ +#define JENTRY_ISSTRING 0x00000000 +#define JENTRY_ISNUMERIC 0x10000000 +#define JENTRY_ISCONTAINER 0x20000000 /* array or object */ +#define JENTRY_ISBOOL_FALSE 0x30000000 +#define JENTRY_ISNULL 0x40000000 +#define JENTRY_ISBOOL_TRUE 0x70000000 + +/* Note possible multiple evaluations, also access to prior array element */ +#define JBE_ISFIRST(je_) (((je_) & JENTRY_ISFIRST) != 0) +#define JBE_ISSTRING(je_) (((je_) & JENTRY_TYPEMASK) == JENTRY_ISSTRING) +#define JBE_ISNUMERIC(je_) (((je_) & JENTRY_TYPEMASK) == JENTRY_ISNUMERIC) +#define JBE_ISCONTAINER(je_) (((je_) & JENTRY_TYPEMASK) == JENTRY_ISCONTAINER) +#define JBE_ISNULL(je_) (((je_) & JENTRY_TYPEMASK) == JENTRY_ISNULL) +#define JBE_ISBOOL_TRUE(je_) (((je_) & JENTRY_TYPEMASK) == JENTRY_ISBOOL_TRUE) +#define JBE_ISBOOL_FALSE(je_) (((je_) & JENTRY_TYPEMASK) == JENTRY_ISBOOL_FALSE) +#define JBE_ISBOOL(je_) (JBE_ISBOOL_TRUE(je_) || JBE_ISBOOL_FALSE(je_)) + +/* Get offset for Jentry */ +#define JBE_ENDPOS(je_) ((je_) & JENTRY_POSMASK) +#define JBE_OFF(je_) (JBE_ISFIRST(je_) ? 0 : JBE_ENDPOS((&(je_))[-1])) +#define JBE_LEN(je_) (JBE_ISFIRST(je_) ? \ + JBE_ENDPOS(je_) \ + : JBE_ENDPOS(je_) - JBE_ENDPOS((&(je_))[-1])) + +/* + * A jsonb array or object node, within a Jsonb Datum. * - * The position offset points to the _end_ so that we can get the length by - * subtraction from the previous entry. The JENTRY_ISFIRST flag indicates if - * there is a previous entry. + * An array has one child for each element. An object has two children for + * each key/value pair. */ +typedef struct JsonbContainer +{ + uint32 header; /* number of elements or key/value pairs, and + * flags */ + JEntry children[1]; /* variable length */ + + /* the data for each child node follows. */ +} JsonbContainer; + +/* flags for the header-field in JsonbContainer */ +#define JB_CMASK 0x0FFFFFFF +#define JB_FSCALAR 0x10000000 +#define JB_FOBJECT 0x20000000 +#define JB_FARRAY 0x40000000 + +/* The top-level on-disk format for a jsonb datum. */ typedef struct { - uint32 header; /* Shares some flags with superheader */ -} JEntry; + int32 vl_len_; /* varlena header (do not touch directly!) */ + JsonbContainer root; +} Jsonb; + +/* convenience macros for accessing the root container in a Jsonb datum */ +#define JB_ROOT_COUNT(jbp_) ( *(uint32*) VARDATA(jbp_) & JB_CMASK) +#define JB_ROOT_IS_SCALAR(jbp_) ( *(uint32*) VARDATA(jbp_) & JB_FSCALAR) +#define JB_ROOT_IS_OBJECT(jbp_) ( *(uint32*) VARDATA(jbp_) & JB_FOBJECT) +#define JB_ROOT_IS_ARRAY(jbp_) ( *(uint32*) VARDATA(jbp_) & JB_FARRAY) -#define IsAJsonbScalar(jsonbval) ((jsonbval)->type >= jbvNull && \ - (jsonbval)->type <= jbvBool) /* * JsonbValue: In-memory representation of Jsonb. This is a convenient @@ -161,8 +198,6 @@ struct JsonbValue jbvBinary } type; /* Influences sort order */ - int estSize; /* Estimated size of node (including subnodes) */ - union { Numeric numeric; @@ -189,11 +224,14 @@ struct JsonbValue struct { int len; - char *data; + JsonbContainer *data; } binary; } val; }; +#define IsAJsonbScalar(jsonbval) ((jsonbval)->type >= jbvNull && \ + (jsonbval)->type <= jbvBool) + /* * Pair within an Object. * @@ -294,27 +332,24 @@ extern Datum gin_consistent_jsonb_hash(PG_FUNCTION_ARGS); extern Datum gin_triconsistent_jsonb_hash(PG_FUNCTION_ARGS); /* Support functions */ -extern int compareJsonbSuperHeaderValue(JsonbSuperHeader a, - JsonbSuperHeader b); -extern JsonbValue *findJsonbValueFromSuperHeader(JsonbSuperHeader sheader, +extern int compareJsonbContainers(JsonbContainer *a, JsonbContainer *b); +extern JsonbValue *findJsonbValueFromContainer(JsonbContainer *sheader, uint32 flags, - uint32 *lowbound, JsonbValue *key); -extern JsonbValue *getIthJsonbValueFromSuperHeader(JsonbSuperHeader sheader, +extern JsonbValue *getIthJsonbValueFromContainer(JsonbContainer *sheader, uint32 i); -extern JsonbValue *pushJsonbValue(JsonbParseState **pstate, int seq, - JsonbValue *scalarVal); -extern JsonbIterator *JsonbIteratorInit(JsonbSuperHeader buffer); -extern int JsonbIteratorNext(JsonbIterator **it, JsonbValue *val, +extern JsonbValue *pushJsonbValue(JsonbParseState **pstate, + JsonbIteratorToken seq, JsonbValue *scalarVal); +extern JsonbIterator *JsonbIteratorInit(JsonbContainer *container); +extern JsonbIteratorToken JsonbIteratorNext(JsonbIterator **it, JsonbValue *val, bool skipNested); extern Jsonb *JsonbValueToJsonb(JsonbValue *val); extern bool JsonbDeepContains(JsonbIterator **val, JsonbIterator **mContained); -extern JsonbValue *arrayToJsonbSortedArray(ArrayType *a); extern void JsonbHashScalarValue(const JsonbValue *scalarVal, uint32 *hash); /* jsonb.c support function */ -extern char *JsonbToCString(StringInfo out, JsonbSuperHeader in, +extern char *JsonbToCString(StringInfo out, JsonbContainer *in, int estimated_len); #endif /* __JSONB_H__ */ |