From: Peter Geoghegan Date: Sun, 16 Mar 2014 02:16:23 +0000 (-0700) Subject: Take more care within compareJsonbSuperHeaderValue(). X-Git-Url: http://git.postgresql.org/gitweb/static/gitweb.js?a=commitdiff_plain;h=5c87b3b79f49ce0bccf3f1289039d4c2dc6edc9d;p=users%2Fandresfreund%2Fpostgres.git Take more care within compareJsonbSuperHeaderValue(). 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. --- diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml index 9c5fb4d005..0c53ee4870 100644 --- a/doc/src/sgml/json.sgml +++ b/doc/src/sgml/json.sgml @@ -308,6 +308,7 @@ CREATE INDEX idxgin ON api USING GIN ((jdoc -> 'tags')); Array with n elements > array with n - 1 elements + Subsequently, individual primitive type comparators are invoked. All comparisons of JSON primitive types occurs using the same comparison rules as the underlying PostgreSQL types. Strings are @@ -316,8 +317,7 @@ CREATE INDEX idxgin ON api USING GIN ((jdoc -> 'tags')); key-1, value-1, key-2 ... - Similarly, arrays with equal - numbers of elements are compared: + Similarly, arrays with equal numbers of elements are compared: element-1, element-2 ... diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c index 704c715292..01999f1dfd 100644 --- a/src/backend/utils/adt/jsonb_util.c +++ b/src/backend/utils/adt/jsonb_util.c @@ -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, diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h index a428a912af..d23cd58477 100644 --- a/src/include/utils/jsonb.h +++ b/src/include/utils/jsonb.h @@ -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;