]> granicus.if.org Git - postgresql/blobdiff - src/backend/utils/adt/jsonb_util.c
Fix initialization of fake LSN for unlogged relations
[postgresql] / src / backend / utils / adt / jsonb_util.c
index 832a08d7ee5b06fd1abe22e81ca61d0177db841c..8b739031609d223d40d15d25b17c5d23b7207646 100644 (file)
@@ -3,7 +3,7 @@
  * jsonb_util.c
  *       converting between Jsonb and JsonbValues, and iterating.
  *
- * Copyright (c) 2014, PostgreSQL Global Development Group
+ * Copyright (c) 2014-2019, PostgreSQL Global Development Group
  *
  *
  * IDENTIFICATION
  */
 #include "postgres.h"
 
-#include "access/hash.h"
 #include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
 #include "miscadmin.h"
 #include "utils/builtins.h"
+#include "utils/datetime.h"
+#include "utils/hashutils.h"
+#include "utils/jsonapi.h"
 #include "utils/jsonb.h"
 #include "utils/memutils.h"
+#include "utils/varlena.h"
 
 /*
  * Maximum number of elements in an array (or key/value pairs in an object).
  * 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)
+ * (The total size of an array's or object's elements is also limited by
+ * JENTRY_OFFLENMASK, 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))
 
-/*
- * convertState: a resizeable buffer used when constructing a Jsonb datum
- */
-typedef struct
-{
-       char       *buffer;
-       int                     len;
-       int                     allocatedsz;
-} convertState;
-
-static void fillJsonbValue(JEntry *array, int index, char *base_addr,
-                          JsonbValue *result);
+static void fillJsonbValue(JsonbContainer *container, int index,
+                                                  char *base_addr, uint32 offset,
+                                                  JsonbValue *result);
+static bool equalsJsonbScalarValue(JsonbValue *a, JsonbValue *b);
 static int     compareJsonbScalarValue(JsonbValue *a, JsonbValue *b);
-static int     lexicalCompareJsonbStringValue(const void *a, const void *b);
 static Jsonb *convertToJsonb(JsonbValue *val);
-static void convertJsonbValue(convertState *buffer, JEntry *header, JsonbValue *val, int level);
-static void convertJsonbArray(convertState *buffer, JEntry *header, JsonbValue *val, int level);
-static void convertJsonbObject(convertState *buffer, JEntry *header, JsonbValue *val, int level);
-static void convertJsonbScalar(convertState *buffer, JEntry *header, JsonbValue *scalarVal);
+static void convertJsonbValue(StringInfo buffer, JEntry *header, JsonbValue *val, int level);
+static void convertJsonbArray(StringInfo buffer, JEntry *header, JsonbValue *val, int level);
+static void convertJsonbObject(StringInfo buffer, JEntry *header, JsonbValue *val, int level);
+static void convertJsonbScalar(StringInfo buffer, JEntry *header, JsonbValue *scalarVal);
 
-static int reserveFromBuffer(convertState *buffer, int len);
-static void appendToBuffer(convertState *buffer, char *data, int len);
-static void copyToBuffer(convertState *buffer, int offset, char *data, int len);
-static short padBufferToInt(convertState *buffer);
+static 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);
 
 static JsonbIterator *iteratorFromContainer(JsonbContainer *container, JsonbIterator *parent);
 static JsonbIterator *freeAndGetParent(JsonbIterator *it);
@@ -64,21 +59,26 @@ static void appendKey(JsonbParseState *pstate, JsonbValue *scalarVal);
 static void appendValue(JsonbParseState *pstate, JsonbValue *scalarVal);
 static void appendElement(JsonbParseState *pstate, JsonbValue *scalarVal);
 static int     lengthCompareJsonbStringValue(const void *a, const void *b);
+static int     lengthCompareJsonbString(const char *val1, int len1,
+                                                                        const char *val2, int len2);
 static int     lengthCompareJsonbPair(const void *a, const void *b, void *arg);
 static void uniqueifyJsonbObject(JsonbValue *object);
+static JsonbValue *pushJsonbValueScalar(JsonbParseState **pstate,
+                                                                               JsonbIteratorToken seq,
+                                                                               JsonbValue *scalarVal);
 
 /*
  * Turn an in-memory JsonbValue into a Jsonb for on-disk storage.
  *
  * There isn't a JsonbToJsonbValue(), because generally we find it more
  * convenient to directly iterate through the Jsonb representation and only
- * really convert nested scalar values.  formIterIsContainer() does this, so
- * that clients of the iteration code don't have to directly deal with the
- * binary representation (JsonbDeepContains() is a notable exception, although
- * all exceptions are internal to this module).  In general, functions that
- * accept a JsonbValue argument are concerned with the manipulation of scalar
- * values, or simple containers of scalar values, where it would be
- * inconvenient to deal with a great amount of other state.
+ * really convert nested scalar values.  JsonbIteratorNext() does this, so that
+ * clients of the iteration code don't have to directly deal with the binary
+ * representation (JsonbDeepContains() is a notable exception, although all
+ * exceptions are internal to this module).  In general, functions that accept
+ * a JsonbValue argument are concerned with the manipulation of scalar values,
+ * or simple containers of scalar values, where it would be inconvenient to
+ * deal with a great amount of other state.
  */
 Jsonb *
 JsonbValueToJsonb(JsonbValue *val)
@@ -117,6 +117,58 @@ JsonbValueToJsonb(JsonbValue *val)
        return out;
 }
 
+/*
+ * 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.
+ */
+uint32
+getJsonbOffset(const JsonbContainer *jc, int index)
+{
+       uint32          offset = 0;
+       int                     i;
+
+       /*
+        * Start offset of this entry is equal to the end offset of the previous
+        * entry.  Walk backwards to the most recent entry stored as an end
+        * offset, returning that offset plus any lengths in between.
+        */
+       for (i = index - 1; i >= 0; i--)
+       {
+               offset += JBE_OFFLENFLD(jc->children[i]);
+               if (JBE_HAS_OFF(jc->children[i]))
+                       break;
+       }
+
+       return offset;
+}
+
+/*
+ * Get the length of the variable-length portion of a Jsonb node.
+ * The node is identified by index within the container's JEntry array.
+ */
+uint32
+getJsonbLength(const JsonbContainer *jc, int index)
+{
+       uint32          off;
+       uint32          len;
+
+       /*
+        * If the length is stored directly in the JEntry, just return it.
+        * Otherwise, get the begin offset of the entry, and subtract that from
+        * the stored end+1 offset.
+        */
+       if (JBE_HAS_OFF(jc->children[index]))
+       {
+               off = getJsonbOffset(jc, index);
+               len = JBE_OFFLENFLD(jc->children[index]) - off;
+       }
+       else
+               len = JBE_OFFLENFLD(jc->children[index]);
+
+       return len;
+}
+
 /*
  * 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
@@ -141,19 +193,12 @@ compareJsonbContainers(JsonbContainer *a, JsonbContainer *b)
        {
                JsonbValue      va,
                                        vb;
-               int                     ra,
+               JsonbIteratorToken ra,
                                        rb;
 
                ra = JsonbIteratorNext(&ita, &va, false);
                rb = JsonbIteratorNext(&itb, &vb, false);
 
-               /*
-                * To a limited extent we'll redundantly iterate over an array/object
-                * while re-performing the same test without any reasonable
-                * expectation of the same container types having differing lengths
-                * (as when we process a WJB_BEGIN_OBJECT, and later the corresponding
-                * WJB_END_OBJECT), but no matter.
-                */
                if (ra == rb)
                {
                        if (ra == WJB_DONE)
@@ -162,13 +207,22 @@ compareJsonbContainers(JsonbContainer *a, JsonbContainer *b)
                                break;
                        }
 
