This is an automated email from the ASF dual-hosted git repository. pgj pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/couchdb.git
commit dca403dabd1b379b073815a241f1b9be73f6c1a2 Author: Gabor Pali <[email protected]> AuthorDate: Tue Oct 3 14:14:58 2023 +0200 mango: add unit tests for text cursor --- src/mango/src/mango_cursor_text.erl | 775 ++++++++++++++++++++++++++++++++++++ 1 file changed, 775 insertions(+) diff --git a/src/mango/src/mango_cursor_text.erl b/src/mango/src/mango_cursor_text.erl index 46692a4a7..f5899914a 100644 --- a/src/mango/src/mango_cursor_text.erl +++ b/src/mango/src/mango_cursor_text.erl @@ -334,4 +334,779 @@ get_json_docs(DbName, Hits) -> Hits ). +%%%%%%%% module tests %%%%%%%% + +-ifdef(TEST). +-include_lib("couch/include/couch_eunit.hrl"). + +% This behavior needs to be revisited and potentially fixed, the tests +% below are only to record the current version. + +create_test_() -> + { + foreach, + fun() -> meck:expect(couch_db, name, [db], meck:val(db_name)) end, + fun(_) -> meck:unload() end, + [ + ?TDEF_FE(t_create_no_indexes), + ?TDEF_FE(t_create_multiple_indexes), + ?TDEF_FE(t_create_regular), + ?TDEF_FE(t_create_no_bookmark), + ?TDEF_FE(t_create_invalid_bookmark) + ] + }. + +t_create_no_indexes(_) -> + Exception = {mango_error, mango_cursor_text, multiple_text_indexes}, + ?assertThrow(Exception, create(db, {[], trace}, selector, options)). + +t_create_multiple_indexes(_) -> + Indexes = [index1, index2, index3], + Exception = {mango_error, mango_cursor_text, multiple_text_indexes}, + ?assertThrow(Exception, create(db, {Indexes, trace}, selector, options)). + +t_create_regular(_) -> + Index = #idx{type = <<"text">>}, + Indexes = [Index], + Trace = #{}, + Limit = 10, + Options = [{limit, Limit}, {skip, skip}, {fields, fields}, {bookmark, bookmark}], + Options1 = [{limit, Limit}, {skip, skip}, {fields, fields}, {bookmark, unpacked_bookmark}], + Cursor = #cursor{ + db = db, + index = Index, + ranges = null, + trace = Trace, + selector = selector, + opts = Options1, + limit = Limit, + skip = skip, + fields = fields + }, + meck:expect(dreyfus_bookmark, unpack, [db_name, bookmark], meck:val(unpacked_bookmark)), + ?assertEqual({ok, Cursor}, create(db, {Indexes, Trace}, selector, Options)). + +t_create_no_bookmark(_) -> + Limit = 99, + Options = [{limit, Limit}, {skip, skip}, {fields, fields}, {bookmark, nil}], + Options1 = [{limit, Limit}, {skip, skip}, {fields, fields}, {bookmark, []}], + Cursor = #cursor{ + db = db, + index = index, + ranges = null, + trace = trace, + selector = selector, + opts = Options1, + limit = Limit, + skip = skip, + fields = fields + }, + ?assertEqual({ok, Cursor}, create(db, {[index], trace}, selector, Options)). + +t_create_invalid_bookmark(_) -> + Options = [{bookmark, invalid}], + Exception = {mango_error, mango_cursor_text, {invalid_bookmark, invalid}}, + meck:expect(dreyfus_bookmark, unpack, [db_name, invalid], meck:raise(error, something)), + ?assertThrow(Exception, create(db, {[index], trace}, selector, Options)). + +execute_test_() -> + { + foreach, + fun() -> + meck:new(foo, [non_strict]), + meck:expect(couch_db, name, [db], meck:val(db_name)), + meck:expect(couch_stats, increment_counter, [[mango, docs_examined]], meck:val(ok)), + % Dummy mock functions to progressively update the + % respective states therefore their results could be + % asserted later on. + meck:expect( + dreyfus_bookmark, + pack, + fun(Bookmark) -> + case Bookmark of + nil -> null; + [bookmark, N] -> [bookmark, N] + end + end + ), + meck:expect( + dreyfus_bookmark, + update, + fun(_Sort, [bookmark, N], [#sortable{}]) -> [bookmark, N + 1] end + ), + meck:expect( + dreyfus_fabric, + get_json_docs, + fun(db_name, Ids) -> + IdDocs = lists:flatmap( + fun({id, N} = Id) -> + case N of + not_found -> []; + _ -> [{Id, {doc, {doc, N}}}] + end + end, + Ids + ), + {ok, IdDocs} + end + ), + meck:expect(mango_execution_stats, log_start, [stats], meck:val({stats, 0})), + meck:expect( + mango_execution_stats, + maybe_add_stats, + fun(_Options, _UserFun, {stats, N}, {acc, M}) -> {{acc, M + 1}, {stats, N + 1}} end + ), + meck:expect(mango_execution_stats, log_stats, [{stats, '_'}], meck:val(ok)), + meck:expect( + mango_cursor, + maybe_add_warning, + fun(_UserFun, _Cursor, {stats, _}, {acc, M}) -> {acc, M + 1} end + ), + meck:expect( + mango_execution_stats, + incr_docs_examined, + fun({stats, N}) -> {stats, N + 1} end + ), + meck:expect( + mango_execution_stats, + incr_results_returned, + fun({stats, N}) -> {stats, N + 1} end + ), + meck:expect( + mango_selector_text, + append_sort_type, + fun(RawField, selector) -> <<RawField/binary, "<type>">> end + ), + meck:expect(mango_fields, extract, fun({doc, N}, fields) -> {final_doc, N} end), + meck:expect( + foo, + add_key_only, + fun({add_key, bookmark, [bookmark, _]}, {acc, N}) -> {ok, {acc, N + 1}} end + ), + meck:expect( + foo, + normal, + fun(Args, {acc, N}) -> + case Args of + {row, {final_doc, _}} -> ok; + {add_key, bookmark, [bookmark, _]} -> ok + end, + {ok, {acc, N + 1}} + end + ) + end, + fun(_) -> meck:unload() end, + [ + ?TDEF_FE(t_execute_empty, 10), + ?TDEF_FE(t_execute_no_results, 10), + ?TDEF_FE(t_execute_more_results, 10), + ?TDEF_FE(t_execute_unique_results, 10), + ?TDEF_FE(t_execute_limit_cutoff, 10), + ?TDEF_FE(t_execute_limit_zero, 10), + ?TDEF_FE(t_execute_limit_unique, 10), + ?TDEF_FE(t_execute_skip, 10), + ?TDEF_FE(t_execute_skip_unique, 10), + ?TDEF_FE(t_execute_no_matches, 10), + ?TDEF_FE(t_execute_mixed_matches, 10), + ?TDEF_FE(t_execute_user_fun_returns_stop, 10), + ?TDEF_FE(t_execute_search_error, 10) + ] + }. + +t_execute_empty(_) -> + Options = [{partition, partition}, {sort, {[]}}, {bookmark, [bookmark, 0]}], + Cursor = #cursor{ + db = db, + index = #idx{ddoc = <<"ddoc">>}, + limit = limit, + skip = skip, + fields = fields, + selector = selector, + opts = Options, + execution_stats = stats + }, + meck:expect(mango_selector_text, convert, [selector], meck:val(<<>>)), + ?assertEqual({ok, {acc, 3}}, execute(Cursor, fun foo:add_key_only/2, {acc, 0})), + ?assertEqual(0, meck:num_calls(couch_stats, increment_counter, 1)), + ?assertEqual(0, meck:num_calls(mango_execution_stats, incr_docs_examined, 1)), + ?assertEqual(0, meck:num_calls(mango_execution_stats, incr_results_returned, 1)). + +t_execute_no_results(_) -> + Limit = 10, + Skip = 0, + IdxDDoc = <<"ddoc">>, + IdxName = <<"index">>, + Sort = [<<"field1">>, <<"field2">>], + Options = [{partition, partition}, {sort, {Sort}}, {bookmark, [bookmark, 0]}], + Cursor = #cursor{ + db = db, + index = #idx{ddoc = IdxDDoc, name = IdxName}, + limit = Limit, + skip = Skip, + fields = fields, + selector = selector, + opts = Options, + execution_stats = stats + }, + QueryArgs = + #index_query_args{ + q = query, + partition = partition, + limit = Skip + Limit, + bookmark = [bookmark, 0], + sort = [<<"field1<type>">>, <<"field2<type>">>], + raw_bookmark = true + }, + meck:expect( + dreyfus_fabric_search, + go, + [db_name, IdxDDoc, IdxName, QueryArgs], + meck:val({ok, [bookmark, 1], undefined, [], undefined, undefined}) + ), + meck:expect(mango_selector_text, convert, [selector], meck:val(query)), + meck:expect(mango_selector, match, fun(selector, {doc, _}) -> true end), + ?assertEqual({ok, {acc, 3}}, execute(Cursor, fun foo:add_key_only/2, {acc, 0})), + ?assertEqual(0, meck:num_calls(dreyfus_fabric, get_json_docs, 2)), + ?assertEqual(0, meck:num_calls(couch_stats, increment_counter, 1)), + ?assertEqual(0, meck:num_calls(mango_execution_stats, incr_docs_examined, 1)), + ?assertEqual(0, meck:num_calls(mango_execution_stats, incr_results_returned, 1)). + +t_execute_more_results(_) -> + Options = [{partition, partition}, {sort, {[]}}, {bookmark, [bookmark, 0]}], + Cursor = #cursor{ + db = db, + index = #idx{ddoc = <<"ddoc">>, name = <<"index">>}, + limit = 10, + skip = 0, + fields = fields, + selector = selector, + opts = Options, + execution_stats = stats + }, + meck:expect( + dreyfus_fabric_search, + go, + fun(db_name, <<"ddoc">>, <<"index">>, QueryArgs) -> + #index_query_args{ + q = query, + partition = partition, + bookmark = [bookmark, B], + sort = relevance, + raw_bookmark = true + } = QueryArgs, + Hits = + case B of + 0 -> + Hit1 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 1}}]}}, + Hit2 = #sortable{item = #hit{fields = [{<<"_id">>, {id, not_found}}]}}, + Hit3 = #sortable{item = #hit{fields = [{<<"_id">>, {id, not_found}}]}}, + [Hit1, Hit2, Hit3]; + 4 -> + Hit4 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 4}}]}}, + Hit5 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 5}}]}}, + [Hit4, Hit5]; + _ -> + [] + end, + {ok, [bookmark, B + 1], undefined, Hits, undefined, undefined} + end + ), + meck:expect(mango_selector_text, convert, [selector], meck:val(query)), + meck:expect(mango_selector, match, fun(selector, {doc, _}) -> true end), + ?assertEqual({ok, {acc, 6}}, execute(Cursor, fun foo:normal/2, {acc, 0})), + ?assertEqual(3, meck:num_calls(couch_stats, increment_counter, 1)), + ?assertEqual(3, meck:num_calls(mango_execution_stats, incr_docs_examined, 1)), + ?assertEqual(3, meck:num_calls(mango_execution_stats, incr_results_returned, 1)). + +t_execute_unique_results(_) -> + Options = [{partition, partition}, {sort, {[]}}, {bookmark, []}], + Cursor = #cursor{ + db = db, + index = #idx{ddoc = <<"ddoc">>, name = <<"index">>}, + limit = 10, + skip = 0, + fields = fields, + selector = selector, + opts = Options, + execution_stats = stats + }, + meck:expect( + dreyfus_fabric_search, + go, + fun(db_name, <<"ddoc">>, <<"index">>, QueryArgs) -> + #index_query_args{ + q = query, + partition = partition, + bookmark = B, + sort = relevance, + raw_bookmark = true + } = QueryArgs, + {Bookmark, Hits} = + case B of + nil -> + Hit1 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 1}}]}}, + Hit2 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 2}}]}}, + Hit3 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 3}}]}}, + {[bookmark, 0], [Hit1, Hit2, Hit3]}; + [bookmark, 3] -> + Hit1 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 1}}]}}, + Hit2 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 2}}]}}, + Hit3 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 3}}]}}, + {[bookmark, 4], [Hit3, Hit2, Hit1]}; + [bookmark, N] -> + {[bookmark, N + 1], []} + end, + {ok, Bookmark, undefined, Hits, undefined, undefined} + end + ), + meck:expect(mango_selector_text, convert, [selector], meck:val(query)), + meck:expect(mango_selector, match, fun(selector, {doc, _}) -> true end), + ?assertEqual({ok, {acc, 9}}, execute(Cursor, fun foo:normal/2, {acc, 0})), + ?assertEqual(6, meck:num_calls(couch_stats, increment_counter, 1)), + ?assertEqual(6, meck:num_calls(mango_execution_stats, incr_docs_examined, 1)), + ?assertEqual(6, meck:num_calls(mango_execution_stats, incr_results_returned, 1)). + +t_execute_limit_cutoff(_) -> + Limit = 2, + Skip = 0, + IdxName = <<"index">>, + Sort = [{<<"field1">>, <<"desc">>}, {<<"field2">>, <<"asc">>}], + Options = [{partition, partition}, {sort, {Sort}}, {bookmark, []}], + Cursor = #cursor{ + db = db, + index = #idx{ddoc = <<"_design/ddoc">>, name = IdxName}, + limit = Limit, + skip = Skip, + fields = fields, + selector = selector, + opts = Options, + execution_stats = stats + }, + QueryArgs = + #index_query_args{ + q = query, + partition = partition, + limit = Skip + Limit, + bookmark = nil, + sort = [<<"-field1<type>">>, <<"field2<type>">>], + raw_bookmark = true + }, + Hit1 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 1}}]}}, + Hit2 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 2}}]}}, + Hit3 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 3}}]}}, + Hits = [Hit1, Hit2, Hit3], + meck:expect( + dreyfus_fabric_search, + go, + [db_name, <<"ddoc">>, IdxName, QueryArgs], + meck:val({ok, [bookmark, 1], undefined, Hits, undefined, undefined}) + ), + meck:expect(mango_selector_text, convert, [selector], meck:val(query)), + meck:expect(mango_selector, match, fun(selector, {doc, _}) -> true end), + ?assertEqual({ok, {acc, 5}}, execute(Cursor, fun foo:normal/2, {acc, 0})), + ?assertEqual(Limit, meck:num_calls(couch_stats, increment_counter, 1)), + ?assertEqual(Limit, meck:num_calls(mango_execution_stats, incr_docs_examined, 1)), + ?assertEqual(Limit, meck:num_calls(mango_execution_stats, incr_results_returned, 1)). + +t_execute_limit_zero(_) -> + Limit = 0, + Skip = 0, + IdxName = <<"index">>, + Options = [{partition, <<>>}, {sort, {[]}}, {bookmark, [bookmark, 0]}], + Cursor = #cursor{ + db = db, + index = #idx{ddoc = <<"_design/ddoc">>, name = IdxName}, + limit = Limit, + skip = Skip, + fields = fields, + selector = selector, + opts = Options, + execution_stats = stats + }, + QueryArgs = + #index_query_args{ + q = query, + partition = nil, + limit = Skip + Limit, + bookmark = [bookmark, 0], + sort = relevance, + raw_bookmark = true + }, + Hit1 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 1}}]}}, + Hit2 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 2}}]}}, + Hit3 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 3}}]}}, + Hits = [Hit1, Hit2, Hit3], + meck:expect( + dreyfus_fabric_search, + go, + [db_name, <<"ddoc">>, IdxName, QueryArgs], + meck:val({ok, [bookmark, 1], undefined, Hits, undefined, undefined}) + ), + meck:expect(mango_selector_text, convert, [selector], meck:val(query)), + meck:expect(mango_selector, match, fun(selector, {doc, _}) -> true end), + ?assertEqual({ok, {acc, 3}}, execute(Cursor, fun foo:add_key_only/2, {acc, 0})), + ?assertEqual(1, meck:num_calls(couch_stats, increment_counter, 1)), + ?assertEqual(1, meck:num_calls(mango_execution_stats, incr_docs_examined, 1)), + ?assertEqual(Limit, meck:num_calls(mango_execution_stats, incr_results_returned, 1)). + +t_execute_limit_unique(_) -> + Limit = 5, + Options = [{partition, partition}, {sort, {[]}}, {bookmark, []}], + Cursor = #cursor{ + db = db, + index = #idx{ddoc = <<"ddoc">>, name = <<"index">>}, + limit = Limit, + skip = 0, + fields = fields, + selector = selector, + opts = Options, + execution_stats = stats + }, + meck:expect( + dreyfus_fabric_search, + go, + fun(db_name, <<"ddoc">>, <<"index">>, QueryArgs) -> + #index_query_args{ + q = query, + partition = partition, + bookmark = B, + sort = relevance, + raw_bookmark = true + } = QueryArgs, + {Bookmark, Hits} = + case B of + nil -> + Hit1 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 1}}]}}, + Hit2 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 2}}]}}, + Hit3 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 3}}]}}, + {[bookmark, 0], [Hit1, Hit2, Hit3]}; + [bookmark, 3] -> + Hit1 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 1}}]}}, + Hit2 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 2}}]}}, + Hit3 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 3}}]}}, + {[bookmark, 4], [Hit3, Hit2, Hit1]} + end, + {ok, Bookmark, undefined, Hits, undefined, undefined} + end + ), + meck:expect(mango_selector_text, convert, [selector], meck:val(query)), + meck:expect(mango_selector, match, fun(selector, {doc, _}) -> true end), + ?assertEqual({ok, {acc, 8}}, execute(Cursor, fun foo:normal/2, {acc, 0})), + ?assertEqual(Limit, meck:num_calls(couch_stats, increment_counter, 1)), + ?assertEqual(Limit, meck:num_calls(mango_execution_stats, incr_docs_examined, 1)), + ?assertEqual(Limit, meck:num_calls(mango_execution_stats, incr_results_returned, 1)). + +t_execute_skip(_) -> + UniqueHits = 3, + Skip = 2, + Options = [{partition, <<>>}, {sort, {[]}}, {bookmark, [bookmark, 0]}], + Cursor = #cursor{ + db = db, + index = #idx{ddoc = <<"_design/ddoc">>, name = <<"index">>}, + limit = 10, + skip = Skip, + fields = fields, + selector = selector, + opts = Options, + execution_stats = stats + }, + meck:expect( + dreyfus_fabric_search, + go, + fun(db_name, <<"ddoc">>, <<"index">>, QueryArgs) -> + #index_query_args{ + q = query, + partition = nil, + bookmark = [bookmark, B], + sort = relevance, + raw_bookmark = true + } = QueryArgs, + Hits = + case B of + 0 -> + Hit1 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 1}}]}}, + Hit2 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 2}}]}}, + Hit3 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 3}}]}}, + [Hit1, Hit2, Hit3]; + _ -> + [] + end, + {ok, [bookmark, B + 1], undefined, Hits, undefined, undefined} + end + ), + meck:expect(mango_selector_text, convert, [selector], meck:val(query)), + meck:expect(mango_selector, match, fun(selector, {doc, _}) -> true end), + ?assertEqual({ok, {acc, 4}}, execute(Cursor, fun foo:normal/2, {acc, 0})), + ?assertEqual(UniqueHits, meck:num_calls(couch_stats, increment_counter, 1)), + ?assertEqual(UniqueHits, meck:num_calls(mango_execution_stats, incr_docs_examined, 1)), + ?assertEqual( + UniqueHits - Skip, meck:num_calls(mango_execution_stats, incr_results_returned, 1) + ). + +t_execute_skip_unique(_) -> + AllHits = 6, + Options = [{partition, partition}, {sort, {[]}}, {bookmark, []}], + Cursor = #cursor{ + db = db, + index = #idx{ddoc = <<"ddoc">>, name = <<"index">>}, + limit = 10, + skip = 2, + fields = fields, + selector = selector, + opts = Options, + execution_stats = stats + }, + meck:expect( + dreyfus_fabric_search, + go, + fun(db_name, <<"ddoc">>, <<"index">>, QueryArgs) -> + #index_query_args{ + q = query, + partition = partition, + bookmark = B, + sort = relevance, + raw_bookmark = true + } = QueryArgs, + {Bookmark, Hits} = + case B of + nil -> + Hit1 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 1}}]}}, + Hit2 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 2}}]}}, + Hit3 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 3}}]}}, + {[bookmark, 0], [Hit1, Hit2, Hit3]}; + [bookmark, 3] -> + Hit1 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 1}}]}}, + Hit2 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 2}}]}}, + Hit3 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 3}}]}}, + {[bookmark, 4], [Hit3, Hit2, Hit1]}; + [bookmark, N] -> + {[bookmark, N + 1], []} + end, + {ok, Bookmark, undefined, Hits, undefined, undefined} + end + ), + meck:expect(mango_selector_text, convert, [selector], meck:val(query)), + meck:expect(mango_selector, match, fun(selector, {doc, _}) -> true end), + ?assertEqual({ok, {acc, 7}}, execute(Cursor, fun foo:normal/2, {acc, 0})), + ?assertEqual(AllHits, meck:num_calls(couch_stats, increment_counter, 1)), + ?assertEqual(AllHits, meck:num_calls(mango_execution_stats, incr_docs_examined, 1)), + ?assertEqual(4, meck:num_calls(mango_execution_stats, incr_results_returned, 1)). + +t_execute_no_matches(_) -> + UniqueHits = 3, + Matches = 0, + Options = [{partition, partition}, {sort, {[]}}, {bookmark, [bookmark, 0]}], + Cursor = #cursor{ + db = db, + index = #idx{ddoc = <<"ddoc">>, name = <<"index">>}, + limit = 10, + skip = 0, + fields = fields, + selector = selector, + opts = Options, + execution_stats = stats + }, + meck:expect( + dreyfus_fabric_search, + go, + fun(db_name, <<"ddoc">>, <<"index">>, QueryArgs) -> + #index_query_args{ + q = query, + partition = partition, + bookmark = [bookmark, B], + sort = relevance, + raw_bookmark = true + } = QueryArgs, + Hits = + case B of + 0 -> + Hit1 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 1}}]}}, + Hit2 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 2}}]}}, + Hit3 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 3}}]}}, + [Hit1, Hit2, Hit3]; + _ -> + [] + end, + {ok, [bookmark, B + 1], undefined, Hits, undefined, undefined} + end + ), + meck:expect(mango_selector_text, convert, [selector], meck:val(query)), + meck:expect(mango_selector, match, fun(selector, {doc, _}) -> false end), + ?assertEqual({ok, {acc, 3}}, execute(Cursor, fun foo:add_key_only/2, {acc, 0})), + ?assertEqual(UniqueHits, meck:num_calls(couch_stats, increment_counter, 1)), + ?assertEqual(UniqueHits, meck:num_calls(mango_execution_stats, incr_docs_examined, 1)), + ?assertEqual(Matches, meck:num_calls(mango_execution_stats, incr_results_returned, 1)). + +t_execute_mixed_matches(_) -> + UniqueHits = 3, + Matches = 1, + Options = [{partition, partition}, {sort, {[]}}, {bookmark, [bookmark, 0]}], + Cursor = #cursor{ + db = db, + index = #idx{ddoc = <<"ddoc">>, name = <<"index">>}, + limit = 10, + skip = 0, + fields = fields, + selector = selector, + opts = Options, + execution_stats = stats + }, + meck:expect( + dreyfus_fabric_search, + go, + fun(db_name, <<"ddoc">>, <<"index">>, QueryArgs) -> + #index_query_args{ + q = query, + partition = partition, + bookmark = [bookmark, B], + sort = relevance, + raw_bookmark = true + } = QueryArgs, + Hits = + case B of + 0 -> + Hit1 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 1}}]}}, + Hit2 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 2}}]}}, + Hit3 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 3}}]}}, + [Hit1, Hit2, Hit3]; + _ -> + [] + end, + {ok, [bookmark, B + 1], undefined, Hits, undefined, undefined} + end + ), + meck:expect(mango_selector_text, convert, [selector], meck:val(query)), + meck:expect(mango_selector, match, fun(selector, {doc, N}) -> N == 2 end), + ?assertEqual({ok, {acc, 4}}, execute(Cursor, fun foo:normal/2, {acc, 0})), + ?assertEqual(UniqueHits, meck:num_calls(couch_stats, increment_counter, 1)), + ?assertEqual(UniqueHits, meck:num_calls(mango_execution_stats, incr_docs_examined, 1)), + ?assertEqual(Matches, meck:num_calls(mango_execution_stats, incr_results_returned, 1)). + +t_execute_user_fun_returns_stop(_) -> + UniqueHits = 3, + Limit = 10, + Skip = 0, + IdxDDoc = <<"ddoc">>, + IdxName = <<"index">>, + Options = [{partition, partition}, {sort, {[]}}, {bookmark, [bookmark, 0]}], + Cursor = #cursor{ + db = db, + index = #idx{ddoc = IdxDDoc, name = IdxName}, + limit = Limit, + skip = Skip, + fields = fields, + selector = selector, + opts = Options, + execution_stats = stats + }, + QueryArgs = + #index_query_args{ + q = query, + partition = partition, + limit = Skip + Limit, + bookmark = [bookmark, 0], + sort = relevance, + raw_bookmark = true + }, + Hit1 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 1}}]}}, + Hit2 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 2}}]}}, + Hit3 = #sortable{item = #hit{fields = [{<<"_id">>, {id, 3}}]}}, + Hits = [Hit1, Hit2, Hit3], + meck:expect( + dreyfus_fabric_search, + go, + [db_name, IdxDDoc, IdxName, QueryArgs], + meck:val({ok, [bookmark, 1], undefined, Hits, undefined, undefined}) + ), + meck:expect(mango_selector_text, convert, [selector], meck:val(query)), + meck:expect(mango_selector, match, fun(selector, {doc, _}) -> true end), + meck:expect( + foo, + stops, + fun(Args, {acc, N}) -> + case Args of + {row, {final_doc, _}} -> ok; + {add_key, bookmark, [bookmark, _]} -> ok + end, + Status = + case N of + 2 -> stop; + _ -> ok + end, + {Status, {acc, N + 1}} + end + ), + ?assertEqual({ok, {acc, 6}}, execute(Cursor, fun foo:stops/2, {acc, 0})), + ?assertEqual(UniqueHits, meck:num_calls(couch_stats, increment_counter, 1)), + ?assertEqual(UniqueHits, meck:num_calls(mango_execution_stats, incr_docs_examined, 1)), + ?assertEqual(3, meck:num_calls(mango_execution_stats, incr_results_returned, 1)). + +t_execute_search_error(_) -> + Limit = 10, + Skip = 0, + IdxDDoc = <<"ddoc">>, + IdxName = <<"index">>, + Options = [{partition, <<>>}, {sort, {[]}}, {bookmark, [bookmark, 0]}], + Cursor = #cursor{ + db = db, + index = #idx{ddoc = IdxDDoc, name = IdxName}, + limit = Limit, + skip = Skip, + fields = fields, + selector = selector, + opts = Options, + execution_stats = stats + }, + QueryArgs = + #index_query_args{ + q = query, + partition = nil, + limit = Skip + Limit, + bookmark = [bookmark, 0], + sort = relevance, + raw_bookmark = true + }, + meck:expect( + dreyfus_fabric_search, + go, + [db_name, IdxDDoc, IdxName, QueryArgs], + meck:val({error, reason}) + ), + meck:expect(mango_selector_text, convert, [selector], meck:val(query)), + Exception = {mango_error, mango_cursor_text, {text_search_error, {error, reason}}}, + ?assertThrow(Exception, execute(Cursor, fun foo:normal/2, {acc, 0})), + ?assertEqual(0, meck:num_calls(dreyfus_fabric, get_json_docs, 2)), + ?assertEqual(0, meck:num_calls(foo, normal, 2)), + ?assertEqual(0, meck:num_calls(couch_stats, increment_counter, 1)), + ?assertEqual(0, meck:num_calls(mango_execution_stats, incr_docs_examined, 1)), + ?assertEqual(0, meck:num_calls(mango_execution_stats, incr_results_returned, 1)). + +explain_test_() -> + { + foreach, + fun() -> ok end, + fun(_) -> meck:unload() end, + [ + ?TDEF_FE(t_explain) + ] + }. + +t_explain(_) -> + Options = [{partition, partition}, {sort, {[]}}], + Cursor = + #cursor{ + selector = selector, + opts = Options + }, + Response = + [ + {query, converted_selector}, + {partition, partition}, + {sort, relevance} + ], + meck:expect(mango_selector_text, convert, [selector], meck:val(converted_selector)), + ?assertEqual(Response, explain(Cursor)). + +-endif. + -endif.
