Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 4a50de1

Browse files
committed
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.
1 parent 733be2a commit 4a50de1

File tree

5 files changed

+97
-8
lines changed

5 files changed

+97
-8
lines changed

src/backend/utils/adt/jsonb_op.c

+4-6
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ jsonb_exists_any(PG_FUNCTION_ARGS)
5757

5858
for (i = 0; i < elem_count; i++)
5959
{
60-
JsonbValue strVal;
60+
JsonbValue strVal;
6161

6262
if (key_nulls[i])
6363
continue;
@@ -90,7 +90,7 @@ jsonb_exists_all(PG_FUNCTION_ARGS)
9090

9191
for (i = 0; i < elem_count; i++)
9292
{
93-
JsonbValue strVal;
93+
JsonbValue strVal;
9494

9595
if (key_nulls[i])
9696
continue;
@@ -117,8 +117,7 @@ jsonb_contains(PG_FUNCTION_ARGS)
117117
JsonbIterator *it1,
118118
*it2;
119119

120-
if (JB_ROOT_COUNT(val) < JB_ROOT_COUNT(tmpl) ||
121-
JB_ROOT_IS_OBJECT(val) != JB_ROOT_IS_OBJECT(tmpl))
120+
if (JB_ROOT_IS_OBJECT(val) != JB_ROOT_IS_OBJECT(tmpl))
122121
PG_RETURN_BOOL(false);
123122

124123
it1 = JsonbIteratorInit(&val->root);
@@ -137,8 +136,7 @@ jsonb_contained(PG_FUNCTION_ARGS)
137136
JsonbIterator *it1,
138137
*it2;
139138

140-
if (JB_ROOT_COUNT(val) < JB_ROOT_COUNT(tmpl) ||
141-
JB_ROOT_IS_OBJECT(val) != JB_ROOT_IS_OBJECT(tmpl))
139+
if (JB_ROOT_IS_OBJECT(val) != JB_ROOT_IS_OBJECT(tmpl))
142140
PG_RETURN_BOOL(false);
143141

144142
it1 = JsonbIteratorInit(&val->root);

src/backend/utils/adt/jsonb_util.c

+14-2
Original file line numberDiff line numberDiff line change
@@ -957,13 +957,24 @@ JsonbDeepContains(JsonbIterator **val, JsonbIterator **mContained)
957957
}
958958
else if (rcont == WJB_BEGIN_OBJECT)
959959
{
960-
JsonbValue *lhsVal; /* lhsVal is from pair in lhs object */
961-
960+
Assert(vval.type == jbvObject);
962961
Assert(vcontained.type == jbvObject);
963962

963+
/*
964+
* If the lhs has fewer pairs than the rhs, it can't possibly contain
965+
* the rhs. (This conclusion is safe only because we de-duplicate
966+
* keys in all Jsonb objects; thus there can be no corresponding
967+
* optimization in the array case.) The case probably won't arise
968+
* often, but since it's such a cheap check we may as well make it.
969+
*/
970+
if (vval.val.object.nPairs < vcontained.val.object.nPairs)
971+
return false;
972+
964973
/* Work through rhs "is it contained within?" object */
965974
for (;;)
966975
{
976+
JsonbValue *lhsVal; /* lhsVal is from pair in lhs object */
977+
967978
rcont = JsonbIteratorNext(mContained, &vcontained, false);
968979

969980
/*
@@ -1047,6 +1058,7 @@ JsonbDeepContains(JsonbIterator **val, JsonbIterator **mContained)
10471058
JsonbValue *lhsConts = NULL;
10481059
uint32 nLhsElems = vval.val.array.nElems;
10491060

1061+
Assert(vval.type == jbvArray);
10501062
Assert(vcontained.type == jbvArray);
10511063

10521064
/*

src/test/regress/expected/jsonb.out

+36
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,42 @@ SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "c":"q"}';
707707
f
708708
(1 row)
709709

710+
SELECT '[1,2]'::jsonb @> '[1,2,2]'::jsonb;
711+
?column?
712+
----------
713+
t
714+
(1 row)
715+
716+
SELECT '[1,1,2]'::jsonb @> '[1,2,2]'::jsonb;
717+
?column?
718+
----------
719+
t
720+
(1 row)
721+
722+
SELECT '[[1,2]]'::jsonb @> '[[1,2,2]]'::jsonb;
723+
?column?
724+
----------
725+
t
726+
(1 row)
727+
728+
SELECT '[1,2,2]'::jsonb <@ '[1,2]'::jsonb;
729+
?column?
730+
----------
731+
t
732+
(1 row)
733+
734+
SELECT '[1,2,2]'::jsonb <@ '[1,1,2]'::jsonb;
735+
?column?
736+
----------
737+
t
738+
(1 row)
739+
740+
SELECT '[[1,2,2]]'::jsonb <@ '[[1,2]]'::jsonb;
741+
?column?
742+
----------
743+
t
744+
(1 row)
745+
710746
SELECT jsonb_contained('{"a":"b"}', '{"a":"b", "b":1, "c":null}');
711747
jsonb_contained
712748
-----------------

src/test/regress/expected/jsonb_1.out

+36
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,42 @@ SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "c":"q"}';
707707
f
708708
(1 row)
709709

710+
SELECT '[1,2]'::jsonb @> '[1,2,2]'::jsonb;
711+
?column?
712+
----------
713+
t
714+
(1 row)
715+
716+
SELECT '[1,1,2]'::jsonb @> '[1,2,2]'::jsonb;
717+
?column?
718+
----------
719+
t
720+
(1 row)
721+
722+
SELECT '[[1,2]]'::jsonb @> '[[1,2,2]]'::jsonb;
723+
?column?
724+
----------
725+
t
726+
(1 row)
727+
728+
SELECT '[1,2,2]'::jsonb <@ '[1,2]'::jsonb;
729+
?column?
730+
----------
731+
t
732+
(1 row)
733+
734+
SELECT '[1,2,2]'::jsonb <@ '[1,1,2]'::jsonb;
735+
?column?
736+
----------
737+
t
738+
(1 row)
739+
740+
SELECT '[[1,2,2]]'::jsonb <@ '[[1,2]]'::jsonb;
741+
?column?
742+
----------
743+
t
744+
(1 row)
745+
710746
SELECT jsonb_contained('{"a":"b"}', '{"a":"b", "b":1, "c":null}');
711747
jsonb_contained
712748
-----------------

src/test/regress/sql/jsonb.sql

+7
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,13 @@ SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"c"}';
156156
SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b"}';
157157
SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "c":"q"}';
158158

159+
SELECT '[1,2]'::jsonb @> '[1,2,2]'::jsonb;
160+
SELECT '[1,1,2]'::jsonb @> '[1,2,2]'::jsonb;
161+
SELECT '[[1,2]]'::jsonb @> '[[1,2,2]]'::jsonb;
162+
SELECT '[1,2,2]'::jsonb <@ '[1,2]'::jsonb;
163+
SELECT '[1,2,2]'::jsonb <@ '[1,1,2]'::jsonb;
164+
SELECT '[[1,2,2]]'::jsonb <@ '[[1,2]]'::jsonb;
165+
159166
SELECT jsonb_contained('{"a":"b"}', '{"a":"b", "b":1, "c":null}');
160167
SELECT jsonb_contained('{"a":"b", "c":null}', '{"a":"b", "b":1, "c":null}');
161168
SELECT jsonb_contained('{"a":"b", "g":null}', '{"a":"b", "b":1, "c":null}');

0 commit comments

Comments
 (0)