+                       if (ra == WJB_END_ARRAY || ra == WJB_END_OBJECT)
+                       {
+                               /*
+                                * There is no array or object to compare at this stage of
+                                * processing.  jbvArray/jbvObject values are compared
+                                * initially, at the WJB_BEGIN_ARRAY and WJB_BEGIN_OBJECT
+                                * tokens.
+                                */
+                               continue;
+                       }
+
                        if (va.type == vb.type)
                        {
                                switch (va.type)
                                {
                                        case jbvString:
-                                               res = lexicalCompareJsonbStringValue(&va, &vb);
-                                               break;
                                        case jbvNull:
                                        case jbvNumeric:
                                        case jbvBool:
@@ -193,6 +247,10 @@ compareJsonbContainers(JsonbContainer *a, JsonbContainer *b)
                                                break;
                                        case jbvBinary:
                                                elog(ERROR, "unexpected jbvBinary value");
+                                               break;
+                                       case jbvDatetime:
+                                               elog(ERROR, "unexpected jbvDatetime value");
+                                               break;
                                }
                        }
                        else
@@ -204,19 +262,26 @@ compareJsonbContainers(JsonbContainer *a, JsonbContainer *b)
                else
                {
                        /*
-                        * It's safe to assume that the types differed.
+                        * It's safe to assume that the types differed, and that the va
+                        * and vb values passed were set.
                         *
-                        * If the two values were the same container type, then there'd
+                        * 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
-                        * can't be scalar types either, because then they'd have to be
-                        * contained in containers already ruled unequal due to differing
-                        * numbers of pairs/elements, or already directly ruled unequal
-                        * with a call to the underlying type's comparator.
+                        * elements/pairs (when processing WJB_BEGIN_OBJECT, say). They're
+                        * either two heterogeneously-typed containers, or a container and
+                        * some scalar type.
+                        *
+                        * We don't have to consider the WJB_END_ARRAY and WJB_END_OBJECT
+                        * cases here, because we would have seen the corresponding
+                        * WJB_BEGIN_ARRAY and WJB_BEGIN_OBJECT tokens first, and
+                        * concluded that they don't match.
                         */
+                       Assert(ra != WJB_END_ARRAY && ra != WJB_END_OBJECT);
+                       Assert(rb != WJB_END_ARRAY && rb != WJB_END_OBJECT);
+
                        Assert(va.type != vb.type);
-                       Assert(va.type == jbvArray || va.type == jbvObject);
-                       Assert(vb.type == jbvArray || vb.type == jbvObject);
+                       Assert(va.type != jbvBinary);
+                       Assert(vb.type != jbvBinary);
                        /* Type-defined order */
                        res = (va.type > vb.type) ? 1 : -1;
                }
@@ -272,77 +337,117 @@ findJsonbValueFromContainer(JsonbContainer *container, uint32 flags,
                                                        JsonbValue *key)
 {
        JEntry     *children = container->children;
-       int                     count = (container->header & JB_CMASK);
-       JsonbValue *result = palloc(sizeof(JsonbValue));
+       int                     count = JsonContainerSize(container);
 
        Assert((flags & ~(JB_FARRAY | JB_FOBJECT)) == 0);
 
-       if (flags & JB_FARRAY & container->header)
+       /* Quick out without a palloc cycle if object/array is empty */
+       if (count <= 0)
+               return NULL;
+
+       if ((flags & JB_FARRAY) && JsonContainerIsArray(container))
        {
+               JsonbValue *result = palloc(sizeof(JsonbValue));
                char       *base_addr = (char *) (children + count);
+               uint32          offset = 0;
                int                     i;
 
                for (i = 0; i < count; i++)
                {
-                       fillJsonbValue(children, i, base_addr, result);
+                       fillJsonbValue(container, i, base_addr, offset, result);
 
                        if (key->type == result->type)
                        {
-                               if (compareJsonbScalarValue(key, result) == 0)
+                               if (equalsJsonbScalarValue(key, result))
                                        return result;
                        }
+
+                       JBE_ADVANCE_OFFSET(offset, children[i]);
                }
+
+               pfree(result);
        }
-       else if (flags & JB_FOBJECT & container->header)
+       else if ((flags & JB_FOBJECT) && JsonContainerIsObject(container))
        {
-               /* Since this is an object, account for *Pairs* of Jentrys */
-               char       *base_addr = (char *) (children + count * 2);
-               uint32          stopLow = 0,
-                                       stopMiddle;
-
-               /* Object key past by caller must be a string */
+               /* Object key passed 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;
+               return getKeyJsonValueFromContainer(container, key->val.string.val,
+                                                                                       key->val.string.len, NULL);
+       }
 
-                       /*
-                        * Note how we compensate for the fact that we're iterating
-                        * through pairs (not entries) throughout.
-                        */
-                       stopMiddle = stopLow + (count - stopLow) / 2;
+       /* Not found */
+       return NULL;
+}
 
-                       index = stopMiddle * 2;
+/*
+ * Find value by key in Jsonb object and fetch it into 'res', which is also
+ * returned.
+ *
+ * 'res' can be passed in as NULL, in which case it's newly palloc'ed here.
+ */
+JsonbValue *
+getKeyJsonValueFromContainer(JsonbContainer *container,
+                                                        const char *keyVal, int keyLen, JsonbValue *res)
+{
+       JEntry     *children = container->children;
+       int                     count = JsonContainerSize(container);
+       char       *baseAddr;
+       uint32          stopLow,
+                               stopHigh;
 
-                       candidate.type = jbvString;
-                       candidate.val.string.val = base_addr + JBE_OFF(children, index);
-                       candidate.val.string.len = JBE_LEN(children, index);
+       Assert(JsonContainerIsObject(container));
 
-                       difference = lengthCompareJsonbStringValue(&candidate, key);
+       /* Quick out without a palloc cycle if object is empty */
+       if (count <= 0)
+               return NULL;
 
-                       if (difference == 0)
-                       {
-                               /* Found our key, return value */
-                               fillJsonbValue(children, index + 1, base_addr, result);
+       /*
+        * Binary search the container. Since we know this is an object, account
+        * for *Pairs* of Jentrys
+        */
+       baseAddr = (char *) (children + count * 2);
+       stopLow = 0;
+       stopHigh = count;
+       while (stopLow < stopHigh)
+       {
+               uint32          stopMiddle;
+               int                     difference;
+               const char *candidateVal;
+               int                     candidateLen;
 
-                               return result;
-                       }
+               stopMiddle = stopLow + (stopHigh - stopLow) / 2;
+
+               candidateVal = baseAddr + getJsonbOffset(container, stopMiddle);
+               candidateLen = getJsonbLength(container, stopMiddle);
+
+               difference = lengthCompareJsonbString(candidateVal, candidateLen,
+                                                                                         keyVal, keyLen);
+
+               if (difference == 0)
+               {
+                       /* Found our key, return corresponding value */
+                       int                     index = stopMiddle + count;
+
+                       if (!res)
+                               res = palloc(sizeof(JsonbValue));
+
+                       fillJsonbValue(container, index, baseAddr,
+                                                  getJsonbOffset(container, index),
+                                                  res);
+
+                       return res;
+               }
+               else
+               {
+                       if (difference < 0)
+                               stopLow = stopMiddle + 1;
                        else
-                       {
-                               if (difference < 0)
-                                       stopLow = stopMiddle + 1;
-                               else
-                                       count = stopMiddle;
-                       }
+                               stopHigh = stopMiddle;
                }
        }
 
        /* Not found */
-       pfree(result);
        return NULL;
 }
 
@@ -358,10 +463,10 @@ getIthJsonbValueFromContainer(JsonbContainer *container, uint32 i)
        char       *base_addr;
        uint32          nelements;
 
-       if ((container->header & JB_FARRAY) == 0)
+       if (!JsonContainerIsArray(container))
                elog(ERROR, "not a jsonb array");
 
-       nelements = container->header & JB_CMASK;
+       nelements = JsonContainerSize(container);
        base_addr = (char *) &container->children[nelements];
 
        if (i >= nelements)
@@ -369,7 +474,9 @@ getIthJsonbValueFromContainer(JsonbContainer *container, uint32 i)
 
        result = palloc(sizeof(JsonbValue));
 
-       fillJsonbValue(container->children, i, base_addr, result);
+       fillJsonbValue(container, i, base_addr,
+                                  getJsonbOffset(container, i),
+                                  result);
 
        return result;
 }
@@ -378,13 +485,20 @@ getIthJsonbValueFromContainer(JsonbContainer *container, uint32 i)
  * 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 container->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 that work across multiple
+ * children.  When it can't, it can just call getJsonbOffset().
+ *
  * 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)
+fillJsonbValue(JsonbContainer *container, int index,
+                          char *base_addr, uint32 offset,
+                          JsonbValue *result)
 {
-       JEntry          entry = children[index];
+       JEntry          entry = container->children[index];
 
        if (JBE_ISNULL(entry))
        {
@@ -393,14 +507,14 @@ fillJsonbValue(JEntry *children, int index, char *base_addr, JsonbValue *result)
        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);
+               result->val.string.val = base_addr + offset;
+               result->val.string.len = getJsonbLength(container, 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)));
+               result->val.numeric = (Numeric) (base_addr + INTALIGN(offset));
        }
        else if (JBE_ISBOOL_TRUE(entry))
        {
@@ -416,8 +530,10 @@ fillJsonbValue(JEntry *children, int index, char *base_addr, JsonbValue *result)
        {
                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));
+               /* Remove alignment padding from data pointer and length */
+               result->val.binary.data = (JsonbContainer *) (base_addr + INTALIGN(offset));
+               result->val.binary.len = getJsonbLength(container, index) -
+                       (INTALIGN(offset) - offset);
        }
 }
 
@@ -432,11 +548,44 @@ fillJsonbValue(JEntry *children, int index, char *base_addr, JsonbValue *result)
  *
  * Only sequential tokens pertaining to non-container types should pass a
  * JsonbValue.  There is one exception -- WJB_BEGIN_ARRAY callers may pass a
