blob: bc88b970cda8bdb4455766d4386fa75ad4e60a89 [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.
% This module is for the "index object" as in, the data structure
% representing an index. Not to be confused with mango_index which
% contains APIs for managing indexes.
-module(mango_idx).
-export([
list/1,
recover/1,
for_sort/2,
new/2,
validate_new/2,
add/2,
remove/2,
from_ddoc/2,
special/1,
dbname/1,
ddoc/1,
name/1,
type/1,
def/1,
opts/1,
columns/1,
is_usable/2,
start_key/2,
end_key/2,
cursor_mod/1,
idx_mod/1,
to_json/1,
delete/4,
get_usable_indexes/3
]).
-include_lib("couch/include/couch_db.hrl").
-include("mango.hrl").
-include("mango_idx.hrl").
list(Db) ->
{ok, Indexes} = ddoc_cache:open(db_to_name(Db), ?MODULE),
Indexes.
get_usable_indexes(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 = mango_cursor: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,
lists:filter(UsableFilter, SortIndexes).
recover(Db) ->
{ok, DDocs0} = mango_util:open_ddocs(Db),
Pred = fun({Props}) ->
case proplists:get_value(<<"language">>, Props) of
<<"query">> -> true;
_ -> false
end
end,
DDocs = lists:filter(Pred, DDocs0),
Special = special(Db),
{ok, Special ++ lists:flatmap(fun(Doc) ->
from_ddoc(Db, Doc)
end, DDocs)}.
for_sort(Indexes, Opts) ->
% If a sort was specified we have to find an index that
% can satisfy the request.
case lists:keyfind(sort, 1, Opts) of
{sort, {SProps}} when is_list(SProps) ->
for_sort_int(Indexes, {SProps});
_ ->
Indexes
end.
for_sort_int(Indexes, Sort) ->
Fields = mango_sort:fields(Sort),
FilterFun = fun(Idx) ->
Cols = mango_idx:columns(Idx),
case {mango_idx:type(Idx), Cols} of
{_, all_fields} ->
true;
{<<"text">>, _} ->
sets:is_subset(sets:from_list(Fields), sets:from_list(Cols));
{<<"json">>, _} ->
lists:prefix(Fields, Cols);
{<<"special">>, _} ->
lists:prefix(Fields, Cols)
end
end,
lists:filter(FilterFun, Indexes).
new(Db, Opts) ->
Def = get_idx_def(Opts),
Type = get_idx_type(Opts),
IdxName = get_idx_name(Def, Opts),
DDoc = get_idx_ddoc(Def, Opts),
{ok, #idx{
dbname = db_to_name(Db),
ddoc = DDoc,
name = IdxName,
type = Type,
def = Def,
opts = filter_opts(Opts)
}}.
validate_new(Idx, Db) ->
Mod = idx_mod(Idx),
Mod:validate_new(Idx, Db).
add(DDoc, Idx) ->
Mod = idx_mod(Idx),
{ok, NewDDoc} = Mod:add(DDoc, Idx),
% Round trip through JSON for normalization
Body = ?JSON_DECODE(?JSON_ENCODE(NewDDoc#doc.body)),
{ok, NewDDoc#doc{body = Body}}.
remove(DDoc, Idx) ->
Mod = idx_mod(Idx),
{ok, NewDDoc} = Mod:remove(DDoc, Idx),
% Round trip through JSON for normalization
Body = ?JSON_DECODE(?JSON_ENCODE(NewDDoc#doc.body)),
{ok, NewDDoc#doc{body = Body}}.
delete(Filt, Db, Indexes, DelOpts) ->
case lists:filter(Filt, Indexes) of
[Idx] ->
{ok, DDoc} = mango_util:load_ddoc(Db, mango_idx:ddoc(Idx)),
{ok, NewDDoc} = mango_idx:remove(DDoc, Idx),
FinalDDoc = case NewDDoc#doc.body of
{[{<<"language">>, <<"query">>}]} ->
NewDDoc#doc{deleted = true, body = {[]}};
_ ->
NewDDoc
end,
case mango_crud:insert(Db, FinalDDoc, DelOpts) of
{ok, _} ->
{ok, true};
Error ->
{error, Error}
end;
[] ->
{error, not_found}
end.
from_ddoc(Db, {Props}) ->
DbName = db_to_name(Db),
DDoc = proplists:get_value(<<"_id">>, Props),
case proplists:get_value(<<"language">>, Props) of
<<"query">> -> ok;
_ ->
?MANGO_ERROR(invalid_query_ddoc_language)
end,
IdxMods = case module_loaded(dreyfus_index) of
true ->
[mango_idx_view, mango_idx_text];
false ->
[mango_idx_view]
end,
Idxs = lists:flatmap(fun(Mod) -> Mod:from_ddoc({Props}) end, IdxMods),
lists:map(fun(Idx) ->
Idx#idx{
dbname = DbName,
ddoc = DDoc
}
end, Idxs).
special(Db) ->
AllDocs = #idx{
dbname = db_to_name(Db),
name = <<"_all_docs">>,
type = <<"special">>,
def = all_docs,
opts = []
},
% Add one for _update_seq
[AllDocs].
dbname(#idx{dbname=DbName}) ->
DbName.
ddoc(#idx{ddoc=DDoc}) ->
DDoc.
name(#idx{name=Name}) ->
Name.
type(#idx{type=Type}) ->
Type.
def(#idx{def=Def}) ->
Def.
opts(#idx{opts=Opts}) ->
Opts.
to_json(#idx{}=Idx) ->
Mod = idx_mod(Idx),
Mod:to_json(Idx).
columns(#idx{}=Idx) ->
Mod = idx_mod(Idx),
Mod:columns(Idx).
is_usable(#idx{}=Idx, Selector) ->
Mod = idx_mod(Idx),
Mod:is_usable(Idx, Selector).
start_key(#idx{}=Idx, Ranges) ->
Mod = idx_mod(Idx),
Mod:start_key(Ranges).
end_key(#idx{}=Idx, Ranges) ->
Mod = idx_mod(Idx),
Mod:end_key(Ranges).
cursor_mod(#idx{type = <<"json">>}) ->
mango_cursor_view;
cursor_mod(#idx{def = all_docs, type= <<"special">>}) ->
mango_cursor_special;
cursor_mod(#idx{type = <<"text">>}) ->
case module_loaded(dreyfus_index) of
true ->
mango_cursor_text;
false ->
?MANGO_ERROR({index_service_unavailable, <<"text">>})
end.
idx_mod(#idx{type = <<"json">>}) ->
mango_idx_view;
idx_mod(#idx{type = <<"special">>}) ->
mango_idx_special;
idx_mod(#idx{type = <<"text">>}) ->
case module_loaded(dreyfus_index) of
true ->
mango_idx_text;
false ->
?MANGO_ERROR({index_service_unavailable, <<"text">>})
end.
db_to_name(#db{name=Name}) ->
Name;
db_to_name(Name) when is_binary(Name) ->
Name;
db_to_name(Name) when is_list(Name) ->
iolist_to_binary(Name).
get_idx_def(Opts) ->
case proplists:get_value(def, Opts) of
undefined ->
?MANGO_ERROR(no_index_definition);
Def ->
Def
end.
get_idx_type(Opts) ->
case proplists:get_value(type, Opts) of
<<"json">> -> <<"json">>;
<<"text">> -> case module_loaded(dreyfus_index) of
true ->
<<"text">>;
false ->
?MANGO_ERROR({index_service_unavailable, <<"text">>})
end;
%<<"geo">> -> <<"geo">>;
undefined -> <<"json">>;
BadType ->
?MANGO_ERROR({invalid_index_type, BadType})
end.
get_idx_ddoc(Idx, Opts) ->
case proplists:get_value(ddoc, Opts) of
<<"_design/", _Rest>> = Name ->
Name;
Name when is_binary(Name) ->
<<"_design/", Name/binary>>;
_ ->
Bin = gen_name(Idx, Opts),
<<"_design/", Bin/binary>>
end.
get_idx_name(Idx, Opts) ->
case proplists:get_value(name, Opts) of
Name when is_binary(Name) ->
Name;
_ ->
gen_name(Idx, Opts)
end.
gen_name(Idx, Opts0) ->
Opts = lists:usort(Opts0),
TermBin = term_to_binary({Idx, Opts}),
Sha = couch_crypto:hash(sha, TermBin),
mango_util:enc_hex(Sha).
filter_opts([]) ->
[];
filter_opts([{user_ctx, _} | Rest]) ->
filter_opts(Rest);
filter_opts([{ddoc, _} | Rest]) ->
filter_opts(Rest);
filter_opts([{name, _} | Rest]) ->
filter_opts(Rest);
filter_opts([{type, _} | Rest]) ->
filter_opts(Rest);
filter_opts([{w, _} | Rest]) ->
filter_opts(Rest);
filter_opts([Opt | Rest]) ->
[Opt | filter_opts(Rest)].