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.

Reply via email to