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 (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to