Take more care within compareJsonbSuperHeaderValue().
authorPeter Geoghegan <[email protected]>
Sun, 16 Mar 2014 02:16:23 +0000 (19:16 -0700)
committerPeter Geoghegan <[email protected]>
Sun, 16 Mar 2014 02:20:05 +0000 (19:20 -0700)
Prior coding did not take sufficient care in using internal Jsonb type
as a tie-breaker in all situations.  The prior practice of using a
JsonbIteratorNext() return code as a tie-breaker was bogus, or was at
the very least incredibly fragile.

doc/src/sgml/json.sgml
src/backend/utils/adt/jsonb_util.c
src/include/utils/jsonb.h

index 9c5fb4d005431e9888fc8830397f60afdf632b5b..0c53ee487086cf3b8547b2615512741a86dfdffa 100644 (file)
@@ -308,6 +308,7 @@ CREATE INDEX idxgin ON api USING GIN ((jdoc -> 'tags'));
 
       <replaceable>Array with n elements</replaceable> > <replaceable>array with n - 1 elements</replaceable>
     </synopsis>
+      Subsequently, individual primitive type comparators are invoked.
       All comparisons of JSON primitive types occurs using the same
       comparison rules as the underlying
       <productname>PostgreSQL</productname> types.  Strings are
@@ -316,8 +317,7 @@ CREATE INDEX idxgin ON api USING GIN ((jdoc -> 'tags'));
     <synopsis>
       <replaceable>key-1</replaceable>, <replaceable>value-1</replaceable>, <replaceable>key-2</replaceable> ...
     </synopsis>
-      Similarly, arrays with equal
-      numbers of elements are compared:
+      Similarly, arrays with equal numbers of elements are compared:
     <synopsis>
       <replaceable>element-1</replaceable>, <replaceable>element-2</replaceable> ...
     </synopsis>
index 704c715292f40bf2364ad3a7e98ca84a63944795..01999f1dfde872e41a46f006c62228f7307b5425 100644 (file)
@@ -149,29 +149,41 @@ compareJsonbSuperHeaderValue(JsonbSuperHeader a, JsonbSuperHeader b)
        r1 = JsonbIteratorNext(&it1, &v1, false);
        r2 = JsonbIteratorNext(&it2, &v2, 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 (r1 == r2)
        {
            if (r1 == WJB_DONE)
-               break;          /* Decisely equal */
+           {
+               /* Decisively equal */
+               Assert(res == 0);
+               break;
+           }
 
            if (v1.type == v2.type)
            {
                switch (v1.type)
                {
+                   case jbvNull:
+                       res = 0;
+                       break;
                    case jbvString:
                        res = lexicalCompareJsonbStringValue(&v1, &v2);
                        break;
-                   case jbvBool:
-                       if (v1.boolean == v2.boolean)
-                           res = 0;
-                       else
-                           res = (v1.boolean > v2.boolean) ? 1 : -1;
-                       break;
                    case jbvNumeric:
                        res = DatumGetInt32(DirectFunctionCall2(numeric_cmp,
                                                                PointerGetDatum(v1.numeric),
                                                                PointerGetDatum(v2.numeric)));
                        break;
+                   case jbvBool:
+                       if (v1.boolean != v2.boolean)
+                           res = (v1.boolean > v2.boolean) ? 1 : -1;
+                       break;
                    case jbvArray:
                        if (v1.array.nElems != v2.array.nElems)
                            res = (v1.array.nElems > v2.array.nElems) ? 1 : -1;
@@ -180,18 +192,53 @@ compareJsonbSuperHeaderValue(JsonbSuperHeader a, JsonbSuperHeader b)
                        if (v1.object.nPairs != v2.object.nPairs)
                            res = (v1.object.nPairs > v2.object.nPairs) ? 1 : -1;
                        break;
-                   default:
+                   case jbvBinary:
+                       /*
+                        * Do nothing.  We can rely on next iterator to have
+                        * details of underlying type.
+                        */
                        break;
                }
            }
            else
            {
-               res = (v1.type > v2.type) ? 1 : -1;     /* dummy order */
+               res = (v1.type > v2.type) ? 1 : -1;     /* Type-defined order */
+               break; /* out of while loop */
            }
        }
        else
        {
-           res = (r1 > r2) ? 1 : -1;   /* dummy order */
+           if (v1.type == v2.type)
+           {
+               Assert(res == 0);
+               /*
+                * Types were the same, and yet since iterator return codes
+                * differed, one must have finished before the other (and thus
+                * there must be a variable number of pairs/elements).
+                */
+               if (v1.type == jbvArray)
+               {
+                   Assert(v1.array.nElems != v2.array.nElems);
+                   res = (v1.array.nElems > v2.array.nElems) ? 1 : -1;
+               }
+               else if (v1.type == jbvObject)
+               {
+                   Assert(v1.object.nPairs != v2.object.nPairs);
+                   res = (v1.object.nPairs > v2.object.nPairs) ? 1 : -1;
+               }
+               else
+               {
+                   elog(ERROR, "unexpected non-container: %d", v1.type);
+               }
+
+               Assert((r1 != WJB_DONE && r2 != WJB_DONE) || res != 0);
+           }
+           else
+           {
+               /* Type-defined order */
+               res = (v1.type > v2.type) ? 1 : -1;
+           }
+           break; /* out of while loop */
        }
    }
    while (res == 0);
