diff options
author | Will Holley <willholley@gmail.com> | 2017-11-23 09:04:17 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-11-23 09:04:17 +0100 |
commit | ede5dd9675285157410311aa8e2ed01c7f5e597e (patch) | |
tree | 429089b3765fa12f077c9fd35dad1c2163df2842 | |
parent | bdaeaff948b90d30686104349b8f01c19f0c482b (diff) | |
download | couchdb-ede5dd9675285157410311aa8e2ed01c7f5e597e.tar.gz |
Fix index validation for nested $and (#1014)
mango_selector:has_required_fields checks that a list of
indexed fields is covered by a given selector. The implementation
recurses through the selector, tracking fields that encounters.
Unfortunately, this skipped peers of combination operators. For
example,
"selector": {
"$and":[
"$and":[
"A": "foo"
],
"$and":[
"B": "bar"
]
]
}
would skip the first nested "$and" operator and only return "B"
as a covered field.
This commit explicitly handles this situation (the only combination
operator we care about is $and), so for the above selector we
would correctly indentify "A" and "B" as covered fields.
-rw-r--r-- | src/mango/src/mango_selector.erl | 57 | ||||
-rw-r--r-- | src/mango/test/05-index-selection-test.py | 10 |
2 files changed, 54 insertions, 13 deletions
diff --git a/src/mango/src/mango_selector.erl b/src/mango/src/mango_selector.erl index 4ff36945a..2a546c9ba 100644 --- a/src/mango/src/mango_selector.erl +++ b/src/mango/src/mango_selector.erl @@ -578,36 +578,46 @@ match({[_, _ | _] = _Props} = Sel, _Value, _Cmp) -> % until we match then all or run out of selector to % match against. +has_required_fields(Selector, RequiredFields) -> + Remainder = has_required_fields_int(Selector, RequiredFields), + Remainder == []. + % Empty selector -has_required_fields({[]}, _) -> - false; +has_required_fields_int({[]}, Remainder) -> + Remainder; % No more required fields -has_required_fields(_, []) -> - true; +has_required_fields_int(_, []) -> + []; % No more selector -has_required_fields([], _) -> - false; +has_required_fields_int([], Remainder) -> + Remainder; -has_required_fields(Selector, RequiredFields) when not is_list(Selector) -> - has_required_fields([Selector], RequiredFields); +has_required_fields_int(Selector, RequiredFields) when not is_list(Selector) -> + has_required_fields_int([Selector], RequiredFields); % We can "see" through $and operator. We ignore other % combination operators because they can't be used to restrict % an index. -has_required_fields([{[{<<"$and">>, Args}]}], RequiredFields) +has_required_fields_int([{[{<<"$and">>, Args}]}], RequiredFields) + when is_list(Args) -> + has_required_fields_int(Args, RequiredFields); + +% Handle $and operator where it has peers +has_required_fields_int([{[{<<"$and">>, Args}]} | Rest], RequiredFields) when is_list(Args) -> - has_required_fields(Args, RequiredFields); + Remainder = has_required_fields_int(Args, RequiredFields), + has_required_fields_int(Rest, Remainder); -has_required_fields([{[{Field, Cond}]} | Rest], RequiredFields) -> +has_required_fields_int([{[{Field, Cond}]} | Rest], RequiredFields) -> case Cond of % $exists:false is a special case - this is the only operator % that explicitly does not require a field to exist {[{<<"$exists">>, false}]} -> - has_required_fields(Rest, RequiredFields); + has_required_fields_int(Rest, RequiredFields); _ -> - has_required_fields(Rest, lists:delete(Field, RequiredFields)) + has_required_fields_int(Rest, lists:delete(Field, RequiredFields)) end. @@ -651,6 +661,27 @@ has_required_fields_and_true_test() -> Normalized = normalize(Selector), ?assertEqual(true, has_required_fields(Normalized, RequiredFields)). +has_required_fields_nested_and_true_test() -> + RequiredFields = [<<"A">>, <<"B">>], + Selector1 = {[{<<"$and">>, + [ + {[{<<"A">>, <<"foo">>}]} + ] + }]}, + Selector2 = {[{<<"$and">>, + [ + {[{<<"B">>, <<"foo">>}]} + ] + }]}, + Selector = {[{<<"$and">>, + [ + Selector1, + Selector2 + ] + }]}, + + ?assertEqual(true, has_required_fields(Selector, RequiredFields)). + has_required_fields_and_false_test() -> RequiredFields = [<<"A">>, <<"C">>], Selector = {[{<<"$and">>, diff --git a/src/mango/test/05-index-selection-test.py b/src/mango/test/05-index-selection-test.py index fe36257e3..f8cc82576 100644 --- a/src/mango/test/05-index-selection-test.py +++ b/src/mango/test/05-index-selection-test.py @@ -28,6 +28,16 @@ class IndexSelectionTests: }, explain=True) self.assertEqual(resp["index"]["type"], "json") + def test_with_nested_and(self): + resp = self.db.find({ + "name.first": { + "$gt": "a", + "$lt": "z" + }, + "name.last": "Foo" + }, explain=True) + self.assertEqual(resp["index"]["type"], "json") + def test_use_most_columns(self): # ddoc id for the age index ddocid = "_design/ad3d537c03cd7c6a43cf8dff66ef70ea54c2b40f" |