Fix bogus optimization in JSONB containment tests.
When determining whether one JSONB object contains another, it's okay to make a quick exit if the first object has fewer pairs than the second: because we de-duplicate keys within objects, it is impossible that the first object has all the keys the second does. However, the code was applying this rule to JSONB arrays as well, where it does *not* hold because arrays can contain duplicate entries. The test was really in the wrong place anyway; we should do it within JsonbDeepContains, where it can be applied to nested objects not only top-level ones. Report and test cases by Alexander Korotkov; fix by Peter Geoghegan and Tom Lane.
This commit is contained in:
parent
733be2a5cd
commit
4a50de1312
|
@ -117,8 +117,7 @@ jsonb_contains(PG_FUNCTION_ARGS)
|
|||
JsonbIterator *it1,
|
||||
*it2;
|
||||
|
||||
if (JB_ROOT_COUNT(val) < JB_ROOT_COUNT(tmpl) ||
|
||||
JB_ROOT_IS_OBJECT(val) != JB_ROOT_IS_OBJECT(tmpl))
|
||||
if (JB_ROOT_IS_OBJECT(val) != JB_ROOT_IS_OBJECT(tmpl))
|
||||
PG_RETURN_BOOL(false);
|
||||
|
||||
it1 = JsonbIteratorInit(&val->root);
|
||||
|
@ -137,8 +136,7 @@ jsonb_contained(PG_FUNCTION_ARGS)
|
|||
JsonbIterator *it1,
|
||||
*it2;
|
||||
|
||||
if (JB_ROOT_COUNT(val) < JB_ROOT_COUNT(tmpl) ||
|
||||
JB_ROOT_IS_OBJECT(val) != JB_ROOT_IS_OBJECT(tmpl))
|
||||
if (JB_ROOT_IS_OBJECT(val) != JB_ROOT_IS_OBJECT(tmpl))
|
||||
PG_RETURN_BOOL(false);
|
||||
|
||||
it1 = JsonbIteratorInit(&val->root);
|
||||
|
|
|
@ -957,13 +957,24 @@ 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 */
|
||||
|
||||
rcont = JsonbIteratorNext(mContained, &vcontained, false);
|
||||
|
||||
/*
|
||||
|
@ -1047,6 +1058,7 @@ JsonbDeepContains(JsonbIterator **val, JsonbIterator **mContained)
|
|||
JsonbValue *lhsConts = NULL;
|
||||
uint32 nLhsElems = vval.val.array.nElems;
|
||||
|
||||
Assert(vval.type == jbvArray);
|
||||
Assert(vcontained.type == jbvArray);
|
||||
|
||||
/*
|
||||
|
|
|
@ -707,6 +707,42 @@ SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "c":"q"}';
|
|||
f
|
||||
(1 row)
|
||||
|
||||
SELECT '[1,2]'::jsonb @> '[1,2,2]'::jsonb;
|
||||
?column?
|
||||
----------
|
||||
t
|
||||
(1 row)
|
||||
|
||||
SELECT '[1,1,2]'::jsonb @> '[1,2,2]'::jsonb;
|
||||
?column?
|
||||
----------
|
||||
t
|
||||
(1 row)
|
||||
|
||||
SELECT '[[1,2]]'::jsonb @> '[[1,2,2]]'::jsonb;
|
||||
?column?
|
||||
----------
|
||||
t
|
||||
(1 row)
|
||||
|
||||
SELECT '[1,2,2]'::jsonb <@ '[1,2]'::jsonb;
|
||||
?column?
|
||||
----------
|
||||
t
|
||||
(1 row)
|
||||
|
||||
SELECT '[1,2,2]'::jsonb <@ '[1,1,2]'::jsonb;
|
||||
?column?
|
||||
----------
|
||||
t
|
||||
(1 row)
|
||||
|
||||
SELECT '[[1,2,2]]'::jsonb <@ '[[1,2]]'::jsonb;
|
||||
?column?
|
||||
----------
|
||||
t
|
||||
(1 row)
|
||||
|
||||
SELECT jsonb_contained('{"a":"b"}', '{"a":"b", "b":1, "c":null}');
|
||||
jsonb_contained
|
||||
-----------------
|
||||
|
|
|
@ -707,6 +707,42 @@ SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "c":"q"}';
|
|||
f
|
||||
(1 row)
|
||||
|
||||
SELECT '[1,2]'::jsonb @> '[1,2,2]'::jsonb;
|
||||
?column?
|
||||
----------
|
||||
t
|
||||
(1 row)
|
||||
|
||||
SELECT '[1,1,2]'::jsonb @> '[1,2,2]'::jsonb;
|
||||
?column?
|
||||
----------
|
||||
t
|
||||
(1 row)
|
||||
|
||||
SELECT '[[1,2]]'::jsonb @> '[[1,2,2]]'::jsonb;
|
||||
?column?
|
||||
----------
|
||||
t
|
||||
(1 row)
|
||||
|
||||
SELECT '[1,2,2]'::jsonb <@ '[1,2]'::jsonb;
|
||||
?column?
|
||||
----------
|
||||
t
|
||||
(1 row)
|
||||
|
||||
SELECT '[1,2,2]'::jsonb <@ '[1,1,2]'::jsonb;
|
||||
?column?
|
||||
----------
|
||||
t
|
||||
(1 row)
|
||||
|
||||
SELECT '[[1,2,2]]'::jsonb <@ '[[1,2]]'::jsonb;
|
||||
?column?
|
||||
----------
|
||||
t
|
||||
(1 row)
|
||||
|
||||
SELECT jsonb_contained('{"a":"b"}', '{"a":"b", "b":1, "c":null}');
|
||||
jsonb_contained
|
||||
-----------------
|
||||
|
|
|
@ -156,6 +156,13 @@ SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"c"}';
|
|||
SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b"}';
|
||||
SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "c":"q"}';
|
||||
|
||||
SELECT '[1,2]'::jsonb @> '[1,2,2]'::jsonb;
|
||||
SELECT '[1,1,2]'::jsonb @> '[1,2,2]'::jsonb;
|
||||
SELECT '[[1,2]]'::jsonb @> '[[1,2,2]]'::jsonb;
|
||||
SELECT '[1,2,2]'::jsonb <@ '[1,2]'::jsonb;
|
||||
SELECT '[1,2,2]'::jsonb <@ '[1,1,2]'::jsonb;
|
||||
SELECT '[[1,2,2]]'::jsonb <@ '[[1,2]]'::jsonb;
|
||||
|
||||
SELECT jsonb_contained('{"a":"b"}', '{"a":"b", "b":1, "c":null}');
|
||||
SELECT jsonb_contained('{"a":"b", "c":null}', '{"a":"b", "b":1, "c":null}');
|
||||
SELECT jsonb_contained('{"a":"b", "g":null}', '{"a":"b", "b":1, "c":null}');
|
||||
|
|
Loading…
Reference in New Issue