@@ -623,6 +670,10 @@ JsonbIteratorInit(JsonbSuperHeader sheader)
  * iterator we left them with to its oldest ancestor, pfree()ing as they go.
  * They can depend on having any other memory previously allocated for
  * iterators but not in that line having already been freed here.
+ *
+ * Returns "Jsonb binary token" value.  Iterator "state" reflects the current
+ * stage of the process in a less granular fashion, and is mostly used here to
+ * track things internally with respect to particular iterators.
  */
 int
 JsonbIteratorNext(JsonbIterator ** it, JsonbValue * v, bool skipNested)
@@ -1075,7 +1126,7 @@ lexicalCompareJsonbStringValue(const void *a, const void *b)
 }
 
 /*
- * Put JsonbValue tree into a preallocated buffer Jsonb buffer
+ * Put JsonbValue tree into a preallocated Jsonb buffer
  */
 static Size
 convertJsonb(JsonbValue * v, Jsonb *buffer)
@@ -1419,11 +1470,11 @@ iteratorFromContainerBuf(JsonbIterator * it, JsonbSuperHeader sheader)
 /*
  * JsonbIteratorNext() worker
  *
- * Returns bool indicating if v was a scalar, and thus if further recursion is
- * required by caller (according to its skipNested preference).  If it is
- * required, we set the caller's iterator for further recursion into the nested
- * value.  If we're going to skip nested items, just set v to a jbvBinary
- * value, but don't set caller's iterator.
+ * Returns bool indicating if v was a non-jbvBinary container, and thus if
+ * further recursion is required by caller (according to its skipNested
+ * preference).  If it is required, we set the caller's iterator for further
+ * recursion into the nested value.  If we're going to skip nested items, just
+ * set v to a jbvBinary value, but don't set caller's iterator.
  */
 static bool
 formIterIsContainer(JsonbIterator ** it, JsonbValue * v, JEntry * e,
index a428a912affab5621975e9fd7758481a34e60ad7..d23cd5847751354160051a3d7d0c6bf064cce22e 100644 (file)
@@ -125,7 +125,6 @@ typedef char*  JsonbSuperHeader;
  * consistent (to the extent that it matters) between the least nested level
  * (Jsonb superheaders), and deeper nesting levels (Jentry headers).
  */
-
 typedef struct
 {
    int32       vl_len_;        /* varlena header (do not touch directly!) */
@@ -170,7 +169,7 @@ struct JsonbValue
        bool        boolean;
        struct
        {
-           Size        len;
+           int         len;
            char       *val;    /* Not necessarily null-terminated */
        } string;       /* String primitive type */
 
@@ -190,7 +189,7 @@ struct JsonbValue
 
        struct
        {
-           Size        len;
+           int         len;
            char       *data;
        } binary;
    };
@@ -248,6 +247,7 @@ typedef struct JsonbIterator
     */
    char       *dataProper;
 
+   /* Private state */
    iterState state;
 
    struct JsonbIterator *parent;