diff options
Diffstat (limited to 'src/mango/src/mango_idx_view.erl')
-rw-r--r-- | src/mango/src/mango_idx_view.erl | 189 |
1 files changed, 85 insertions, 104 deletions
diff --git a/src/mango/src/mango_idx_view.erl b/src/mango/src/mango_idx_view.erl index 37911498c..ff8f6c6bb 100644 --- a/src/mango/src/mango_idx_view.erl +++ b/src/mango/src/mango_idx_view.erl @@ -12,7 +12,6 @@ -module(mango_idx_view). - -export([ validate_new/2, validate_index_def/1, @@ -30,75 +29,75 @@ field_ranges/2 ]). - -include_lib("couch/include/couch_db.hrl"). -include("mango.hrl"). -include("mango_idx.hrl"). -include("mango_idx_view.hrl"). - -validate_new(#idx{}=Idx, _Db) -> +validate_new(#idx{} = Idx, _Db) -> {ok, Def} = do_validate(Idx#idx.def), - {ok, Idx#idx{def=Def}}. - + {ok, Idx#idx{def = Def}}. validate_index_def(Def) -> def_to_json(Def). - -add(#doc{body={Props0}}=DDoc, Idx) -> - Views1 = case proplists:get_value(<<"views">>, Props0) of - {Views0} -> Views0; - _ -> [] - end, +add(#doc{body = {Props0}} = DDoc, Idx) -> + Views1 = + case proplists:get_value(<<"views">>, Props0) of + {Views0} -> Views0; + _ -> [] + end, NewView = make_view(Idx), Views2 = lists:keystore(element(1, NewView), 1, Views1, NewView), Props1 = lists:keystore(<<"views">>, 1, Props0, {<<"views">>, {Views2}}), - {ok, DDoc#doc{body={Props1}}}. - - -remove(#doc{body={Props0}}=DDoc, Idx) -> - Views1 = case proplists:get_value(<<"views">>, Props0) of - {Views0} -> - Views0; - _ -> - ?MANGO_ERROR({index_not_found, Idx#idx.name}) - end, + {ok, DDoc#doc{body = {Props1}}}. + +remove(#doc{body = {Props0}} = DDoc, Idx) -> + Views1 = + case proplists:get_value(<<"views">>, Props0) of + {Views0} -> + Views0; + _ -> + ?MANGO_ERROR({index_not_found, Idx#idx.name}) + end, Views2 = lists:keydelete(Idx#idx.name, 1, Views1), - if Views2 /= Views1 -> ok; true -> - ?MANGO_ERROR({index_not_found, Idx#idx.name}) - end, - Props1 = case Views2 of - [] -> - lists:keydelete(<<"views">>, 1, Props0); - _ -> - lists:keystore(<<"views">>, 1, Props0, {<<"views">>, {Views2}}) + if + Views2 /= Views1 -> ok; + true -> ?MANGO_ERROR({index_not_found, Idx#idx.name}) end, - {ok, DDoc#doc{body={Props1}}}. - + Props1 = + case Views2 of + [] -> + lists:keydelete(<<"views">>, 1, Props0); + _ -> + lists:keystore(<<"views">>, 1, Props0, {<<"views">>, {Views2}}) + end, + {ok, DDoc#doc{body = {Props1}}}. from_ddoc({Props}) -> case lists:keyfind(<<"views">>, 1, Props) of {<<"views">>, {Views}} when is_list(Views) -> - lists:flatmap(fun({Name, {VProps}}) -> - case validate_ddoc(VProps) of - invalid_view -> - []; - {Def, Opts} -> - I = #idx{ - type = <<"json">>, - name = Name, - def = Def, - opts = Opts - }, - [I] - end - end, Views); + lists:flatmap( + fun({Name, {VProps}}) -> + case validate_ddoc(VProps) of + invalid_view -> + []; + {Def, Opts} -> + I = #idx{ + type = <<"json">>, + name = Name, + def = Def, + opts = Opts + }, + [I] + end + end, + Views + ); _ -> [] end. - to_json(Idx) -> {[ {ddoc, Idx#idx.ddoc}, @@ -108,20 +107,18 @@ to_json(Idx) -> {def, {def_to_json(Idx#idx.def)}} ]}. - columns(Idx) -> {Props} = Idx#idx.def, {<<"fields">>, {Fields}} = lists:keyfind(<<"fields">>, 1, Props), [Key || {Key, _} <- Fields]. - is_usable(Idx, Selector, SortFields) -> % This index is usable if all of the columns are % restricted by the selector such that they are required to exist % and the selector is not a text search (so requires a text index) RequiredFields = columns(Idx), - % sort fields are required to exist in the results so + % sort fields are required to exist in the results so % we don't need to check the selector for these RequiredFields1 = ordsets:subtract(lists:usort(RequiredFields), lists:usort(SortFields)), @@ -129,31 +126,35 @@ is_usable(Idx, Selector, SortFields) -> % we don't need to check the selector for these either RequiredFields2 = ordsets:subtract( RequiredFields1, - [<<"_id">>, <<"_rev">>]), - - mango_selector:has_required_fields(Selector, RequiredFields2) - andalso not is_text_search(Selector) - andalso can_use_sort(RequiredFields, SortFields, Selector). + [<<"_id">>, <<"_rev">>] + ), + mango_selector:has_required_fields(Selector, RequiredFields2) andalso + not is_text_search(Selector) andalso + can_use_sort(RequiredFields, SortFields, Selector). is_text_search({[]}) -> false; is_text_search({[{<<"$default">>, _}]}) -> true; is_text_search({[{_Field, Cond}]}) when is_list(Cond) -> - lists:foldl(fun(C, Exists) -> - Exists orelse is_text_search(C) - end, false, Cond); + lists:foldl( + fun(C, Exists) -> + Exists orelse is_text_search(C) + end, + false, + Cond + ); is_text_search({[{_Field, Cond}]}) when is_tuple(Cond) -> is_text_search(Cond); is_text_search({[{_Field, _Cond}]}) -> false; %% we reached values, which should always be false -is_text_search(Val) - when is_number(Val); is_boolean(Val); is_binary(Val)-> +is_text_search(Val) when + is_number(Val); is_boolean(Val); is_binary(Val) +-> false. - start_key([]) -> []; start_key([{'$gt', Key, _, _} | Rest]) -> @@ -170,7 +171,6 @@ start_key([{'$eq', Key, '$eq', Key} | Rest]) -> false = mango_json:special(Key), [Key | start_key(Rest)]. - end_key([]) -> [?MAX_JSON_OBJ]; end_key([{_, _, '$lt', Key} | Rest]) -> @@ -187,14 +187,12 @@ end_key([{'$eq', Key, '$eq', Key} | Rest]) -> false = mango_json:special(Key), [Key | end_key(Rest)]. - do_validate({Props}) -> {ok, Opts} = mango_opts:validate(Props, opts()), {ok, {Opts}}; do_validate(Else) -> ?MANGO_ERROR({invalid_index_json, Else}). - def_to_json({Props}) -> def_to_json(Props); def_to_json([]) -> @@ -210,7 +208,6 @@ def_to_json([{<<"partial_filter_selector">>, {[]}} | Rest]) -> def_to_json([{Key, Value} | Rest]) -> [{Key, Value} | def_to_json(Rest)]. - opts() -> [ {<<"fields">>, [ @@ -225,16 +222,15 @@ opts() -> ]} ]. - make_view(Idx) -> - View = {[ - {<<"map">>, Idx#idx.def}, - {<<"reduce">>, <<"_count">>}, - {<<"options">>, {Idx#idx.opts}} - ]}, + View = + {[ + {<<"map">>, Idx#idx.def}, + {<<"reduce">>, <<"_count">>}, + {<<"options">>, {Idx#idx.opts}} + ]}, {Idx#idx.name, View}. - validate_ddoc(VProps) -> try Def = proplists:get_value(<<"map">>, VProps), @@ -242,13 +238,15 @@ validate_ddoc(VProps) -> {Opts0} = proplists:get_value(<<"options">>, VProps), Opts = lists:keydelete(<<"sort">>, 1, Opts0), {Def, Opts} - catch Error:Reason -> - couch_log:error("Invalid Index Def ~p. Error: ~p, Reason: ~p", - [VProps, Error, Reason]), - invalid_view + catch + Error:Reason -> + couch_log:error( + "Invalid Index Def ~p. Error: ~p, Reason: ~p", + [VProps, Error, Reason] + ), + invalid_view end. - % This function returns a list of indexes that % can be used to restrict this query. This works by % searching the selector looking for field names that @@ -268,11 +266,9 @@ validate_ddoc(VProps) -> % We can see through '$and' trivially indexable_fields({[{<<"$and">>, Args}]}) -> lists:usort(lists:flatten([indexable_fields(A) || A <- Args])); - % So far we can't see through any other operator indexable_fields({[{<<"$", _/binary>>, _}]}) -> []; - % If we have a field with a terminator that is locatable % using an index then the field is a possible index indexable_fields({[{Field, Cond}]}) -> @@ -282,12 +278,10 @@ indexable_fields({[{Field, Cond}]}) -> false -> [] end; - % An empty selector indexable_fields({[]}) -> []. - % Check if a condition is indexable. The logical % comparisons are mostly straight forward. We % currently don't understand '$in' which is @@ -304,24 +298,20 @@ indexable({[{<<"$gt">>, _}]}) -> true; indexable({[{<<"$gte">>, _}]}) -> true; - % All other operators are currently not indexable. % This is also a subtle assertion that we don't % call indexable/1 on a field name. indexable({[{<<"$", _/binary>>, _}]}) -> false. - % For each field, return {Field, Range} field_ranges(Selector) -> Fields = indexable_fields(Selector), field_ranges(Selector, Fields). - field_ranges(Selector, Fields) -> field_ranges(Selector, Fields, []). - field_ranges(_Selector, [], Acc) -> lists:reverse(Acc); field_ranges(Selector, [Field | Rest], Acc) -> @@ -332,7 +322,6 @@ field_ranges(Selector, [Field | Rest], Acc) -> field_ranges(Selector, Rest, [{Field, Range} | Acc]) end. - % Find the complete range for a given index in this % selector. This works by AND'ing logical comparisons % together so that we can define the start and end @@ -343,32 +332,31 @@ field_ranges(Selector, [Field | Rest], Acc) -> range(Selector, Index) -> range(Selector, Index, '$gt', mango_json:min(), '$lt', mango_json:max()). - % Adjust Low and High based on values found for the % givend Index in Selector. range({[{<<"$and">>, Args}]}, Index, LCmp, Low, HCmp, High) -> - lists:foldl(fun - (Arg, {LC, L, HC, H}) -> - range(Arg, Index, LC, L, HC, H); - (_Arg, empty) -> - empty - end, {LCmp, Low, HCmp, High}, Args); - + lists:foldl( + fun + (Arg, {LC, L, HC, H}) -> + range(Arg, Index, LC, L, HC, H); + (_Arg, empty) -> + empty + end, + {LCmp, Low, HCmp, High}, + Args + ); % We can currently only traverse '$and' operators range({[{<<"$", _/binary>>}]}, _Index, LCmp, Low, HCmp, High) -> {LCmp, Low, HCmp, High}; - % If the field name matches the index see if we can narrow % the acceptable range. range({[{Index, Cond}]}, Index, LCmp, Low, HCmp, High) -> range(Cond, LCmp, Low, HCmp, High); - % Else we have a field unrelated to this index so just % return the current values. range(_, _, LCmp, Low, HCmp, High) -> {LCmp, Low, HCmp, High}. - % The comments below are a bit cryptic at first but they show % where the Arg cand land in the current range. % @@ -425,7 +413,6 @@ range({[{<<"$lt">>, Arg}]}, LCmp, Low, HCmp, High) -> max -> {LCmp, Low, HCmp, High} end; - range({[{<<"$lte">>, Arg}]}, LCmp, Low, HCmp, High) -> case range_pos(Low, Arg, High) of min -> @@ -441,7 +428,6 @@ range({[{<<"$lte">>, Arg}]}, LCmp, Low, HCmp, High) -> max -> {LCmp, Low, HCmp, High} end; - range({[{<<"$eq">>, Arg}]}, LCmp, Low, HCmp, High) -> case range_pos(Low, Arg, High) of min -> @@ -459,7 +445,6 @@ range({[{<<"$eq">>, Arg}]}, LCmp, Low, HCmp, High) -> max -> empty end; - range({[{<<"$gte">>, Arg}]}, LCmp, Low, HCmp, High) -> case range_pos(Low, Arg, High) of min -> @@ -475,7 +460,6 @@ range({[{<<"$gte">>, Arg}]}, LCmp, Low, HCmp, High) -> max -> empty end; - range({[{<<"$gt">>, Arg}]}, LCmp, Low, HCmp, High) -> case range_pos(Low, Arg, High) of min -> @@ -489,14 +473,12 @@ range({[{<<"$gt">>, Arg}]}, LCmp, Low, HCmp, High) -> max -> empty end; - % There's some other un-indexable restriction on the index % that will be applied as a post-filter. Ignore it and % carry on our merry way. range({[{<<"$", _/binary>>, _}]}, LCmp, Low, HCmp, High) -> {LCmp, Low, HCmp, High}. - % Returns the value min | low | mid | high | max depending % on how Arg compares to Low and High. range_pos(Low, Arg, High) -> @@ -514,7 +496,6 @@ range_pos(Low, Arg, High) -> end end. - % Can_use_sort works as follows: % % * no sort fields then we can use this |