I wrote:
> I wish it were cache-friendly too, per the upthread tangent about having
> to fetch keys from all over the place within a large JSON object.
> ... and while I was typing that sentence, lightning struck. The existing
> arrangement of object subfields with keys and values interleaved is just
> plain dumb. We should rearrange that as all the keys in order, then all
> the values in the same order. Then the keys are naturally adjacent in
> memory and object-key searches become much more cache-friendly: you
> probably touch most of the key portion of the object, but none of the
> values portion, until you know exactly what part of the latter to fetch.
> This approach might complicate the lookup logic marginally but I bet not
> very much; and it will be a huge help if we ever want to do smart access
> to EXTERNAL (non-compressed) JSON values.
> I will go prototype that just to see how much code rearrangement is
> required.
This looks pretty good from a coding point of view. I have not had time
yet to see if it affects the speed of the benchmark cases we've been
trying. I suspect that it won't make much difference in them. I think
if we do decide to make an on-disk format change, we should seriously
consider including this change.
The same concept could be applied to offset-based storage of course,
although I rather doubt that we'd make that combination of choices since
it would be giving up on-disk compatibility for benefits that are mostly
in the future.
Attached are two patches: one is a "delta" against the last jsonb-lengths
patch I posted, and the other is a "merged" patch showing the total change
from HEAD, for ease of application.
regards, tom lane
diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index e47eaea..4e7fe67 100644
*** a/src/backend/utils/adt/jsonb_util.c
--- b/src/backend/utils/adt/jsonb_util.c
***************
*** 26,33 ****
* in MaxAllocSize, and the number of elements (or pairs) must fit in the bits
* reserved for that in the JsonbContainer.header field.
*
! * (the total size of an array's elements is also limited by JENTRY_LENMASK,
! * but we're not concerned about that here)
*/
#define JSONB_MAX_ELEMS (Min(MaxAllocSize / sizeof(JsonbValue), JB_CMASK))
#define JSONB_MAX_PAIRS (Min(MaxAllocSize / sizeof(JsonbPair), JB_CMASK))
--- 26,33 ----
* in MaxAllocSize, and the number of elements (or pairs) must fit in the bits
* reserved for that in the JsonbContainer.header field.
*
! * (The total size of an array's or object's elements is also limited by
! * JENTRY_LENMASK, but we're not concerned about that here.)
*/
#define JSONB_MAX_ELEMS (Min(MaxAllocSize / sizeof(JsonbValue), JB_CMASK))
#define JSONB_MAX_PAIRS (Min(MaxAllocSize / sizeof(JsonbPair), JB_CMASK))
*************** findJsonbValueFromContainer(JsonbContain
*** 294,303 ****
{
JEntry *children = container->children;
int count = (container->header & JB_CMASK);
! JsonbValue *result = palloc(sizeof(JsonbValue));
Assert((flags & ~(JB_FARRAY | JB_FOBJECT)) == 0);
if (flags & JB_FARRAY & container->header)
{
char *base_addr = (char *) (children + count);
--- 294,309 ----
{
JEntry *children = container->children;
int count = (container->header & JB_CMASK);
! JsonbValue *result;
Assert((flags & ~(JB_FARRAY | JB_FOBJECT)) == 0);
+ /* Quick out without a palloc cycle if object/array is empty */
+ if (count <= 0)
+ return NULL;
+
+ result = palloc(sizeof(JsonbValue));
+
if (flags & JB_FARRAY & container->header)
{
char *base_addr = (char *) (children + count);
*************** findJsonbValueFromContainer(JsonbContain
*** 323,329 ****
char *base_addr = (char *) (children + count * 2);
uint32 *offsets;
uint32 lastoff;
! int lastoffpos;
uint32 stopLow = 0,
stopHigh = count;
--- 329,335 ----
char *base_addr = (char *) (children + count * 2);
uint32 *offsets;
uint32 lastoff;
! int i;
uint32 stopLow = 0,
stopHigh = count;
*************** findJsonbValueFromContainer(JsonbContain
*** 332,379 ****
/*
* We use a cache to avoid redundant getJsonbOffset() computations
! * inside the search loop. Note that count may well be zero at this
! * point; to avoid an ugly special case for initializing lastoff and
! * lastoffpos, we allocate one extra array element.
*/
! offsets = (uint32 *) palloc((count * 2 + 1) * sizeof(uint32));
! offsets[0] = lastoff = 0;
! lastoffpos = 0;
/* Binary search on object/pair keys *only* */
while (stopLow < stopHigh)
{
uint32 stopMiddle;
- int index;
int difference;
JsonbValue candidate;
stopMiddle = stopLow + (stopHigh - stopLow) / 2;
- /*
- * Compensate for the fact that we're searching through pairs (not
- * entries).
- */
- index = stopMiddle * 2;
-
- /* Update the offsets cache through at least index+1 */
- while (lastoffpos <= index)
- {
- lastoff += JBE_LEN(children, lastoffpos);
- offsets[++lastoffpos] = lastoff;
- }
-
candidate.type = jbvString;
! candidate.val.string.val = base_addr + offsets[index];
! candidate.val.string.len = JBE_LEN(children, index);
difference = lengthCompareJsonbStringValue(&candidate, key);
if (difference == 0)
{
! /* Found our key, return value */
! fillJsonbValue(children, index + 1,
! base_addr, offsets[index + 1],
result);
pfree(offsets);
--- 338,383 ----
/*
* We use a cache to avoid redundant getJsonbOffset() computations
! * inside the search loop. The entire cache can be filled immediately
! * since we expect to need the last offset for value access. (This
! * choice could lose if the key is not present, but avoiding extra
! * logic inside the search loop probably makes up for that.)
*/
! offsets = (uint32 *) palloc(count * sizeof(uint32));
! lastoff = 0;
! for (i = 0; i < count; i++)
! {
! offsets[i] = lastoff;
! lastoff += JBE_LEN(children, i);
! }
! /* lastoff now has the offset of the first value item */
/* Binary search on object/pair keys *only* */
while (stopLow < stopHigh)
{
uint32 stopMiddle;
int difference;
JsonbValue candidate;
stopMiddle = stopLow + (stopHigh - stopLow) / 2;
candidate.type = jbvString;
! candidate.val.string.val = base_addr + offsets[stopMiddle];
! candidate.val.string.len = JBE_LEN(children, stopMiddle);
difference = lengthCompareJsonbStringValue(&candidate, key);
if (difference == 0)
{
! /* Found our key, return corresponding value */
! int index = stopMiddle + count;
!
! /* navigate to appropriate offset */
! for (i = count; i < index; i++)
! lastoff += JBE_LEN(children, i);
!
! fillJsonbValue(children, index,
! base_addr, lastoff,
result);
pfree(offsets);
*************** recurse:
*** 730,735 ****
--- 734,740 ----
val->val.array.rawScalar = (*it)->isScalar;
(*it)->curIndex = 0;
(*it)->curDataOffset = 0;
+ (*it)->curValueOffset = 0; /* not actually used */
/* Set state for next call */
(*it)->state = JBI_ARRAY_ELEM;
return WJB_BEGIN_ARRAY;
*************** recurse:
*** 780,785 ****
--- 785,792 ----
*/
(*it)->curIndex = 0;
(*it)->curDataOffset = 0;
+ (*it)->curValueOffset = getJsonbOffset((*it)->children,
+ (*it)->nElems);
/* Set state for next call */
(*it)->state = JBI_OBJECT_KEY;
return WJB_BEGIN_OBJECT;
*************** recurse:
*** 799,805 ****
else
{
/* Return key of a key/value pair. */
! fillJsonbValue((*it)->children, (*it)->curIndex * 2,
(*it)->dataProper, (*it)->curDataOffset,
val);
if (val->type != jbvString)
--- 806,812 ----
else
{
/* Return key of a key/value pair. */
! fillJsonbValue((*it)->children, (*it)->curIndex,
(*it)->dataProper, (*it)->curDataOffset,
val);
if (val->type != jbvString)
*************** recurse:
*** 814,828 ****
/* Set state for next call */
(*it)->state = JBI_OBJECT_KEY;
! (*it)->curDataOffset += JBE_LEN((*it)->children,
! (*it)->curIndex * 2);
!
! fillJsonbValue((*it)->children, (*it)->curIndex * 2 + 1,
! (*it)->dataProper, (*it)->curDataOffset,
val);
(*it)->curDataOffset += JBE_LEN((*it)->children,
! (*it)->curIndex * 2 + 1);
(*it)->curIndex++;
/*
--- 821,834 ----
/* Set state for next call */
(*it)->state = JBI_OBJECT_KEY;
! fillJsonbValue((*it)->children, (*it)->curIndex + (*it)->nElems,
! (*it)->dataProper, (*it)->curValueOffset,
val);
(*it)->curDataOffset += JBE_LEN((*it)->children,
! (*it)->curIndex);
! (*it)->curValueOffset += JBE_LEN((*it)->children,
! (*it)->curIndex + (*it)->nElems);
(*it)->curIndex++;
/*
*************** convertJsonbObject(StringInfo buffer, JE
*** 1509,1514 ****
--- 1515,1524 ----
/* Reserve space for the JEntries of the keys and values. */
metaoffset = reserveFromBuffer(buffer, sizeof(JEntry) * val->val.object.nPairs * 2);
+ /*
+ * Iterate over the keys, then over the values, since that is the ordering
+ * we want in the on-disk representation.
+ */
totallen = 0;
for (i = 0; i < val->val.object.nPairs; i++)
{
*************** convertJsonbObject(StringInfo buffer, JE
*** 1529,1534 ****
--- 1539,1561 ----
metaoffset += sizeof(JEntry);
/*
+ * Bail out if total variable-length data exceeds what will fit in a
+ * JEntry length field. We check this in each iteration, not just
+ * once at the end, to forestall possible integer overflow.
+ */
+ if (totallen > JENTRY_LENMASK)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("total size of jsonb object elements exceeds the maximum of %u bytes",
+ JENTRY_LENMASK)));
+ }
+ for (i = 0; i < val->val.object.nPairs; i++)
+ {
+ JsonbPair *pair = &val->val.object.pairs[i];
+ int len;
+ JEntry meta;
+
+ /*
* Convert value, producing a JEntry and appending its variable-length
* data to buffer
*/
*************** convertJsonbObject(StringInfo buffer, JE
*** 1543,1551 ****
/*
* Bail out if total variable-length data exceeds what will fit in a
* JEntry length field. We check this in each iteration, not just
! * once at the end, to forestall possible integer overflow. But it
! * should be sufficient to check once per iteration, since
! * JENTRY_LENMASK is several bits narrower than int.
*/
if (totallen > JENTRY_LENMASK)
ereport(ERROR,
--- 1570,1576 ----
/*
* Bail out if total variable-length data exceeds what will fit in a
* JEntry length field. We check this in each iteration, not just
! * once at the end, to forestall possible integer overflow.
*/
if (totallen > JENTRY_LENMASK)
ereport(ERROR,
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index b9a4314..f9472af 100644
*** a/src/include/utils/jsonb.h
--- b/src/include/utils/jsonb.h
*************** typedef uint32 JEntry;
*** 149,156 ****
/*
* A jsonb array or object node, within a Jsonb Datum.
*
! * An array has one child for each element. An object has two children for
! * each key/value pair.
*/
typedef struct JsonbContainer
{
--- 149,160 ----
/*
* A jsonb array or object node, within a Jsonb Datum.
*
! * An array has one child for each element, stored in array order.
! *
! * An object has two children for each key/value pair. The keys all appear
! * first, in key sort order; then the values appear, in an order matching the
! * key order. This arrangement keeps the keys compact in memory, making a
! * search for a particular key more cache-friendly.
*/
typedef struct JsonbContainer
{
*************** typedef struct JsonbContainer
*** 162,169 ****
} JsonbContainer;
/* flags for the header-field in JsonbContainer */
! #define JB_CMASK 0x0FFFFFFF
! #define JB_FSCALAR 0x10000000
#define JB_FOBJECT 0x20000000
#define JB_FARRAY 0x40000000
--- 166,173 ----
} JsonbContainer;
/* flags for the header-field in JsonbContainer */
! #define JB_CMASK 0x0FFFFFFF /* mask for count field */
! #define JB_FSCALAR 0x10000000 /* flag bits */
#define JB_FOBJECT 0x20000000
#define JB_FARRAY 0x40000000
*************** struct JsonbValue
*** 238,255 ****
(jsonbval)->type <= jbvBool)
/*
! * Pair within an Object.
*
! * Pairs with duplicate keys are de-duplicated. We store the order for the
! * benefit of doing so in a well-defined way with respect to the original
! * observed order (which is "last observed wins"). This is only used briefly
! * when originally constructing a Jsonb.
*/
struct JsonbPair
{
JsonbValue key; /* Must be a jbvString */
JsonbValue value; /* May be of any type */
! uint32 order; /* preserves order of pairs with equal keys */
};
/* Conversion state used when parsing Jsonb from text, or for type coercion */
--- 242,261 ----
(jsonbval)->type <= jbvBool)
/*
! * Key/value pair within an Object.
*
! * This struct type is only used briefly while constructing a Jsonb; it is
! * *not* the on-disk representation.
! *
! * Pairs with duplicate keys are de-duplicated. We store the originally
! * observed pair ordering for the purpose of removing duplicates in a
! * well-defined way (which is "last observed wins").
*/
struct JsonbPair
{
JsonbValue key; /* Must be a jbvString */
JsonbValue value; /* May be of any type */
! uint32 order; /* Pair's index in original sequence */
};
/* Conversion state used when parsing Jsonb from text, or for type coercion */
*************** typedef struct JsonbIterator
*** 284,295 ****
/* Data proper. This points just past end of children array */
char *dataProper;
! /* Current item in buffer (up to nElems, but must * 2 for objects) */
int curIndex;
/* Data offset corresponding to current item */
uint32 curDataOffset;
/* Private state */
JsonbIterState state;
--- 290,308 ----
/* Data proper. This points just past end of children array */
char *dataProper;
! /* Current item in buffer (up to nElems) */
int curIndex;
/* Data offset corresponding to current item */
uint32 curDataOffset;
+ /*
+ * If the container is an object, we want to return keys and values
+ * alternately; so curDataOffset points to the current key, and
+ * curValueOffset points to the current value.
+ */
+ uint32 curValueOffset;
+
/* Private state */
JsonbIterState state;
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 2fd87fc..456011a 100644
*** a/src/backend/utils/adt/jsonb.c
--- b/src/backend/utils/adt/jsonb.c
*************** jsonb_from_cstring(char *json, int len)
*** 196,207 ****
static size_t
checkStringLen(size_t len)
{
! if (len > JENTRY_POSMASK)
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("string too long to represent as jsonb string"),
errdetail("Due to an implementation restriction, jsonb strings cannot exceed %d bytes.",
! JENTRY_POSMASK)));
return len;
}
--- 196,207 ----
static size_t
checkStringLen(size_t len)
{
! if (len > JENTRY_LENMASK)
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("string too long to represent as jsonb string"),
errdetail("Due to an implementation restriction, jsonb strings cannot exceed %d bytes.",
! JENTRY_LENMASK)));
return len;
}
diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index 04f35bf..4e7fe67 100644
*** a/src/backend/utils/adt/jsonb_util.c
--- b/src/backend/utils/adt/jsonb_util.c
***************
*** 26,40 ****
* in MaxAllocSize, and the number of elements (or pairs) must fit in the bits
* reserved for that in the JsonbContainer.header field.
*
! * (the total size of an array's elements is also limited by JENTRY_POSMASK,
! * but we're not concerned about that here)
*/
#define JSONB_MAX_ELEMS (Min(MaxAllocSize / sizeof(JsonbValue), JB_CMASK))
#define JSONB_MAX_PAIRS (Min(MaxAllocSize / sizeof(JsonbPair), JB_CMASK))
! static void fillJsonbValue(JEntry *array, int index, char *base_addr,
JsonbValue *result);
! static bool equalsJsonbScalarValue(JsonbValue *a, JsonbValue *b);
static int compareJsonbScalarValue(JsonbValue *a, JsonbValue *b);
static Jsonb *convertToJsonb(JsonbValue *val);
static void convertJsonbValue(StringInfo buffer, JEntry *header, JsonbValue *val, int level);
--- 26,41 ----
* in MaxAllocSize, and the number of elements (or pairs) must fit in the bits
* reserved for that in the JsonbContainer.header field.
*
! * (The total size of an array's or object's elements is also limited by
! * JENTRY_LENMASK, but we're not concerned about that here.)
*/
#define JSONB_MAX_ELEMS (Min(MaxAllocSize / sizeof(JsonbValue), JB_CMASK))
#define JSONB_MAX_PAIRS (Min(MaxAllocSize / sizeof(JsonbPair), JB_CMASK))
! static void fillJsonbValue(JEntry *children, int index,
! 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 convertJsonbValue(StringInfo buffer, JEntry *header, JsonbValue *val, int level);
*************** static void convertJsonbArray(StringInfo
*** 42,48 ****
static void convertJsonbObject(StringInfo buffer, JEntry *header, JsonbValue *val, int level);
static void convertJsonbScalar(StringInfo buffer, JEntry *header, JsonbValue *scalarVal);
! static int reserveFromBuffer(StringInfo buffer, int len);
static void appendToBuffer(StringInfo buffer, const char *data, int len);
static void copyToBuffer(StringInfo buffer, int offset, const char *data, int len);
static short padBufferToInt(StringInfo buffer);
--- 43,49 ----
static void convertJsonbObject(StringInfo buffer, JEntry *header, JsonbValue *val, int level);
static void convertJsonbScalar(StringInfo buffer, JEntry *header, JsonbValue *scalarVal);
! static int reserveFromBuffer(StringInfo buffer, int len);
static void appendToBuffer(StringInfo buffer, const char *data, int len);
static void copyToBuffer(StringInfo buffer, int offset, const char *data, int len);
static short padBufferToInt(StringInfo buffer);
*************** JsonbValueToJsonb(JsonbValue *val)
*** 108,113 ****
--- 109,135 ----
}
/*
+ * Get the offset of the variable-length portion of a Jsonb node within
+ * the variable-length-data part of its container. The node is identified
+ * by index within the container's JEntry array.
+ *
+ * We do this by adding up the lengths of all the previous nodes'
+ * variable-length portions. It's best to avoid using this function when
+ * iterating through all the nodes in a container, since that would result
+ * in O(N^2) work.
+ */
+ uint32
+ getJsonbOffset(const JEntry *ja, int index)
+ {
+ uint32 off = 0;
+ int i;
+
+ for (i = 0; i < index; i++)
+ off += JBE_LEN(ja, i);
+ return off;
+ }
+
+ /*
* 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
*************** compareJsonbContainers(JsonbContainer *a
*** 201,207 ****
*
* If the two values were of the same container type, then there'd
* have been a chance to observe the variation in the number of
! * elements/pairs (when processing WJB_BEGIN_OBJECT, say). They're
* either two heterogeneously-typed containers, or a container and
* some scalar type.
*
--- 223,229 ----
*
* If the two values were of the same container type, then there'd
* have been a chance to observe the variation in the number of
! * elements/pairs (when processing WJB_BEGIN_OBJECT, say). They're
* either two heterogeneously-typed containers, or a container and
* some scalar type.
*
*************** findJsonbValueFromContainer(JsonbContain
*** 272,333 ****
{
JEntry *children = container->children;
int count = (container->header & JB_CMASK);
! JsonbValue *result = palloc(sizeof(JsonbValue));
Assert((flags & ~(JB_FARRAY | JB_FOBJECT)) == 0);
if (flags & JB_FARRAY & container->header)
{
char *base_addr = (char *) (children + count);
int i;
for (i = 0; i < count; i++)
{
! fillJsonbValue(children, i, base_addr, result);
if (key->type == result->type)
{
if (equalsJsonbScalarValue(key, result))
return result;
}
}
}
else if (flags & JB_FOBJECT & container->header)
{
/* Since this is an object, account for *Pairs* of Jentrys */
char *base_addr = (char *) (children + count * 2);
uint32 stopLow = 0,
! stopMiddle;
! /* Object key past by caller must be a string */
Assert(key->type == jbvString);
/* Binary search on object/pair keys *only* */
! while (stopLow < count)
{
! int index;
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;
!
! index = stopMiddle * 2;
candidate.type = jbvString;
! candidate.val.string.val = base_addr + JBE_OFF(children, index);
! candidate.val.string.len = JBE_LEN(children, index);
difference = lengthCompareJsonbStringValue(&candidate, key);
if (difference == 0)
{
! /* Found our key, return value */
! fillJsonbValue(children, index + 1, base_addr, result);
return result;
}
else
--- 294,386 ----
{
JEntry *children = container->children;
int count = (container->header & JB_CMASK);
! JsonbValue *result;
Assert((flags & ~(JB_FARRAY | JB_FOBJECT)) == 0);
+ /* Quick out without a palloc cycle if object/array is empty */
+ if (count <= 0)
+ return NULL;
+
+ result = palloc(sizeof(JsonbValue));
+
if (flags & JB_FARRAY & container->header)
{
char *base_addr = (char *) (children + count);
+ uint32 offset = 0;
int i;
for (i = 0; i < count; i++)
{
! fillJsonbValue(children, i, base_addr, offset, result);
if (key->type == result->type)
{
if (equalsJsonbScalarValue(key, result))
return result;
}
+
+ offset += JBE_LEN(children, i);
}
}
else if (flags & JB_FOBJECT & container->header)
{
/* Since this is an object, account for *Pairs* of Jentrys */
char *base_addr = (char *) (children + count * 2);
+ uint32 *offsets;
+ uint32 lastoff;
+ int i;
uint32 stopLow = 0,
! stopHigh = count;
! /* Object key passed by caller must be a string */
Assert(key->type == jbvString);
+ /*
+ * We use a cache to avoid redundant getJsonbOffset() computations
+ * inside the search loop. The entire cache can be filled immediately
+ * since we expect to need the last offset for value access. (This
+ * choice could lose if the key is not present, but avoiding extra
+ * logic inside the search loop probably makes up for that.)
+ */
+ offsets = (uint32 *) palloc(count * sizeof(uint32));
+ lastoff = 0;
+ for (i = 0; i < count; i++)
+ {
+ offsets[i] = lastoff;
+ lastoff += JBE_LEN(children, i);
+ }
+ /* lastoff now has the offset of the first value item */
+
/* Binary search on object/pair keys *only* */
! while (stopLow < stopHigh)
{
! uint32 stopMiddle;
int difference;
JsonbValue candidate;
! stopMiddle = stopLow + (stopHigh - stopLow) / 2;
candidate.type = jbvString;
! candidate.val.string.val = base_addr + offsets[stopMiddle];
! candidate.val.string.len = JBE_LEN(children, stopMiddle);
difference = lengthCompareJsonbStringValue(&candidate, key);
if (difference == 0)
{
! /* Found our key, return corresponding value */
! int index = stopMiddle + count;
!
! /* navigate to appropriate offset */
! for (i = count; i < index; i++)
! lastoff += JBE_LEN(children, i);
!
! fillJsonbValue(children, index,
! base_addr, lastoff,
! result);
+ pfree(offsets);
return result;
}
else
*************** findJsonbValueFromContainer(JsonbContain
*** 335,343 ****
if (difference < 0)
stopLow = stopMiddle + 1;
else
! count = stopMiddle;
}
}
}
/* Not found */
--- 388,398 ----
if (difference < 0)
stopLow = stopMiddle + 1;
else
! stopHigh = stopMiddle;
}
}
+
+ pfree(offsets);
}
/* Not found */
*************** getIthJsonbValueFromContainer(JsonbConta
*** 368,374 ****
result = palloc(sizeof(JsonbValue));
! fillJsonbValue(container->children, i, base_addr, result);
return result;
}
--- 423,431 ----
result = palloc(sizeof(JsonbValue));
! fillJsonbValue(container->children, i, base_addr,
! getJsonbOffset(container->children, i),
! result);
return result;
}
*************** getIthJsonbValueFromContainer(JsonbConta
*** 377,387 ****
* A helper function to fill in a JsonbValue to represent an element of an
* array, or a key or value of an object.
*
* A nested array or object will be returned as jbvBinary, ie. it won't be
* expanded.
*/
static void
! fillJsonbValue(JEntry *children, int index, char *base_addr, JsonbValue *result)
{
JEntry entry = children[index];
--- 434,450 ----
* A helper function to fill in a JsonbValue to represent an element of an
* array, or a key or value of an object.
*
+ * The node's JEntry is at children[index], and its variable-length data
+ * is at base_addr + offset. We make the caller determine the offset since
+ * in many cases the caller can amortize the work across multiple children.
+ *
* A nested array or object will be returned as jbvBinary, ie. it won't be
* expanded.
*/
static void
! fillJsonbValue(JEntry *children, int index,
! char *base_addr, uint32 offset,
! JsonbValue *result)
{
JEntry entry = children[index];
*************** fillJsonbValue(JEntry *children, int ind
*** 392,405 ****
else if (JBE_ISSTRING(entry))
{
result->type = jbvString;
! result->val.string.val = base_addr + JBE_OFF(children, index);
! result->val.string.len = JBE_LEN(children, index);
Assert(result->val.string.len >= 0);
}
else if (JBE_ISNUMERIC(entry))
{
result->type = jbvNumeric;
! result->val.numeric = (Numeric) (base_addr + INTALIGN(JBE_OFF(children, index)));
}
else if (JBE_ISBOOL_TRUE(entry))
{
--- 455,468 ----
else if (JBE_ISSTRING(entry))
{
result->type = jbvString;
! result->val.string.val = base_addr + offset;
! result->val.string.len = JBE_LENFLD(entry);
Assert(result->val.string.len >= 0);
}
else if (JBE_ISNUMERIC(entry))
{
result->type = jbvNumeric;
! result->val.numeric = (Numeric) (base_addr + INTALIGN(offset));
}
else if (JBE_ISBOOL_TRUE(entry))
{
*************** fillJsonbValue(JEntry *children, int ind
*** 415,422 ****
{
Assert(JBE_ISCONTAINER(entry));
result->type = jbvBinary;
! result->val.binary.data = (JsonbContainer *) (base_addr + INTALIGN(JBE_OFF(children, index)));
! result->val.binary.len = JBE_LEN(children, index) - (INTALIGN(JBE_OFF(children, index)) - JBE_OFF(children, index));
}
}
--- 478,486 ----
{
Assert(JBE_ISCONTAINER(entry));
result->type = jbvBinary;
! /* Remove alignment padding from data pointer and len */
! result->val.binary.data = (JsonbContainer *) (base_addr + INTALIGN(offset));
! result->val.binary.len = JBE_LENFLD(entry) - (INTALIGN(offset) - offset);
}
}
*************** recurse:
*** 668,680 ****
* a full conversion
*/
val->val.array.rawScalar = (*it)->isScalar;
! (*it)->i = 0;
/* Set state for next call */
(*it)->state = JBI_ARRAY_ELEM;
return WJB_BEGIN_ARRAY;
case JBI_ARRAY_ELEM:
! if ((*it)->i >= (*it)->nElems)
{
/*
* All elements within array already processed. Report this
--- 732,746 ----
* a full conversion
*/
val->val.array.rawScalar = (*it)->isScalar;
! (*it)->curIndex = 0;
! (*it)->curDataOffset = 0;
! (*it)->curValueOffset = 0; /* not actually used */
/* Set state for next call */
(*it)->state = JBI_ARRAY_ELEM;
return WJB_BEGIN_ARRAY;
case JBI_ARRAY_ELEM:
! if ((*it)->curIndex >= (*it)->nElems)
{
/*
* All elements within array already processed. Report this
*************** recurse:
*** 686,692 ****
return WJB_END_ARRAY;
}
! fillJsonbValue((*it)->children, (*it)->i++, (*it)->dataProper, val);
if (!IsAJsonbScalar(val) && !skipNested)
{
--- 752,763 ----
return WJB_END_ARRAY;
}
! fillJsonbValue((*it)->children, (*it)->curIndex,
! (*it)->dataProper, (*it)->curDataOffset,
! val);
!
! (*it)->curDataOffset += JBE_LEN((*it)->children, (*it)->curIndex);
! (*it)->curIndex++;
if (!IsAJsonbScalar(val) && !skipNested)
{
*************** recurse:
*** 697,704 ****
else
{
/*
! * Scalar item in array, or a container and caller didn't
! * want us to recurse into it.
*/
return WJB_ELEM;
}
--- 768,775 ----
else
{
/*
! * Scalar item in array, or a container and caller didn't want
! * us to recurse into it.
*/
return WJB_ELEM;
}
*************** recurse:
*** 712,724 ****
* v->val.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_OBJECT_KEY;
return WJB_BEGIN_OBJECT;
case JBI_OBJECT_KEY:
! if ((*it)->i >= (*it)->nElems)
{
/*
* All pairs within object already processed. Report this to
--- 783,798 ----
* v->val.object.pairs is not actually set, because we aren't
* doing a full conversion
*/
! (*it)->curIndex = 0;
! (*it)->curDataOffset = 0;
! (*it)->curValueOffset = getJsonbOffset((*it)->children,
! (*it)->nElems);
/* Set state for next call */
(*it)->state = JBI_OBJECT_KEY;
return WJB_BEGIN_OBJECT;
case JBI_OBJECT_KEY:
! if ((*it)->curIndex >= (*it)->nElems)
{
/*
* All pairs within object already processed. Report this to
*************** recurse:
*** 732,738 ****
else
{
/* Return key of a key/value pair. */
! fillJsonbValue((*it)->children, (*it)->i * 2, (*it)->dataProper, val);
if (val->type != jbvString)
elog(ERROR, "unexpected jsonb type as object key");
--- 806,814 ----
else
{
/* Return key of a key/value pair. */
! fillJsonbValue((*it)->children, (*it)->curIndex,
! (*it)->dataProper, (*it)->curDataOffset,
! val);
if (val->type != jbvString)
elog(ERROR, "unexpected jsonb type as object key");
*************** recurse:
*** 745,752 ****
/* Set state for next call */
(*it)->state = JBI_OBJECT_KEY;
! fillJsonbValue((*it)->children, ((*it)->i++) * 2 + 1,
! (*it)->dataProper, val);
/*
* Value may be a container, in which case we recurse with new,
--- 821,835 ----
/* Set state for next call */
(*it)->state = JBI_OBJECT_KEY;
! fillJsonbValue((*it)->children, (*it)->curIndex + (*it)->nElems,
! (*it)->dataProper, (*it)->curValueOffset,
! val);
!
! (*it)->curDataOffset += JBE_LEN((*it)->children,
! (*it)->curIndex);
! (*it)->curValueOffset += JBE_LEN((*it)->children,
! (*it)->curIndex + (*it)->nElems);
! (*it)->curIndex++;
/*
* Value may be a container, in which case we recurse with new,
*************** reserveFromBuffer(StringInfo buffer, int
*** 1209,1216 ****
buffer->len += len;
/*
! * Keep a trailing null in place, even though it's not useful for us;
! * it seems best to preserve the invariants of StringInfos.
*/
buffer->data[buffer->len] = '\0';
--- 1292,1299 ----
buffer->len += len;
/*
! * Keep a trailing null in place, even though it's not useful for us; it
! * seems best to preserve the invariants of StringInfos.
*/
buffer->data[buffer->len] = '\0';
*************** convertToJsonb(JsonbValue *val)
*** 1284,1291 ****
/*
* Note: the JEntry of the root is discarded. Therefore the root
! * JsonbContainer struct must contain enough information to tell what
! * kind of value it is.
*/
res = (Jsonb *) buffer.data;
--- 1367,1374 ----
/*
* Note: the JEntry of the root is discarded. Therefore the root
! * JsonbContainer struct must contain enough information to tell what kind
! * of value it is.
*/
res = (Jsonb *) buffer.data;
*************** convertJsonbValue(StringInfo buffer, JEn
*** 1315,1324 ****
return;
/*
! * A JsonbValue passed as val should never have a type of jbvBinary,
! * and neither should any of its sub-components. Those values will be
! * produced by convertJsonbArray and convertJsonbObject, the results of
! * which will not be passed back to this function as an argument.
*/
if (IsAJsonbScalar(val))
--- 1398,1407 ----
return;
/*
! * A JsonbValue passed as val should never have a type of jbvBinary, and
! * neither should any of its sub-components. Those values will be produced
! * by convertJsonbArray and convertJsonbObject, the results of which will
! * not be passed back to this function as an argument.
*/
if (IsAJsonbScalar(val))
*************** convertJsonbArray(StringInfo buffer, JEn
*** 1340,1353 ****
int totallen;
uint32 header;
! /* Initialize pointer into conversion buffer at this level */
offset = buffer->len;
padBufferToInt(buffer);
/*
! * 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)
--- 1423,1437 ----
int totallen;
uint32 header;
! /* Remember where variable-length data starts for this array */
offset = buffer->len;
+ /* Align to 4-byte boundary (any padding counts as part of my data) */
padBufferToInt(buffer);
/*
! * Construct the header Jentry and store it in the beginning of the
! * variable-length payload.
*/
header = val->val.array.nElems | JB_FARRAY;
if (val->val.array.rawScalar)
*************** convertJsonbArray(StringInfo buffer, JEn
*** 1358,1364 ****
}
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;
--- 1442,1449 ----
}
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;
*************** convertJsonbArray(StringInfo buffer, JEn
*** 1368,1391 ****
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 = (meta & ~JENTRY_POSMASK) | totallen;
copyToBuffer(buffer, metaoffset, (char *) &meta, sizeof(JEntry));
metaoffset += sizeof(JEntry);
}
totallen = buffer->len - offset;
/* Initialize the header of this node, in the container's JEntry array */
*pheader = JENTRY_ISCONTAINER | totallen;
}
--- 1453,1491 ----
int len;
JEntry meta;
+ /*
+ * Convert element, producing a JEntry and appending its
+ * variable-length data to buffer
+ */
convertJsonbValue(buffer, &meta, elem, level + 1);
! /*
! * Bail out if total variable-length data exceeds what will fit in a
! * JEntry length field. We check this in each iteration, not just
! * once at the end, to forestall possible integer overflow.
! */
! len = JBE_LENFLD(meta);
! totallen += len;
! if (totallen > JENTRY_LENMASK)
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("total size of jsonb array elements exceeds the maximum of %u bytes",
! JENTRY_LENMASK)));
copyToBuffer(buffer, metaoffset, (char *) &meta, sizeof(JEntry));
metaoffset += sizeof(JEntry);
}
+ /* Total data size is everything we've appended to buffer */
totallen = buffer->len - offset;
+ /* Check length again, since we didn't include the metadata above */
+ if (totallen > JENTRY_LENMASK)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("total size of jsonb array elements exceeds the maximum of %u bytes",
+ JENTRY_LENMASK)));
+
/* Initialize the header of this node, in the container's JEntry array */
*pheader = JENTRY_ISCONTAINER | totallen;
}
*************** convertJsonbArray(StringInfo buffer, JEn
*** 1393,1457 ****
static void
convertJsonbObject(StringInfo buffer, JEntry *pheader, JsonbValue *val, int level)
{
- uint32 header;
int offset;
int metaoffset;
int i;
int totallen;
! /* Initialize pointer into conversion buffer at this level */
offset = buffer->len;
padBufferToInt(buffer);
! /* Initialize header */
header = val->val.object.nPairs | JB_FOBJECT;
appendToBuffer(buffer, (char *) &header, sizeof(uint32));
! /* reserve space for the JEntries of the keys and values */
metaoffset = reserveFromBuffer(buffer, sizeof(JEntry) * val->val.object.nPairs * 2);
totallen = 0;
for (i = 0; i < val->val.object.nPairs; i++)
{
! JsonbPair *pair = &val->val.object.pairs[i];
! int len;
! JEntry meta;
! /* put key */
convertJsonbScalar(buffer, &meta, &pair->key);
! 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 = (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;
}
--- 1493,1595 ----
static void
convertJsonbObject(StringInfo buffer, JEntry *pheader, JsonbValue *val, int level)
{
int offset;
int metaoffset;
int i;
int totallen;
+ uint32 header;
! /* Remember where variable-length data starts for this object */
offset = buffer->len;
+ /* Align to 4-byte boundary (any padding counts as part of my data) */
padBufferToInt(buffer);
! /*
! * Construct the header Jentry and store it in the beginning of the
! * variable-length payload.
! */
header = val->val.object.nPairs | JB_FOBJECT;
appendToBuffer(buffer, (char *) &header, sizeof(uint32));
! /* Reserve space for the JEntries of the keys and values. */
metaoffset = reserveFromBuffer(buffer, sizeof(JEntry) * val->val.object.nPairs * 2);
+ /*
+ * Iterate over the keys, then over the values, since that is the ordering
+ * we want in the on-disk representation.
+ */
totallen = 0;
for (i = 0; i < val->val.object.nPairs; i++)
{
! JsonbPair *pair = &val->val.object.pairs[i];
! int len;
! JEntry meta;
! /*
! * Convert key, producing a JEntry and appending its variable-length
! * data to buffer
! */
convertJsonbScalar(buffer, &meta, &pair->key);
! len = JBE_LENFLD(meta);
totallen += len;
copyToBuffer(buffer, metaoffset, (char *) &meta, sizeof(JEntry));
metaoffset += sizeof(JEntry);
! /*
! * Bail out if total variable-length data exceeds what will fit in a
! * JEntry length field. We check this in each iteration, not just
! * once at the end, to forestall possible integer overflow.
! */
! if (totallen > JENTRY_LENMASK)
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
! errmsg("total size of jsonb object elements exceeds the maximum of %u bytes",
! JENTRY_LENMASK)));
! }
! for (i = 0; i < val->val.object.nPairs; i++)
! {
! JsonbPair *pair = &val->val.object.pairs[i];
! int len;
! JEntry meta;
!
! /*
! * Convert value, producing a JEntry and appending its variable-length
! * data to buffer
! */
! convertJsonbValue(buffer, &meta, &pair->value, level + 1);
!
! len = JBE_LENFLD(meta);
! totallen += len;
copyToBuffer(buffer, metaoffset, (char *) &meta, sizeof(JEntry));
metaoffset += sizeof(JEntry);
+
+ /*
+ * Bail out if total variable-length data exceeds what will fit in a
+ * JEntry length field. We check this in each iteration, not just
+ * once at the end, to forestall possible integer overflow.
+ */
+ if (totallen > JENTRY_LENMASK)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("total size of jsonb object elements exceeds the maximum of %u bytes",
+ JENTRY_LENMASK)));
}
+ /* Total data size is everything we've appended to buffer */
totallen = buffer->len - offset;
+ /* Check length again, since we didn't include the metadata above */
+ if (totallen > JENTRY_LENMASK)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("total size of jsonb object elements exceeds the maximum of %u bytes",
+ JENTRY_LENMASK)));
+
+ /* Initialize the header of this node, in the container's JEntry array */
*pheader = JENTRY_ISCONTAINER | totallen;
}
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index 91e3e14..f9472af 100644
*** a/src/include/utils/jsonb.h
--- b/src/include/utils/jsonb.h
*************** typedef struct JsonbValue JsonbValue;
*** 83,91 ****
* buffer is accessed, but they can also be deep copied and passed around.
*
* 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.
*
* 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
--- 83,91 ----
* buffer is accessed, but they can also be deep copied and passed around.
*
* Jsonb is a tree structure. Each node in the tree consists of a JEntry
! * header and a variable-length content (possibly of zero size). The JEntry
! * header indicates what kind of a node it is, e.g. a string or an array,
! * and includes the length of its variable-length portion.
*
* 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
*************** typedef struct JsonbValue JsonbValue;
*** 95,133 ****
* hold its JEntry. Hence, no JEntry header is stored for the root node. It
* is implicitly known that 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. 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.
*/
/*
* 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 unused, and should be set to zero.
*/
typedef uint32 JEntry;
! #define JENTRY_POSMASK 0x0FFFFFFF
#define JENTRY_TYPEMASK 0x70000000
/* values stored in the type bits */
--- 95,126 ----
* hold its JEntry. Hence, no JEntry header is stored for the root node. It
* is implicitly known that 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 begin 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.
*
* 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 so that the actual numeric data is 4-byte aligned.
*/
/*
* Jentry format.
*
! * The least significant 28 bits store the data length of the entry (see
! * JBE_LENFLD and JBE_LEN macros below). The next three bits store the type
! * of the entry. The most significant bit is reserved for future use, and
! * should be set to zero.
*/
typedef uint32 JEntry;
! #define JENTRY_LENMASK 0x0FFFFFFF
#define JENTRY_TYPEMASK 0x70000000
/* values stored in the type bits */
*************** typedef uint32 JEntry;
*** 148,166 ****
#define JBE_ISBOOL(je_) (JBE_ISBOOL_TRUE(je_) || JBE_ISBOOL_FALSE(je_))
/*
! * Macros for getting the offset and length of an element. Note multiple
! * evaluations and access to prior array element.
*/
! #define JBE_ENDPOS(je_) ((je_) & JENTRY_POSMASK)
! #define JBE_OFF(ja, i) ((i) == 0 ? 0 : JBE_ENDPOS((ja)[i - 1]))
! #define JBE_LEN(ja, i) ((i) == 0 ? JBE_ENDPOS((ja)[i]) \
! : JBE_ENDPOS((ja)[i]) - JBE_ENDPOS((ja)[i - 1]))
/*
* A jsonb array or object node, within a Jsonb Datum.
*
! * An array has one child for each element. An object has two children for
! * each key/value pair.
*/
typedef struct JsonbContainer
{
--- 141,160 ----
#define JBE_ISBOOL(je_) (JBE_ISBOOL_TRUE(je_) || JBE_ISBOOL_FALSE(je_))
/*
! * Macros for getting the data length of a JEntry.
*/
! #define JBE_LENFLD(je_) ((je_) & JENTRY_LENMASK)
! #define JBE_LEN(ja, i) JBE_LENFLD((ja)[i])
/*
* A jsonb array or object node, within a Jsonb Datum.
*
! * An array has one child for each element, stored in array order.
! *
! * An object has two children for each key/value pair. The keys all appear
! * first, in key sort order; then the values appear, in an order matching the
! * key order. This arrangement keeps the keys compact in memory, making a
! * search for a particular key more cache-friendly.
*/
typedef struct JsonbContainer
{
*************** typedef struct JsonbContainer
*** 172,179 ****
} JsonbContainer;
/* flags for the header-field in JsonbContainer */
! #define JB_CMASK 0x0FFFFFFF
! #define JB_FSCALAR 0x10000000
#define JB_FOBJECT 0x20000000
#define JB_FARRAY 0x40000000
--- 166,173 ----
} JsonbContainer;
/* flags for the header-field in JsonbContainer */
! #define JB_CMASK 0x0FFFFFFF /* mask for count field */
! #define JB_FSCALAR 0x10000000 /* flag bits */
#define JB_FOBJECT 0x20000000
#define JB_FARRAY 0x40000000
*************** struct JsonbValue
*** 248,265 ****
(jsonbval)->type <= jbvBool)
/*
! * Pair within an Object.
*
! * Pairs with duplicate keys are de-duplicated. We store the order for the
! * benefit of doing so in a well-defined way with respect to the original
! * observed order (which is "last observed wins"). This is only used briefly
! * when originally constructing a Jsonb.
*/
struct JsonbPair
{
JsonbValue key; /* Must be a jbvString */
JsonbValue value; /* May be of any type */
! uint32 order; /* preserves order of pairs with equal keys */
};
/* Conversion state used when parsing Jsonb from text, or for type coercion */
--- 242,261 ----
(jsonbval)->type <= jbvBool)
/*
! * Key/value pair within an Object.
*
! * This struct type is only used briefly while constructing a Jsonb; it is
! * *not* the on-disk representation.
! *
! * Pairs with duplicate keys are de-duplicated. We store the originally
! * observed pair ordering for the purpose of removing duplicates in a
! * well-defined way (which is "last observed wins").
*/
struct JsonbPair
{
JsonbValue key; /* Must be a jbvString */
JsonbValue value; /* May be of any type */
! uint32 order; /* Pair's index in original sequence */
};
/* Conversion state used when parsing Jsonb from text, or for type coercion */
*************** typedef struct JsonbIterator
*** 287,306 ****
{
/* Container being iterated */
JsonbContainer *container;
! uint32 nElems; /* Number of elements in children array (will be
! * nPairs for objects) */
bool isScalar; /* Pseudo-array scalar value? */
! JEntry *children;
! /* Current item in buffer (up to nElems, but must * 2 for objects) */
! int i;
/*
! * Data proper. This points just past end of children array.
! * We use the JBE_OFF() macro on the Jentrys to find offsets of each
! * child in this area.
*/
! char *dataProper;
/* Private state */
JsonbIterState state;
--- 283,307 ----
{
/* Container being iterated */
JsonbContainer *container;
! uint32 nElems; /* Number of elements in children array (will
! * be nPairs for objects) */
bool isScalar; /* Pseudo-array scalar value? */
! JEntry *children; /* JEntrys for child nodes */
! /* Data proper. This points just past end of children array */
! char *dataProper;
! /* Current item in buffer (up to nElems) */
! int curIndex;
!
! /* Data offset corresponding to current item */
! uint32 curDataOffset;
/*
! * If the container is an object, we want to return keys and values
! * alternately; so curDataOffset points to the current key, and
! * curValueOffset points to the current value.
*/
! uint32 curValueOffset;
/* Private state */
JsonbIterState state;
*************** extern Datum gin_consistent_jsonb_path(P
*** 344,349 ****
--- 345,351 ----
extern Datum gin_triconsistent_jsonb_path(PG_FUNCTION_ARGS);
/* Support functions */
+ extern uint32 getJsonbOffset(const JEntry *ja, int index);
extern int compareJsonbContainers(JsonbContainer *a, JsonbContainer *b);
extern JsonbValue *findJsonbValueFromContainer(JsonbContainer *sheader,
uint32 flags,
--
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers