pgj commented on code in PR #4662:
URL: https://github.com/apache/couchdb/pull/4662#discussion_r1306558990


##########
src/mango/src/mango_cursor.erl:
##########
@@ -44,27 +44,286 @@
 
 -define(SUPERVISOR, mango_cursor_sup).
 
-create(Db, Selector0, Opts) ->
+-spec create(Db, Selector, Options, Kind) -> {ok, #cursor{}} when
+    Db :: database(),
+    Selector :: selector(),
+    Options :: cursor_options(),
+    Kind :: cursor_kind().
+create(Db, Selector0, Opts, Kind) ->
     Selector = mango_selector:normalize(Selector0),
-    UsableIndexes = mango_idx:get_usable_indexes(Db, Selector, Opts),
-    case mango_cursor:maybe_filter_indexes_by_ddoc(UsableIndexes, Opts) of
+    {UsableIndexes, Trace} = mango_idx:get_usable_indexes(Db, Selector, Opts, 
Kind),
+    case maybe_filter_indexes_by_ddoc(UsableIndexes, Opts) of
         [] ->
             % use_index doesn't match a valid index - fall back to a valid one
-            create_cursor(Db, UsableIndexes, Selector, Opts);
+            create_cursor(Db, {UsableIndexes, Trace}, Selector, Opts);
         UserSpecifiedIndex ->
-            create_cursor(Db, UserSpecifiedIndex, Selector, Opts)
+            create_cursor(Db, {UserSpecifiedIndex, Trace}, Selector, Opts)
     end.
 
+-spec enhance_candidates(Candidates, [{#idx{}, properties()}]) -> Candidates 
when
+    Candidates :: #{#idx{} => properties()}.
+enhance_candidates(Entries, Inputs) ->
+    Combiner =
+        fun(Key, Value1, Value2) ->
+            case Key of
+                usable -> Value1 andalso Value2;
+                reason -> lists:append(Value1, Value2);
+                ranking -> Value1 + Value2
+            end
+        end,
+    lists:foldr(
+        fun({Index, Delta}, Map) ->
+            Updater = fun(Value) -> maps:merge_with(Combiner, Value, Delta) 
end,
+            maps:update_with(Index, Updater, Map)
+        end,
+        Entries,
+        Inputs
+    ).
+
+-type ranking() :: pos_integer().
+-type properties() ::
+    #{
+        usable := boolean(),
+        ranking := ranking(),
+        reason := [reason()]
+    }.
+
+-spec tag_elems(properties(), sets:set(#idx{})) -> [{#idx{}, properties()}].
+tag_elems(Properties, Set) ->
+    sets:fold(fun(Index, Acc) -> [{Index, Properties} | Acc] end, [], Set).
+
+-type reason_description() ::
+    {name, reason()}.
+-type analysis_attribute() ::
+    {usable, boolean()}
+    | {reasons, [reason_description()]}
+    | {ranking, ranking()}
+    | {covering, boolean()}.
+-type analysis() ::
+    {[analysis_attribute()]}.
+-type candidate_index_attribute() ::
+    {index, #idx{}} | {analysis, analysis()}.
+-type candidate_index() ::
+    {[candidate_index_attribute()]}.
+
+-spec extract_candidate_indexes(#cursor{}) -> [candidate_index()].
+extract_candidate_indexes(Cursor) ->
+    #cursor{trace = Trace, index = Winner, fields = Fields} = Cursor,
+    #{
+        all_indexes := AllIndexes,
+        global_indexes := GlobalIndexes,
+        partition_indexes := PartitionIndexes,
+        usable_indexes := UsableIndexes,
+        usability_map := UsabilityMap,
+        filtered_indexes := FilteredIndexes,
+        indexes_of_type := IndexesOfType
+    } = Trace,
+    % specific to view indexes
+    SortedIndexRanges = maps:get(sorted_index_ranges, Trace, []),
+    % simple difference calculations to determine the results in each stage,
+    % without looking at the implementation
+    PartialIndexes = sets:subtract(AllIndexes, GlobalIndexes),
+    OutOfScopeIndexes = sets:subtract(GlobalIndexes, PartitionIndexes),
+    NotUsableIndexes = sets:subtract(PartitionIndexes, UsableIndexes),
+    ExcludedIndexes = sets:subtract(UsableIndexes, FilteredIndexes),
+    UnfavoredIndexes = sets:subtract(FilteredIndexes, IndexesOfType),
+    % determine rankings
+    UnfavoredIndexesRank = max(1, length(SortedIndexRanges)),
+    {_, [PartialIndexesRank, OutOfScopeIndexesRank, NotUsableIndexesRank, 
ExcludedIndexesRank]} =
+        lists:foldl(
+            fun(Indexes, {Rank0, Acc}) ->
+                Rank =
+                    case (sets:size(Indexes) > 0) of
+                        true -> Rank0 + 1;
+                        false -> Rank0
+                    end,
+                {Rank, [Rank | Acc]}
+            end,
+            {UnfavoredIndexesRank, []},
+            [UnfavoredIndexes, ExcludedIndexes, NotUsableIndexes, 
OutOfScopeIndexes]
+        ),
+    % start building the list of candidates
+    AddCandidate =
+        fun(Index, Map) ->
+            maps:put(Index, #{}, Map)
+        end,
+    Candidates0 = sets:fold(AddCandidate, #{}, AllIndexes),
+    PartialIndexesTags = tag_elems(
+        #{usable => false, reason => [is_partial], ranking => 
PartialIndexesRank},
+        PartialIndexes
+    ),
+    Candidates1 = enhance_candidates(Candidates0, PartialIndexesTags),
+    OutOfScopeIndexesTags = tag_elems(
+        #{usable => false, reason => [scope_mismatch], ranking => 
OutOfScopeIndexesRank},
+        OutOfScopeIndexes
+    ),
+    Candidates2 = enhance_candidates(Candidates1, OutOfScopeIndexesTags),
+    NotUsableIndexesTags = tag_elems(
+        % the reason is going to be filled out by the mango_idx modules
+        #{usable => false, ranking => NotUsableIndexesRank},
+        NotUsableIndexes
+    ),
+    Candidates3 = enhance_candidates(Candidates2, NotUsableIndexesTags),
+    ExcludedIndexesTags = tag_elems(
+        #{usable => true, reason => [excluded_by_user], ranking => 
ExcludedIndexesRank},
+        ExcludedIndexes
+    ),
+    Candidates4 = enhance_candidates(Candidates3, ExcludedIndexesTags),
+    UnfavoredIndexesTags = tag_elems(
+        #{usable => true, reason => [unfavored_type], ranking => 
UnfavoredIndexesRank},
+        UnfavoredIndexes
+    ),
+    Candidates5 = enhance_candidates(Candidates4, UnfavoredIndexesTags),
+    NotChosenIndexesTags = tag_elems(
+        #{usable => true},
+        IndexesOfType
+    ),
+    Candidates6 = enhance_candidates(Candidates5, NotChosenIndexesTags),
+    NotUsableDetails =
+        lists:flatmap(
+            fun({Index, {Usable, Details}}) ->
+                case Usable of
+                    true -> [];
+                    false -> [{Index, Details}]
+                end
+            end,
+            UsabilityMap
+        ),
+    Candidates7 = enhance_candidates(Candidates6, NotUsableDetails),
+    WinnerColumnWidth =
+        case Winner of
+            none ->
+                0;
+            W ->
+                case mango_idx:columns(W) of
+                    X when is_list(X) -> length(X);
+                    all_fields -> 256
+                end
+        end,
+    {_, NotChosenDetails} =
+        lists:foldl(
+            fun({Index, _Prefix, Distance}, {N, Acc}) ->
+                ColumnWidth = length(mango_idx:columns(Index)),
+                Reason =
+                    case {} of
+                        _ when Distance > 0 -> [less_overlap];
+                        _ when ColumnWidth > WinnerColumnWidth -> 
[too_many_fields];
+                        _ -> [alphabetically_comes_after]
+                    end,
+                Tags = #{
+                    reason => Reason,
+                    ranking => N
+                },
+                {N + 1, [{Index, Tags} | Acc]}
+            end,
+            {0, []},
+            SortedIndexRanges
+        ),
+    Candidates8 = enhance_candidates(Candidates7, NotChosenDetails),
+    Candidates = maps:remove(Winner, Candidates8),
+    % produce the final list
+    ToList =
+        fun(Index, Tags, Acc) ->
+            #idx{type = IndexType} = Index,
+            #{usable := Usable, reason := Reason, ranking := Ranking} = Tags,
+            Covering =
+                case IndexType of
+                    <<"json">> -> mango_idx_view:covers(Index, Fields);
+                    _ -> null
+                end,
+            Reasons = [{[{name, hd(Reason)}]}],
+            Analysis =
+                {[
+                    {usable, Usable},
+                    {reasons, Reasons},
+                    {ranking, Ranking},
+                    {covering, Covering}
+                ]},
+            Entry =
+                {[
+                    {index, mango_idx:to_json(Index)},
+                    {analysis, Analysis}
+                ]},
+            [Entry | Acc]
+        end,
+    lists:reverse(maps:fold(ToList, [], Candidates)).

Review Comment:
   Ahh, nice catch!  The use of `reverse` is just an old habit from Haskell, 
where `fold` is usually a fold right and the result should be reversed to the 
get back the original order.  But later (after writing this piece of code) I 
learned that folds are taken lighter (formally) in Erlang and they are usually 
fold lefts.
   
   Independently of that, your comment about the original order is valid -- for 
what it is worth, the actual ordering does not matter.  The value in 
`analysis.ranking`, optionally combined with the reasons, determines the order 
and it is up to the client to sort (or not) the elements of the array before 
processing them.  I am not sure if the API shall offer any guarantees about 
that.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to