Fix user defined index selection
This fix modifies indexable_fields to remove extraneous fields
that are created by the mango_selector_text:convert function so that
the index can now be used when accessing arrays, $in operations, and
$size operations.
COUCHDB-2835
diff --git a/src/mango_idx_text.erl b/src/mango_idx_text.erl
index 9ade6e2..5528915 100644
--- a/src/mango_idx_text.erl
+++ b/src/mango_idx_text.erl
@@ -283,6 +283,20 @@
lists:foldl(fun(Arg, Fields0) -> indexable_fields(Fields0, Arg) end,
Fields, Args);
+%% For queries that use array element access or $in operations, two
+%% fields get generated by mango_selector_text:convert. At index
+%% definition time, only one field gets defined. In this situation, we
+%% remove the extra generated field so that the index can be used. For
+%% all other situations, we include the fields as normal.
+indexable_fields(Fields, {op_or, [{op_field, Field0},
+ {op_field, {[Name | _], _}} = Field1]}) ->
+ case lists:member(<<"[]">>, Name) of
+ true ->
+ indexable_fields(Fields, Field1);
+ false ->
+ Fields1 = indexable_fields(Fields, Field0),
+ indexable_fields(Fields1, Field1)
+ end;
indexable_fields(Fields, {op_or, Args}) when is_list(Args) ->
lists:foldl(fun(Arg, Fields0) -> indexable_fields(Fields0, Arg) end,
Fields, Args);
@@ -294,6 +308,9 @@
indexable_fields(Fields, {op_insert, Arg}) when is_binary(Arg) ->
Fields;
+%% fieldname.[]:length is not a user defined field.
+indexable_fields(Fields, {op_field, {[_, <<":length">>], _}}) ->
+ Fields;
indexable_fields(Fields, {op_field, {Name, _}}) ->
[iolist_to_binary(Name) | Fields];
diff --git a/test/07-text-custom-field-list-test.py b/test/07-text-custom-field-list-test.py
index f299ef7..8a8a5f8 100644
--- a/test/07-text-custom-field-list-test.py
+++ b/test/07-text-custom-field-list-test.py
@@ -44,6 +44,48 @@
docs = self.db.find({"age": 22, "manager": False})
assert len(docs) == 0
+ def test_element_acess(self):
+ docs = self.db.find({"favorites.0": "Ruby"})
+ assert len(docs) == 3
+ for d in docs:
+ assert "Ruby" in d["favorites"]
+
+ # This should throw an exception because we only index the array
+ # favorites.[], and not the string field favorites
+ def test_index_selection(self):
+ try:
+ self.db.find({"selector": {"$or": [{"favorites": "Ruby"},
+ {"favorites.0":"Ruby"}]}})
+ except Exception, e:
+ assert e.response.status_code == 400
+
+ def test_in_with_array(self):
+ vals = ["Lisp", "Python"]
+ docs = self.db.find({"favorites": {"$in": vals}})
+ assert len(docs) == 10
+
+ # This should also throw an error because we only indexed
+ # favorites.[] of type string. For the following query to work, the
+ # user has to index favorites.[] of type number, and also
+ # favorites.[].Versions.Alpha of type string.
+ def test_in_different_types(self):
+ vals = ["Random Garbage", 52, {"Versions": {"Alpha": "Beta"}}]
+ try:
+ self.db.find({"favorites": {"$in": vals}})
+ except Exception, e:
+ assert e.response.status_code == 400
+
+ # This test differs from the situation where we index everything.
+ # When we index everything the actual number of docs that gets
+ # returned is 5. That's because of the special situation where we
+ # have an array of an array, i.e: [["Lisp"]], because we're indexing
+ # specifically favorites.[] of type string. So it does not count
+ # the example and we only get 4 back.
+ def test_nin_with_array(self):
+ vals = ["Lisp", "Python"]
+ docs = self.db.find({"favorites": {"$nin": vals}})
+ assert len(docs) == 4
+
def test_missing(self):
self.db.find({"location.state": "Nevada"})