blob: 6d0fb2400d61b2ff24bc3233f1cc0ac9dcb77740 [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_native_proc).
-behavior(gen_server).
-include("mango_idx.hrl").
-export([
start_link/0,
set_timeout/2,
prompt/2
]).
-export([
init/1,
terminate/2,
handle_call/3,
handle_cast/2,
handle_info/2,
code_change/3
]).
-record(st, {
indexes = [],
timeout = 5000
}).
-record(tacc, {
index_array_lengths = true,
fields = all_fields,
path = []
}).
start_link() ->
gen_server:start_link(?MODULE, [], []).
set_timeout(Pid, TimeOut) when is_integer(TimeOut), TimeOut > 0 ->
gen_server:call(Pid, {set_timeout, TimeOut}).
prompt(Pid, Data) ->
gen_server:call(Pid, {prompt, Data}).
init(_) ->
{ok, #st{}}.
terminate(_Reason, _St) ->
ok.
handle_call({set_timeout, TimeOut}, _From, St) ->
{reply, ok, St#st{timeout=TimeOut}};
handle_call({prompt, [<<"reset">>]}, _From, St) ->
{reply, true, St#st{indexes=[]}};
handle_call({prompt, [<<"reset">>, _QueryConfig]}, _From, St) ->
{reply, true, St#st{indexes=[]}};
handle_call({prompt, [<<"add_fun">>, IndexInfo]}, _From, St) ->
Indexes = case validate_index_info(IndexInfo) of
true ->
St#st.indexes ++ [IndexInfo];
false ->
couch_log:error("No Valid Indexes For: ~p", [IndexInfo]),
St#st.indexes
end,
NewSt = St#st{indexes = Indexes},
{reply, true, NewSt};
handle_call({prompt, [<<"map_doc">>, Doc]}, _From, St) ->
{reply, map_doc(St, mango_json:to_binary(Doc)), St};
handle_call({prompt, [<<"reduce">>, _, _]}, _From, St) ->
{reply, null, St};
handle_call({prompt, [<<"rereduce">>, _, _]}, _From, St) ->
{reply, null, St};
handle_call({prompt, [<<"index_doc">>, Doc]}, _From, St) ->
Vals = case index_doc(St, mango_json:to_binary(Doc)) of
[] ->
[[]];
Else ->
Else
end,
{reply, Vals, St};
handle_call(Msg, _From, St) ->
{stop, {invalid_call, Msg}, {invalid_call, Msg}, St}.
handle_cast(garbage_collect, St) ->
erlang:garbage_collect(),
{noreply, St};
handle_cast(Msg, St) ->
{stop, {invalid_cast, Msg}, St}.
handle_info(Msg, St) ->
{stop, {invalid_info, Msg}, St}.
code_change(_OldVsn, St, _Extra) ->
{ok, St}.
map_doc(#st{indexes=Indexes}, Doc) ->
lists:map(fun(Idx) -> get_index_entries(Idx, Doc) end, Indexes).
index_doc(#st{indexes=Indexes}, Doc) ->
lists:map(fun(Idx) -> get_text_entries(Idx, Doc) end, Indexes).
get_index_entries({IdxProps}, Doc) ->
{Fields} = couch_util:get_value(<<"fields">>, IdxProps),
Values = lists:map(fun({Field, _Dir}) ->
case mango_doc:get_field(Doc, Field) of
not_found -> not_found;
bad_path -> not_found;
Else -> Else
end
end, Fields),
case lists:member(not_found, Values) of
true ->
[];
false ->
[[Values, null]]
end.
get_text_entries({IdxProps}, Doc) ->
Selector = case couch_util:get_value(<<"selector">>, IdxProps) of
[] -> {[]};
Else -> Else
end,
case should_index(Selector, Doc) of
true ->
get_text_entries0(IdxProps, Doc);
false ->
[]
end.
get_text_entries0(IdxProps, Doc) ->
DefaultEnabled = get_default_enabled(IdxProps),
IndexArrayLengths = get_index_array_lengths(IdxProps),
FieldsList = get_text_field_list(IdxProps),
TAcc = #tacc{
index_array_lengths = IndexArrayLengths,
fields = FieldsList
},
Fields0 = get_text_field_values(Doc, TAcc),
Fields = if not DefaultEnabled -> Fields0; true ->
add_default_text_field(Fields0)
end,
FieldNames = get_field_names(Fields, []),
Converted = convert_text_fields(Fields),
FieldNames ++ Converted.
get_text_field_values({Props}, TAcc) when is_list(Props) ->
get_text_field_values_obj(Props, TAcc, []);
get_text_field_values(Values, TAcc) when is_list(Values) ->
IndexArrayLengths = TAcc#tacc.index_array_lengths,
NewPath = ["[]" | TAcc#tacc.path],
NewTAcc = TAcc#tacc{path = NewPath},
case IndexArrayLengths of
true ->
% We bypass make_text_field and directly call make_text_field_name
% because the length field name is not part of the path.
LengthFieldName = make_text_field_name(NewTAcc#tacc.path, <<"length">>),
LengthField = [{LengthFieldName, <<"length">>, length(Values)}],
get_text_field_values_arr(Values, NewTAcc, LengthField);
_ ->
get_text_field_values_arr(Values, NewTAcc, [])
end;
get_text_field_values(Bin, TAcc) when is_binary(Bin) ->
make_text_field(TAcc, <<"string">>, Bin);
get_text_field_values(Num, TAcc) when is_number(Num) ->
make_text_field(TAcc, <<"number">>, Num);
get_text_field_values(Bool, TAcc) when is_boolean(Bool) ->
make_text_field(TAcc, <<"boolean">>, Bool);
get_text_field_values(null, TAcc) ->
make_text_field(TAcc, <<"null">>, true).
get_text_field_values_obj([], _, FAcc) ->
FAcc;
get_text_field_values_obj([{Key, Val} | Rest], TAcc, FAcc) ->
NewPath = [Key | TAcc#tacc.path],
NewTAcc = TAcc#tacc{path = NewPath},
Fields = get_text_field_values(Val, NewTAcc),
get_text_field_values_obj(Rest, TAcc, Fields ++ FAcc).
get_text_field_values_arr([], _, FAcc) ->
FAcc;
get_text_field_values_arr([Value | Rest], TAcc, FAcc) ->
Fields = get_text_field_values(Value, TAcc),
get_text_field_values_arr(Rest, TAcc, Fields ++ FAcc).
get_default_enabled(Props) ->
case couch_util:get_value(<<"default_field">>, Props, {[]}) of
Bool when is_boolean(Bool) ->
Bool;
{[]} ->
true;
{Opts}->
couch_util:get_value(<<"enabled">>, Opts, true)
end.
get_index_array_lengths(Props) ->
couch_util:get_value(<<"index_array_lengths">>, Props, true).
add_default_text_field(Fields) ->
DefaultFields = add_default_text_field(Fields, []),
DefaultFields ++ Fields.
add_default_text_field([], Acc) ->
Acc;
add_default_text_field([{_Name, <<"string">>, Value} | Rest], Acc) ->
NewAcc = [{<<"$default">>, <<"string">>, Value} | Acc],
add_default_text_field(Rest, NewAcc);
add_default_text_field([_ | Rest], Acc) ->
add_default_text_field(Rest, Acc).
%% index of all field names
get_field_names([], FAcc) ->
FAcc;
get_field_names([{Name, _Type, _Value} | Rest], FAcc) ->
case lists:member([<<"$fieldnames">>, Name, []], FAcc) of
true ->
get_field_names(Rest, FAcc);
false ->
get_field_names(Rest, [[<<"$fieldnames">>, Name, []] | FAcc])
end.
convert_text_fields([]) ->
[];
convert_text_fields([{Name, _Type, Value} | Rest]) ->
[[Name, Value, []] | convert_text_fields(Rest)].
should_index(Selector, Doc) ->
% We should do this
NormSelector = mango_selector:normalize(Selector),
Matches = mango_selector:match(NormSelector, Doc),
IsDesign = case mango_doc:get_field(Doc, <<"_id">>) of
<<"_design/", _/binary>> -> true;
_ -> false
end,
Matches and not IsDesign.
get_text_field_list(IdxProps) ->
case couch_util:get_value(<<"fields">>, IdxProps) of
Fields when is_list(Fields) ->
RawList = lists:flatmap(fun get_text_field_info/1, Fields),
[mango_util:lucene_escape_user(Field) || Field <- RawList];
_ ->
all_fields
end.
get_text_field_info({Props}) ->
Name = couch_util:get_value(<<"name">>, Props),
Type0 = couch_util:get_value(<<"type">>, Props),
if not is_binary(Name) -> []; true ->
Type = get_text_field_type(Type0),
[iolist_to_binary([Name, ":", Type])]
end.
get_text_field_type(<<"number">>) ->
<<"number">>;
get_text_field_type(<<"boolean">>) ->
<<"boolean">>;
get_text_field_type(_) ->
<<"string">>.
make_text_field(TAcc, Type, Value) ->
FieldName = make_text_field_name(TAcc#tacc.path, Type),
Fields = TAcc#tacc.fields,
case Fields == all_fields orelse lists:member(FieldName, Fields) of
true ->
[{FieldName, Type, Value}];
false ->
[]
end.
make_text_field_name([P | Rest], Type) ->
Parts = lists:reverse(Rest, [iolist_to_binary([P, ":", Type])]),
Escaped = [mango_util:lucene_escape_field(N) || N <- Parts],
iolist_to_binary(mango_util:join(".", Escaped)).
validate_index_info(IndexInfo) ->
IdxTypes = case module_loaded(dreyfus_index) of
true ->
[mango_idx_view, mango_idx_text];
false ->
[mango_idx_view]
end,
Results = lists:foldl(fun(IdxType, Results0) ->
try
IdxType:validate_index_def(IndexInfo),
[valid_index | Results0]
catch _:_ ->
[invalid_index | Results0]
end
end, [], IdxTypes),
lists:member(valid_index, Results).