blob: 6cb7fee8e78bbc3b4eec0b6ff00edb86bdedb522 [file] [log] [blame]
% Licensed under the Apache License, Version 2.0 (the "License"); you may not
% use this file except in compliance with the License. You may obtain a copy of
% the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
% License for the specific language governing permissions and limitations under
% the License.
-module(mango_cursor).
-export([
create/3,
explain/1,
execute/3
]).
-include_lib("couch/include/couch_db.hrl").
-include("mango.hrl").
-include("mango_cursor.hrl").
-define(SUPERVISOR, mango_cursor_sup).
create(Db, Selector0, Opts) ->
Selector = mango_selector:normalize(Selector0),
ExistingIndexes = mango_idx:list(Db),
if ExistingIndexes /= [] -> ok; true ->
?MANGO_ERROR({no_usable_index, no_indexes_defined})
end,
FilteredIndexes = maybe_filter_indexes(ExistingIndexes, Opts),
if FilteredIndexes /= [] -> ok; true ->
?MANGO_ERROR({no_usable_index, no_index_matching_name})
end,
SortIndexes = mango_idx:for_sort(FilteredIndexes, Opts),
if SortIndexes /= [] -> ok; true ->
?MANGO_ERROR({no_usable_index, missing_sort_index})
end,
UsableFilter = fun(I) -> mango_idx:is_usable(I, Selector) end,
UsableIndexes = lists:filter(UsableFilter, SortIndexes),
if UsableIndexes /= [] -> ok; true ->
?MANGO_ERROR({no_usable_index, selector_unsupported})
end,
create_cursor(Db, UsableIndexes, Selector, Opts).
explain(#cursor{}=Cursor) ->
#cursor{
index = Idx,
selector = Selector,
opts = Opts0,
limit = Limit,
skip = Skip,
fields = Fields
} = Cursor,
Mod = mango_idx:cursor_mod(Idx),
Opts = lists:keydelete(user_ctx, 1, Opts0),
{[
{dbname, mango_idx:dbname(Idx)},
{index, mango_idx:to_json(Idx)},
{selector, Selector},
{opts, {Opts}},
{limit, Limit},
{skip, Skip},
{fields, Fields}
] ++ Mod:explain(Cursor)}.
execute(#cursor{index=Idx}=Cursor, UserFun, UserAcc) ->
Mod = mango_idx:cursor_mod(Idx),
Mod:execute(Cursor, UserFun, UserAcc).
maybe_filter_indexes(Indexes, Opts) ->
case lists:keyfind(use_index, 1, Opts) of
{use_index, []} ->
Indexes;
{use_index, [DesignId]} ->
filter_indexes(Indexes, DesignId);
{use_index, [DesignId, ViewName]} ->
filter_indexes(Indexes, DesignId, ViewName)
end.
filter_indexes(Indexes, DesignId0) ->
DesignId = case DesignId0 of
<<"_design/", _/binary>> ->
DesignId0;
Else ->
<<"_design/", Else/binary>>
end,
FiltFun = fun(I) -> mango_idx:ddoc(I) == DesignId end,
lists:filter(FiltFun, Indexes).
filter_indexes(Indexes0, DesignId, ViewName) ->
Indexes = filter_indexes(Indexes0, DesignId),
FiltFun = fun(I) -> mango_idx:name(I) == ViewName end,
lists:filter(FiltFun, Indexes).
create_cursor(Db, Indexes, Selector, Opts) ->
[{CursorMod, CursorModIndexes} | _] = group_indexes_by_type(Indexes),
CursorMod:create(Db, CursorModIndexes, Selector, Opts).
group_indexes_by_type(Indexes) ->
IdxDict = lists:foldl(fun(I, D) ->
dict:append(mango_idx:cursor_mod(I), I, D)
end, dict:new(), Indexes),
% The first cursor module that has indexes will be
% used to service this query. This is so that we
% don't suddenly switch indexes for existing client
% queries.
CursorModules = case module_loaded(dreyfus_index) of
true ->
[mango_cursor_view, mango_cursor_text];
false ->
[mango_cursor_view]
end,
lists:flatmap(fun(CMod) ->
case dict:find(CMod, IdxDict) of
{ok, CModIndexes} ->
[{CMod, CModIndexes}];
error ->
[]
end
end, CursorModules).