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


##########
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)).
+
+-type selector_hint() :: {indexable_fields, [field()]} | {unindexable_fields, 
[field()]}.
+-type selector_hints() :: {[selector_hint()]}.
+
+-spec extract_selector_hints(selector()) -> selector_hints().
+extract_selector_hints(Selector) ->
+    DreyfusAvailable = dreyfus:available(),
+    NouveauEnabled = nouveau:enabled(),
+    AsIndex =
+        fun(Module) ->
+            case Module of
+                mango_cursor_view -> [{mango_idx_view, json}];
+                mango_cursor_text when DreyfusAvailable -> [{mango_idx_text, 
text}];
+                mango_cursor_nouveau when NouveauEnabled -> 
[{mango_idx_nouveau, nouveau}];
+                _ -> []
+            end
+        end,
+    Modules = lists:flatmap(AsIndex, ?CURSOR_MODULES),
+    Populate =
+        fun({Module, IndexType}) ->
+            AllFields = sets:from_list(mango_selector:fields(Selector)),
+            Normalize = fun(N) -> hd(string:split(N, ":")) end,
+            IndexableFields = sets:from_list(
+                lists:map(Normalize, Module:indexable_fields(Selector))
+            ),
+            UnindexableFields = sets:subtract(AllFields, IndexableFields),
+            {[
+                {type, IndexType},
+                {indexable_fields, sets:to_list(IndexableFields)},
+                {unindexable_fields, sets:to_list(UnindexableFields)}
+            ]}
+        end,
+    lists:map(Populate, Modules).
+
+-type explain_attribute() ::
+    {dbname, database()}
+    | {index, ejson()}
+    | {partitioned, boolean()}
+    | {selector, ejson()}
+    | {opts, ejson()}
+    | {limit, integer()}
+    | {skip, integer()}
+    | {fields, fields()}
+    | {index_candidates, [candidate_index()]}
+    | {selector_hints, selector_hints()}
+    | {atom(), any()}.
+-type explain_response() :: {[explain_attribute()]}.
+
+-spec explain(#cursor{}) -> explain_response().
 explain(#cursor{} = Cursor) ->
     #cursor{
-        index = Idx,
+        db = Db,
+        index = Index,
         selector = Selector,
         opts = Opts0,
         limit = Limit,
         skip = Skip,
         fields = Fields
     } = Cursor,
-    Mod = mango_idx:cursor_mod(Idx),
+    {ModExplain, DbName, JSON, Partitioned} =
+        case Index of
+            none ->
+                {
+                    [],
+                    couch_db:name(Db),
+                    null,
+                    null

Review Comment:
   The original intention about using `null` here is to reflect JSON-wise that 
this property is not applicable.  There is no index in this case so it is not 
either global or partitioned.  Similarly, it does not have a definition.  As 
far as I know, `jiffy` can only create `null` if the `null` atom is used.



-- 
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