- * "raw scalar" pseudo array to append that.
+ * "raw scalar" pseudo array to append it - the actual scalar should be passed
+ * next and it will be added as the only member of the array.
+ *
+ * Values of type jbvBinary, which are rolled up arrays and objects,
+ * are unpacked before being added to the result.
  */
 JsonbValue *
 pushJsonbValue(JsonbParseState **pstate, JsonbIteratorToken seq,
-                          JsonbValue *scalarVal)
+                          JsonbValue *jbval)
+{
+       JsonbIterator *it;
+       JsonbValue *res = NULL;
+       JsonbValue      v;
+       JsonbIteratorToken tok;
+
+       if (!jbval || (seq != WJB_ELEM && seq != WJB_VALUE) ||
+               jbval->type != jbvBinary)
+       {
+               /* drop through */
+               return pushJsonbValueScalar(pstate, seq, jbval);
+       }
+
+       /* unpack the binary and add each piece to the pstate */
+       it = JsonbIteratorInit(jbval->val.binary.data);
+       while ((tok = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
+               res = pushJsonbValueScalar(pstate, tok,
+                                                                  tok < WJB_BEGIN_ARRAY ? &v : NULL);
+
+       return res;
+}
+
+/*
+ * Do the actual pushing, with only scalar or pseudo-scalar-array values
+ * accepted.
+ */
+static JsonbValue *
+pushJsonbValueScalar(JsonbParseState **pstate, JsonbIteratorToken seq,
+                                        JsonbValue *scalarVal)
 {
        JsonbValue *result = NULL;
 
@@ -449,7 +598,7 @@ pushJsonbValue(JsonbParseState **pstate, JsonbIteratorToken seq,
                        (*pstate)->contVal.type = jbvArray;
                        (*pstate)->contVal.val.array.nElems = 0;
                        (*pstate)->contVal.val.array.rawScalar = (scalarVal &&
-                                                                                        scalarVal->val.array.rawScalar);
+                                                                                                         scalarVal->val.array.rawScalar);
                        if (scalarVal && scalarVal->val.array.nElems > 0)
                        {
                                /* Assume that this array is still really a scalar */
@@ -478,13 +627,11 @@ pushJsonbValue(JsonbParseState **pstate, JsonbIteratorToken seq,
                        appendKey(*pstate, scalarVal);
                        break;
                case WJB_VALUE:
-                       Assert(IsAJsonbScalar(scalarVal) ||
-                                  scalarVal->type == jbvBinary);
+                       Assert(IsAJsonbScalar(scalarVal));
                        appendValue(*pstate, scalarVal);
                        break;
                case WJB_ELEM:
-                       Assert(IsAJsonbScalar(scalarVal) ||
-                                  scalarVal->type == jbvBinary);
+                       Assert(IsAJsonbScalar(scalarVal));
                        appendElement(*pstate, scalarVal);
                        break;
                case WJB_END_OBJECT:
@@ -640,7 +787,9 @@ JsonbIteratorInit(JsonbContainer *container)
  * It is our job to expand the jbvBinary representation without bothering them
  * with it.  However, clients should not take it upon themselves to touch array
  * or Object element/pair buffers, since their element/pair pointers are
- * garbage.
+ * garbage.  Also, *val will not be set when returning WJB_END_ARRAY or
+ * WJB_END_OBJECT, on the assumption that it's only useful to access values
+ * when recursing in.
  */
 JsonbIteratorToken
 JsonbIteratorNext(JsonbIterator **it, JsonbValue *val, bool skipNested)
@@ -667,13 +816,15 @@ recurse:
                         * a full conversion
                         */
                        val->val.array.rawScalar = (*it)->isScalar;
-                       (*it)->i = 0;
+                       (*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)->i >= (*it)->nElems)
+                       if ((*it)->curIndex >= (*it)->nElems)
                        {
                                /*
                                 * All elements within array already processed.  Report this
@@ -685,7 +836,13 @@ recurse:
                                return WJB_END_ARRAY;
                        }
 
-                       fillJsonbValue((*it)->children, (*it)->i++, (*it)->dataProper, val);
+                       fillJsonbValue((*it)->container, (*it)->curIndex,
+                                                  (*it)->dataProper, (*it)->curDataOffset,
+                                                  val);
+
+                       JBE_ADVANCE_OFFSET((*it)->curDataOffset,
+                                                          (*it)->children[(*it)->curIndex]);
+                       (*it)->curIndex++;
 
                        if (!IsAJsonbScalar(val) && !skipNested)
                        {
@@ -696,8 +853,8 @@ recurse:
                        else
                        {
                                /*
-                                * Scalar item in array (or a container and caller didn't
-                                * want us to recurse into it.
+                                * Scalar item in array, or a container and caller didn't want
+                                * us to recurse into it.
                                 */
                                return WJB_ELEM;
                        }
@@ -711,13 +868,16 @@ recurse:
                         * v->val.object.pairs is not actually set, because we aren't
                         * doing a full conversion
                         */
-                       (*it)->i = 0;
+                       (*it)->curIndex = 0;
+                       (*it)->curDataOffset = 0;
+                       (*it)->curValueOffset = getJsonbOffset((*it)->container,
+                                                                                                  (*it)->nElems);
                        /* Set state for next call */
                        (*it)->state = JBI_OBJECT_KEY;
                        return WJB_BEGIN_OBJECT;
 
                case JBI_OBJECT_KEY:
-                       if ((*it)->i >= (*it)->nElems)
+                       if ((*it)->curIndex >= (*it)->nElems)
                        {
                                /*
                                 * All pairs within object already processed.  Report this to
@@ -731,7 +891,9 @@ recurse:
                        else
                        {
                                /* Return key of a key/value pair.  */
-                               fillJsonbValue((*it)->children, (*it)->i * 2, (*it)->dataProper, val);
+                               fillJsonbValue((*it)->container, (*it)->curIndex,
+                                                          (*it)->dataProper, (*it)->curDataOffset,
+                                                          val);
                                if (val->type != jbvString)
                                        elog(ERROR, "unexpected jsonb type as object key");
 
@@ -744,8 +906,15 @@ recurse:
                        /* Set state for next call */
                        (*it)->state = JBI_OBJECT_KEY;
 
-                       fillJsonbValue((*it)->children, ((*it)->i++) * 2 + 1,
-                                                  (*it)->dataProper, val);
+                       fillJsonbValue((*it)->container, (*it)->curIndex + (*it)->nElems,
+                                                  (*it)->dataProper, (*it)->curValueOffset,
+                                                  val);
+
+                       JBE_ADVANCE_OFFSET((*it)->curDataOffset,
+                                                          (*it)->children[(*it)->curIndex]);
+                       JBE_ADVANCE_OFFSET((*it)->curValueOffset,
+                                                          (*it)->children[(*it)->curIndex + (*it)->nElems]);
+                       (*it)->curIndex++;
 
                        /*
                         * Value may be a container, in which case we recurse with new,
@@ -773,10 +942,10 @@ iteratorFromContainer(JsonbContainer *container, JsonbIterator *parent)
 {
        JsonbIterator *it;
 
-       it = palloc(sizeof(JsonbIterator));
+       it = palloc0(sizeof(JsonbIterator));
        it->container = container;
        it->parent = parent;
-       it->nElems = container->header & JB_CMASK;
+       it->nElems = JsonContainerSize(container);
 
        /* Array starts just after header */
        it->children = container->children;
@@ -786,7 +955,7 @@ iteratorFromContainer(JsonbContainer *container, JsonbIterator *parent)
                case JB_FARRAY:
                        it->dataProper =
                                (char *) it->children + it->nElems * sizeof(JEntry);
-                       it->isScalar = (container->header & JB_FSCALAR) != 0;
+                       it->isScalar = JsonContainerIsScalar(container);
                        /* This is either a "raw scalar", or an array */
                        Assert(!it->isScalar || it->nElems == 1);
 
@@ -794,11 +963,6 @@ iteratorFromContainer(JsonbContainer *container, JsonbIterator *parent)
                        break;
 
                case JB_FOBJECT:
-
-                       /*
-                        * Offset reflects that nElems indicates JsonbPairs in an object.
-                        * Each key and each value contain Jentry metadata just the same.
-                        */
                        it->dataProper =
                                (char *) it->children + it->nElems * sizeof(JEntry) * 2;
                        it->state = JBI_OBJECT_START;
@@ -839,10 +1003,10 @@ freeAndGetParent(JsonbIterator *it)
 bool
 JsonbDeepContains(JsonbIterator **val, JsonbIterator **mContained)
 {
-       uint32          rval,
-                               rcont;
        JsonbValue      vval,
                                vcontained;
+       JsonbIteratorToken rval,
+                               rcont;
 
        /*
         * Guard against stack overflow due to overly complex Jsonb.
@@ -869,13 +1033,25 @@ JsonbDeepContains(JsonbIterator **val, JsonbIterator **mContained)
        }
        else if (rcont == WJB_BEGIN_OBJECT)
        {
-               JsonbValue *lhsVal;             /* lhsVal is from pair in lhs object */
-
+               Assert(vval.type == jbvObject);
                Assert(vcontained.type == jbvObject);
 
+               /*
+                * If the lhs has fewer pairs than the rhs, it can't possibly contain
+                * the rhs.  (This conclusion is safe only because we de-duplicate
+                * keys in all Jsonb objects; thus there can be no corresponding
+                * optimization in the array case.)  The case probably won't arise
+                * often, but since it's such a cheap check we may as well make it.
+                */
+               if (vval.val.object.nPairs < vcontained.val.object.nPairs)
+                       return false;
+
                /* Work through rhs "is it contained within?" object */
                for (;;)
                {
+                       JsonbValue *lhsVal; /* lhsVal is from pair in lhs object */
+                       JsonbValue      lhsValBuf;
+
                        rcont = JsonbIteratorNext(mContained, &vcontained, false);
 
                        /*
@@ -887,12 +1063,14 @@ JsonbDeepContains(JsonbIterator **val, JsonbIterator **mContained)
                                return true;
 
                        Assert(rcont == WJB_KEY);
+                       Assert(vcontained.type == jbvString);
 
                        /* First, find value by key... */
-                       lhsVal = findJsonbValueFromContainer((*val)->container,
-                                                                                                JB_FOBJECT,
-                                                                                                &vcontained);
-
+                       lhsVal =
+                               getKeyJsonValueFromContainer((*val)->container,
+                                                                                        vcontained.val.string.val,
+                                                                                        vcontained.val.string.len,
+                                                                                        &lhsValBuf);
                        if (!lhsVal)
                                return false;
 
@@ -914,7 +1092,7 @@ JsonbDeepContains(JsonbIterator **val, JsonbIterator **mContained)
                        }
                        else if (IsAJsonbScalar(lhsVal))
                        {
-                               if (compareJsonbScalarValue(lhsVal, &vcontained) != 0)
+                               if (!equalsJsonbScalarValue(lhsVal, &vcontained))
                                        return false;
                        }
                        else
@@ -959,6 +1137,7 @@ JsonbDeepContains(JsonbIterator **val, JsonbIterator **mContained)
                JsonbValue *lhsConts = NULL;
                uint32          nLhsElems = vval.val.array.nElems;
 
+               Assert(vval.type == jbvArray);
                Assert(vcontained.type == jbvArray);
 
                /*
@@ -1093,10 +1272,11 @@ JsonbHashScalarValue(const JsonbValue *scalarVal, uint32 *hash)
                case jbvNumeric:
                        /* Must hash equal numerics to equal hash codes */
                        tmp = DatumGetUInt32(DirectFunctionCall1(hash_numeric,
-                                                                  NumericGetDatum(scalarVal->val.numeric)));
+                                                                                                        NumericGetDatum(scalarVal->val.numeric)));
                        break;
                case jbvBool:
                        tmp = scalarVal->val.boolean ? 0x02 : 0x04;
+
                        break;
                default:
                        elog(ERROR, "invalid jsonb scalar type");
@@ -1113,82 +1293,136 @@ JsonbHashScalarValue(const JsonbValue *scalarVal, uint32 *hash)
        *hash ^= tmp;
 }
 
+/*
+ * Hash a value to a 64-bit value, with a seed. Otherwise, similar to
+ * JsonbHashScalarValue.
+ */
+void
+JsonbHashScalarValueExtended(const JsonbValue *scalarVal, uint64 *hash,
+                                                        uint64 seed)
+{
+       uint64          tmp;
+
+       switch (scalarVal->type)
+       {
+               case jbvNull:
+                       tmp = seed + 0x01;
+                       break;
+               case jbvString:
+                       tmp = DatumGetUInt64(hash_any_extended((const unsigned char *) scalarVal->val.string.val,
+                                                                                                  scalarVal->val.string.len,
+                                                                                                  seed));
+                       break;
+               case jbvNumeric:
+                       tmp = DatumGetUInt64(DirectFunctionCall2(hash_numeric_extended,
+                                                                                                        NumericGetDatum(scalarVal->val.numeric),
+                                                                                                        UInt64GetDatum(seed)));
+                       break;
+               case jbvBool:
+                       if (seed)
+                               tmp = DatumGetUInt64(DirectFunctionCall2(hashcharextended,
+                                                                                                                BoolGetDatum(scalarVal->val.boolean),
+                                                                                                                UInt64GetDatum(seed)));
+                       else
+                               tmp = scalarVal->val.boolean ? 0x02 : 0x04;
+
+                       break;
+               default:
+                       elog(ERROR, "invalid jsonb scalar type");
+                       break;
+       }
+
+       *hash = ROTATE_HIGH_AND_LOW_32BITS(*hash);
+       *hash ^= tmp;
+}
+
 /*
  * Are two scalar JsonbValues of the same type a and b equal?
- *
- * Does not use lexical comparisons.  Therefore, it is essentially that this
- * never be used against Strings for anything other than searching for values
- * within a single jsonb.
  */
-static int
-compareJsonbScalarValue(JsonbValue *aScalar, JsonbValue *bScalar)
+static bool
+equalsJsonbScalarValue(JsonbValue *aScalar, JsonbValue *bScalar)
 {
        if (aScalar->type == bScalar->type)
        {
                switch (aScalar->type)
                {
                        case jbvNull:
-                               return 0;
+                               return true;
                        case jbvString:
-                               return lengthCompareJsonbStringValue(aScalar, bScalar);
+                               return lengthCompareJsonbStringValue(aScalar, bScalar) == 0;
                        case jbvNumeric:
-                               return DatumGetInt32(DirectFunctionCall2(numeric_cmp,
-                                                                          PointerGetDatum(aScalar->val.numeric),
-                                                                        PointerGetDatum(bScalar->val.numeric)));
+                               return DatumGetBool(DirectFunctionCall2(numeric_eq,
+                                                                                                               PointerGetDatum(aScalar->val.numeric),
+                                                                                                               PointerGetDatum(bScalar->val.numeric)));
                        case jbvBool:
-                               if (aScalar->val.boolean != bScalar->val.boolean)
-                                       return (aScalar->val.boolean > bScalar->val.boolean) ? 1 : -1;
-                               else
-                                       return 0;
+                               return aScalar->val.boolean == bScalar->val.boolean;
+
                        default:
                                elog(ERROR, "invalid jsonb scalar type");
                }
        }
        elog(ERROR, "jsonb scalar type mismatch");
-       return -1;
+       return false;
 }
 
 /*
- * Standard lexical qsort() comparator of jsonb strings.
+ * Compare two scalar JsonbValues, returning -1, 0, or 1.
  *
- * Sorts strings lexically, using the default database collation.  Used by
- * B-Tree operators, where a lexical sort order is generally expected.
+ * Strings are compared using the default collation.  Used by B-tree
+ * operators, where a lexical sort order is generally expected.
  */
 static int
-lexicalCompareJsonbStringValue(const void *a, const void *b)
+compareJsonbScalarValue(JsonbValue *aScalar, JsonbValue *bScalar)
 {
-       const JsonbValue *va = (const JsonbValue *) a;
-       const JsonbValue *vb = (const JsonbValue *) b;
-
-       Assert(va->type == jbvString);
-       Assert(vb->type == jbvString);
-
-       return varstr_cmp(va->val.string.val, va->val.string.len, vb->val.string.val,
-                                         vb->val.string.len, DEFAULT_COLLATION_OID);
+       if (aScalar->type == bScalar->type)
+       {
+               switch (aScalar->type)
+               {
+                       case jbvNull:
+                               return 0;
+                       case jbvString:
+                               return varstr_cmp(aScalar->val.string.val,
+                                                                 aScalar->val.string.len,
+                                                                 bScalar->val.string.val,
+                                                                 bScalar->val.string.len,
+                                                                 DEFAULT_COLLATION_OID);
+                       case jbvNumeric:
+                               return DatumGetInt32(DirectFunctionCall2(numeric_cmp,
+                                                                                                                PointerGetDatum(aScalar->val.numeric),
+                                                                                                                PointerGetDatum(bScalar->val.numeric)));
+                       case jbvBool:
+                               if (aScalar->val.boolean == bScalar->val.boolean)
+                                       return 0;
+                               else if (aScalar->val.boolean > bScalar->val.boolean)
+                                       return 1;
+                               else
+                                       return -1;
+                       default:
+                               elog(ERROR, "invalid jsonb scalar type");
+               }
+       }
+       elog(ERROR, "jsonb scalar type mismatch");
+       return -1;
 }
 
 
 /*
- * Functions for manipulating the resizeable buffer used by convertJsonb and
+ * Functions for manipulating the resizable buffer used by convertJsonb and
  * its subroutines.
  */
 
 /*
  * Reserve 'len' bytes, at the end of the buffer, enlarging it if necessary.
- * Returns the offset to the reserved area. The caller is expected to copy
- * the data to the reserved area later with copyToBuffer()
+ * Returns the offset to the reserved area. The caller is expected to fill
+ * the reserved area later with copyToBuffer().
  */
 static int
-reserveFromBuffer(convertState *buffer, int len)
+reserveFromBuffer(StringInfo buffer, int len)
 {
        int                     offset;
 
        /* Make more room if needed */
-       if (buffer->len + len > buffer->allocatedsz)
-       {
-               buffer->allocatedsz *= 2;
-               buffer->buffer = repalloc(buffer->buffer, buffer->allocatedsz);
-       }
+       enlargeStringInfo(buffer, len);
 
        /* remember current offset */
        offset = buffer->len;
@@ -1196,6 +1430,12 @@ reserveFromBuffer(convertState *buffer, int len)
        /* reserve the space */
        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';
+
        return offset;
 }
 
@@ -1203,16 +1443,16 @@ reserveFromBuffer(convertState *buffer, int len)
  * Copy 'len' bytes to a previously reserved area in buffer.
  */
 static void
-copyToBuffer(convertState *buffer, int offset, char *data, int len)
+copyToBuffer(StringInfo buffer, int offset, const char *data, int len)
 {
-       memcpy(buffer->buffer + offset, data, len);
+       memcpy(buffer->data + offset, data, len);
 }
 
 /*
  * A shorthand for reserveFromBuffer + copyToBuffer.
  */
 static void
-appendToBuffer(convertState *buffer, char *data, int len)
+appendToBuffer(StringInfo buffer, const char *data, int len)
 {
        int                     offset;
 
@@ -1226,17 +1466,19 @@ appendToBuffer(convertState *buffer, char *data, int len)
  * Returns the number of padding bytes appended.
  */
 static short
-padBufferToInt(convertState *buffer)
+padBufferToInt(StringInfo buffer)
 {
-       short           padlen,
-                               p;
-       int                     offset;
+       int                     padlen,
+                               p,
+                               offset;
 
        padlen = INTALIGN(buffer->len) - buffer->len;
 
        offset = reserveFromBuffer(buffer, padlen);
+
+       /* padlen must be small, so this is probably faster than a memset */
        for (p = 0; p < padlen; p++)
-               buffer->buffer[offset + p] = 0;
+               buffer->data[offset + p] = '\0';
 
        return padlen;
 }
@@ -1247,7 +1489,7 @@ padBufferToInt(convertState *buffer)
 static Jsonb *
 convertToJsonb(JsonbValue *val)
 {
-       convertState buffer;
+       StringInfoData buffer;
        JEntry          jentry;
        Jsonb      *res;
 
@@ -1255,22 +1497,20 @@ convertToJsonb(JsonbValue *val)
        Assert(val->type != jbvBinary);
 
        /* Allocate an output buffer. It will be enlarged as needed */
-       buffer.buffer = palloc(128);
-       buffer.len = 0;
-       buffer.allocatedsz = 128;
+       initStringInfo(&buffer);
 
        /* Make room for the varlena header */
-       reserveFromBuffer(&buffer, sizeof(VARHDRSZ));
+       reserveFromBuffer(&buffer, VARHDRSZ);
 
        convertJsonbValue(&buffer, &jentry, val, 0);
 
        /*
         * 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.
+        * JsonbContainer struct must contain enough information to tell what kind
+        * of value it is.
         */
 
-       res = (Jsonb *) buffer.buffer;
+       res = (Jsonb *) buffer.data;
 
        SET_VARSIZE(res, buffer.len);
 
@@ -1280,158 +1520,241 @@ convertToJsonb(JsonbValue *val)
 /*
  * Subroutine of convertJsonb: serialize a single JsonbValue into buffer.
  *
- * The JEntry header for this node is returned in *header. It is filled in
- * with the length of this value, but if it is stored in an array or an
- * object (which is always, except for the root node), it is the caller's
- * responsibility to adjust it with the offset within the container.
+ * The JEntry header for this node is returned in *header.  It is filled in
+ * with the length of this value and appropriate type bits.  If we wish to
+ * store an end offset rather than a length, it is the caller's responsibility
+ * to adjust for that.
  *
  * If the value is an array or an object, this recurses. 'level' is only used
  * for debugging purposes.
  */
 static void
-convertJsonbValue(convertState *buffer, JEntry *header, JsonbValue *val, int level)
+convertJsonbValue(StringInfo buffer, JEntry *header, JsonbValue *val, int level)
 {
        check_stack_depth();
 
        if (!val)
                return;
 
-       if (IsAJsonbScalar(val) || val->type == jbvBinary)
+       /*
+        * 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))
                convertJsonbScalar(buffer, header, val);
        else if (val->type == jbvArray)
                convertJsonbArray(buffer, header, val, level);
        else if (val->type == jbvObject)
                convertJsonbObject(buffer, header, val, level);
        else
-               elog(ERROR, "unknown type of jsonb container");
+               elog(ERROR, "unknown type of jsonb container to convert");
 }
 
 static void
-convertJsonbArray(convertState *buffer, JEntry *pheader, JsonbValue *val, int level)
+convertJsonbArray(StringInfo buffer, JEntry *pheader, JsonbValue *val, int level)
 {
-       int                     offset;
-       int                     metaoffset;
+       int                     base_offset;
+       int                     jentry_offset;
        int                     i;
        int                     totallen;
        uint32          header;
+       int                     nElems = val->val.array.nElems;
 
-       /* Initialize pointer into conversion buffer at this level */
-       offset = buffer->len;
+       /* Remember where in the buffer this array starts. */
+       base_offset = buffer->len;
 
+       /* Align to 4-byte boundary (any padding counts as part of my data) */
        padBufferToInt(buffer);
 
        /*
-        * Construct the header Jentry, stored in the beginning of the variable-
-        * length payload.
+        * Construct the header Jentry and store it in the beginning of the
+        * variable-length payload.
         */
-       header = val->val.array.nElems | JB_FARRAY;
+       header = nElems | JB_FARRAY;
        if (val->val.array.rawScalar)
        {
-               Assert(val->val.array.nElems == 1);
+               Assert(nElems == 1);
                Assert(level == 0);
                header |= JB_FSCALAR;
        }
 
        appendToBuffer(buffer, (char *) &header, sizeof(uint32));
-       /* reserve space for the JEntries of the elements. */
-       metaoffset = reserveFromBuffer(buffer, sizeof(JEntry) * val->val.array.nElems);
+
+       /* Reserve space for the JEntries of the elements. */
+       jentry_offset = reserveFromBuffer(buffer, sizeof(JEntry) * nElems);
 
        totallen = 0;
-       for (i = 0; i < val->val.array.nElems; i++)
+       for (i = 0; i < nElems; i++)
        {
                JsonbValue *elem = &val->val.array.elems[i];
                int                     len;
                JEntry          meta;
 
+               /*
+                * Convert element, producing a JEntry and appending its
+                * variable-length data to buffer
+                */
                convertJsonbValue(buffer, &meta, elem, level + 1);
-               len = meta & JENTRY_POSMASK;
+
+               len = JBE_OFFLENFLD(meta);
                totallen += len;
 
-               if (totallen > JENTRY_POSMASK)
+               /*
+                * 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_OFFLENMASK)
                        ereport(ERROR,
                                        (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
                                         errmsg("total size of jsonb array elements exceeds the maximum of %u bytes",
-                                                       JENTRY_POSMASK)));
+                                                       JENTRY_OFFLENMASK)));
+
+               /*
+                * Convert each JB_OFFSET_STRIDE'th length to an offset.
+                */
+               if ((i % JB_OFFSET_STRIDE) == 0)
+                       meta = (meta & JENTRY_TYPEMASK) | totallen | JENTRY_HAS_OFF;
 
-               if (i > 0)
-                       meta = (meta & ~JENTRY_POSMASK) | totallen;
-               copyToBuffer(buffer, metaoffset, (char *) &meta, sizeof(JEntry));
-               metaoffset += sizeof(JEntry);
+               copyToBuffer(buffer, jentry_offset, (char *) &meta, sizeof(JEntry));
+               jentry_offset += sizeof(JEntry);
        }
 
-       totallen = buffer->len - offset;
+       /* Total data size is everything we've appended to buffer */
+       totallen = buffer->len - base_offset;
+
+       /* Check length again, since we didn't include the metadata above */
+       if (totallen > JENTRY_OFFLENMASK)
+               ereport(ERROR,
+                               (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+                                errmsg("total size of jsonb array elements exceeds the maximum of %u bytes",
+                                               JENTRY_OFFLENMASK)));
 
-       /* Initialize the header of this node, in the container's JEntry array */
+       /* Initialize the header of this node in the container's JEntry array */
        *pheader = JENTRY_ISCONTAINER | totallen;
 }
 
 static void
-convertJsonbObject(convertState *buffer, JEntry *pheader, JsonbValue *val, int level)
+convertJsonbObject(StringInfo buffer, JEntry *pheader, JsonbValue *val, int level)
 {
-       uint32          header;
-       int                     offset;
-       int                     metaoffset;
+       int                     base_offset;
+       int                     jentry_offset;
        int                     i;
        int                     totallen;
+       uint32          header;
+       int                     nPairs = val->val.object.nPairs;
 
-       /* Initialize pointer into conversion buffer at this level */
-       offset = buffer->len;
+       /* Remember where in the buffer this object starts. */
+       base_offset = buffer->len;
 
+       /* Align to 4-byte boundary (any padding counts as part of my data) */
        padBufferToInt(buffer);
 
-       /* Initialize header */
-       header = val->val.object.nPairs | JB_FOBJECT;
+       /*
+        * Construct the header Jentry and store it in the beginning of the
+        * variable-length payload.
+        */
+       header = 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);
+       /* Reserve space for the JEntries of the keys and values. */
+       jentry_offset = reserveFromBuffer(buffer, sizeof(JEntry) * 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++)
+       for (i = 0; i < nPairs; i++)
        {
-               JsonbPair *pair = &val->val.object.pairs[i];
-               int len;
-               JEntry meta;
+               JsonbPair  *pair = &val->val.object.pairs[i];
+               int                     len;
+               JEntry          meta;
 
-               /* put key */
+               /*
+                * Convert key, producing a JEntry and appending its variable-length
+                * data to buffer
+                */
                convertJsonbScalar(buffer, &meta, &pair->key);
 
-               len = meta & JENTRY_POSMASK;
+               len = JBE_OFFLENFLD(meta);
                totallen += len;
 
-               if (totallen > JENTRY_POSMASK)
+               /*
+                * 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_OFFLENMASK)
                        ereport(ERROR,
                                        (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
-                                        errmsg("total size of jsonb array elements exceeds the maximum of %u bytes",
-                                                       JENTRY_POSMASK)));
+                                        errmsg("total size of jsonb object elements exceeds the maximum of %u bytes",
+                                                       JENTRY_OFFLENMASK)));
+
+               /*
+                * Convert each JB_OFFSET_STRIDE'th length to an offset.
+                */
+               if ((i % JB_OFFSET_STRIDE) == 0)
+                       meta = (meta & JENTRY_TYPEMASK) | totallen | JENTRY_HAS_OFF;
+
+               copyToBuffer(buffer, jentry_offset, (char *) &meta, sizeof(JEntry));
+               jentry_offset += sizeof(JEntry);
+       }
+       for (i = 0; i < nPairs; i++)
+       {
+               JsonbPair  *pair = &val->val.object.pairs[i];
+               int                     len;
+               JEntry          meta;
 
-               if (i > 0)
-                       meta = (meta & ~JENTRY_POSMASK) | totallen;
-               copyToBuffer(buffer, metaoffset, (char *) &meta, sizeof(JEntry));
-               metaoffset += sizeof(JEntry);
+               /*
+                * Convert value, producing a JEntry and appending its variable-length
+                * data to buffer
+                */
+               convertJsonbValue(buffer, &meta, &pair->value, level + 1);
 
-               convertJsonbValue(buffer, &meta, &pair->value, level);
-               len = meta & JENTRY_POSMASK;
+               len = JBE_OFFLENFLD(meta);
                totallen += len;
 
-               if (totallen > JENTRY_POSMASK)
+               /*
+                * 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_OFFLENMASK)
                        ereport(ERROR,
                                        (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
-                                        errmsg("total size of jsonb array elements exceeds the maximum of %u bytes",
-                                                       JENTRY_POSMASK)));
+                                        errmsg("total size of jsonb object elements exceeds the maximum of %u bytes",
+                                                       JENTRY_OFFLENMASK)));
+
+               /*
+                * Convert each JB_OFFSET_STRIDE'th length to an offset.
+                */
+               if (((i + nPairs) % JB_OFFSET_STRIDE) == 0)
+                       meta = (meta & JENTRY_TYPEMASK) | totallen | JENTRY_HAS_OFF;
 
-               meta = (meta & ~JENTRY_POSMASK) | totallen;
-               copyToBuffer(buffer, metaoffset, (char *) &meta, sizeof(JEntry));
-               metaoffset += sizeof(JEntry);
+               copyToBuffer(buffer, jentry_offset, (char *) &meta, sizeof(JEntry));
+               jentry_offset += sizeof(JEntry);
        }
 
-       totallen = buffer->len - offset;
+       /* Total data size is everything we've appended to buffer */
+       totallen = buffer->len - base_offset;
 
+       /* Check length again, since we didn't include the metadata above */
+       if (totallen > JENTRY_OFFLENMASK)
+               ereport(ERROR,
+                               (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+                                errmsg("total size of jsonb object elements exceeds the maximum of %u bytes",
+                                               JENTRY_OFFLENMASK)));
+
+       /* Initialize the header of this node in the container's JEntry array */
        *pheader = JENTRY_ISCONTAINER | totallen;
 }
 
 static void
-convertJsonbScalar(convertState *buffer, JEntry *jentry, JsonbValue *scalarVal)
+convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal)
 {
        int                     numlen;
        short           padlen;
@@ -1449,6 +1772,14 @@ convertJsonbScalar(convertState *buffer, JEntry *jentry, JsonbValue *scalarVal)
                        break;
 
                case jbvNumeric:
+                       /* replace numeric NaN with string "NaN" */
+                       if (numeric_is_nan(scalarVal->val.numeric))
+                       {
+                               appendToBuffer(buffer, "NaN", 3);
+                               *jentry = 3;
+                               break;
+                       }
+
                        numlen = VARSIZE_ANY(scalarVal->val.numeric);
                        padlen = padBufferToInt(buffer);
 
@@ -1462,6 +1793,22 @@ convertJsonbScalar(convertState *buffer, JEntry *jentry, JsonbValue *scalarVal)
                                JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE;
                        break;
 
+               case jbvDatetime:
+                       {
+                               char            buf[MAXDATELEN + 1];
+                               size_t          len;
+
+                               JsonEncodeDateTime(buf,
+                                                                  scalarVal->val.datetime.value,
+                                                                  scalarVal->val.datetime.typid,
+                                                                  &scalarVal->val.datetime.tz);
+                               len = strlen(buf);
+                               appendToBuffer(buffer, buf, len);
+
+                               *jentry = len;
+                       }
+                       break;
+
                default:
                        elog(ERROR, "invalid jsonb scalar type");
        }
@@ -1470,7 +1817,7 @@ convertJsonbScalar(convertState *buffer, JEntry *jentry, JsonbValue *scalarVal)
 /*
  * Compare two jbvString JsonbValue values, a and b.
  *
- * This is a special qsort_arg() comparator used to sort strings in certain
+ * This is a special qsort() comparator used to sort strings in certain
  * internal contexts where it is sufficient to have a well-defined sort order.
  * In particular, object pair keys are sorted according to this criteria to
  * facilitate cheap binary searches where we don't care about lexical sort
@@ -1484,21 +1831,27 @@ lengthCompareJsonbStringValue(const void *a, const void *b)
 {
        const JsonbValue *va = (const JsonbValue *) a;
        const JsonbValue *vb = (const JsonbValue *) b;
-       int                     res;
 
        Assert(va->type == jbvString);
        Assert(vb->type == jbvString);
 
-       if (va->val.string.len == vb->val.string.len)
-       {
-               res = memcmp(va->val.string.val, vb->val.string.val, va->val.string.len);
-       }
-       else
-       {
-               res = (va->val.string.len > vb->val.string.len) ? 1 : -1;
-       }
+       return lengthCompareJsonbString(va->val.string.val, va->val.string.len,
+                                                                       vb->val.string.val, vb->val.string.len);
+}
 
-       return res;
+/*
+ * Subroutine for lengthCompareJsonbStringValue
+ *
+ * This is also useful separately to implement binary search on
+ * JsonbContainers.
+ */
+static int
+lengthCompareJsonbString(const char *val1, int len1, const char *val2, int len2)
+{
+       if (len1 == len2)
+               return memcmp(val1, val2, len1);
+       else
+               return len1 > len2 ? 1 : -1;
 }
 
 /*