icydb-core 0.69.0

IcyDB — A type-safe, embedded ORM and schema system for the Internet Computer
Documentation
//! Module: query::plan::access_choice
//! Responsibility: planner-owned access-choice explain metadata projection.
//! Does not own: access-path execution, route decisions, or explain rendering.
//! Boundary: derives deterministic candidate/rejection metadata from planning contracts.

mod evaluator;
mod model;

///
/// TESTS
///

#[cfg(test)]
mod tests;

use crate::{
    db::{
        query::{
            explain::ExplainAccessPath,
            plan::{
                AccessPlannedQuery,
                access_choice::{
                    evaluator::{
                        chosen_access_shape_projection, chosen_selection_reason,
                        evaluate_index_candidate, ranked_rejection_reason, sorted_indexes,
                    },
                    model::{AccessChoiceFamily, AccessChoiceSelectedReason},
                },
            },
        },
        schema::SchemaInfo,
    },
    model::entity::EntityModel,
};

pub(in crate::db) use self::model::AccessChoiceExplainSnapshot;

///
/// project_access_choice_explain_snapshot
///
/// Project planner-owned access-choice candidate metadata for EXPLAIN.
/// This keeps alternative/rejection reporting aligned to planner predicates
/// instead of model-only index hints.
///

#[must_use]
pub(in crate::db) fn project_access_choice_explain_snapshot(
    model: &EntityModel,
    plan: &AccessPlannedQuery,
    access: &ExplainAccessPath,
) -> AccessChoiceExplainSnapshot {
    // Phase 1: classify chosen access family and seed non-index fallbacks.
    let (family, chosen_index_name, chosen_score_hint) = chosen_access_shape_projection(access);
    if matches!(family, AccessChoiceFamily::NonIndex) {
        return AccessChoiceExplainSnapshot {
            chosen_reason: AccessChoiceSelectedReason::NonIndexAccess,
            alternatives: Vec::new(),
            rejected: Vec::new(),
        };
    }

    let Some(chosen_index_name) = chosen_index_name else {
        return AccessChoiceExplainSnapshot {
            chosen_reason: AccessChoiceSelectedReason::SelectedIndexUnavailable,
            alternatives: Vec::new(),
            rejected: Vec::new(),
        };
    };

    let Ok(schema_info) = SchemaInfo::from_entity_model(model) else {
        return AccessChoiceExplainSnapshot {
            chosen_reason: AccessChoiceSelectedReason::SchemaUnavailable,
            alternatives: Vec::new(),
            rejected: Vec::new(),
        };
    };

    let predicate = plan.scalar_plan().predicate.as_ref();
    let mut chosen_score = chosen_score_hint;
    let mut alternatives = Vec::new();
    let mut rejected = Vec::new();
    let mut eligible_other_scores = Vec::new();

    // Phase 2: walk deterministic model order once so chosen-score recovery
    // and alternative/rejection projection stay under one evaluation owner.
    for index in sorted_indexes(model) {
        let index_name = index.name();
        match evaluate_index_candidate(family, index, &schema_info, predicate) {
            self::model::CandidateEvaluation::Eligible(score)
                if index_name == chosen_index_name =>
            {
                chosen_score = score;
            }
            self::model::CandidateEvaluation::Eligible(score) => {
                alternatives.push(index_name);
                eligible_other_scores.push(score);
                rejected.push(
                    ranked_rejection_reason(family, score, chosen_score)
                        .render_for_index(index_name),
                );
            }
            self::model::CandidateEvaluation::Rejected(reason) => {
                rejected.push(reason.render_for_index(index_name));
            }
        }
    }

    // Phase 3: derive deterministic winner/rejection reason codes from the
    // one-pass candidate evaluation results above.
    AccessChoiceExplainSnapshot {
        chosen_reason: chosen_selection_reason(family, chosen_score, &eligible_other_scores),
        alternatives,
        rejected,
    }
}