mod compare;
mod index_select;
mod order_select;
mod predicate;
mod prefix;
mod range;
mod ranking;
use crate::{
db::{
access::{AccessPlan, SemanticIndexAccessContract, normalize_access_plan_value},
predicate::Predicate,
query::construction::ConstructionBudget,
query::plan::{OrderSpec, PlanError, PlannedNonIndexAccessReason},
schema::SchemaInfo,
},
error::InternalError,
value::Value,
};
pub(in crate::db::query::plan) use crate::db::access::MAX_INDEX_BRANCH_SET_VALUES;
#[cfg(test)]
pub(in crate::db::query::plan) use compare::prefix_tests::schema as prefix_test_schema;
pub(in crate::db::query::plan) use index_select::index_stream_is_complete_for_query;
pub(in crate::db::query) use index_select::{
eligible_sorted_index_contracts, index_field_literal_matcher, index_literal_matches_schema,
};
pub(in crate::db) use index_select::{
residual_query_predicate_after_access_path_bounds,
residual_query_predicate_after_filtered_access_contract,
};
pub(in crate::db::query) use prefix::count_cardinality_index_branch_set_from_and;
pub(in crate::db::query::plan) use ranking::{
AccessCandidateScore, AndFamilyCandidateScore, AndFamilyPriorityClass,
access_candidate_score_from_index_contract, access_candidate_score_outranks,
and_family_candidate_score_outranks, range_bound_count,
selected_index_contract_satisfies_secondary_order,
};
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db::query) struct PlannedAccessSelection {
access: AccessPlan<Value>,
planned_non_index_reason: Option<PlannedNonIndexAccessReason>,
}
impl PlannedAccessSelection {
#[must_use]
pub(in crate::db::query) const fn new(
access: AccessPlan<Value>,
planned_non_index_reason: Option<PlannedNonIndexAccessReason>,
) -> Self {
Self {
access,
planned_non_index_reason,
}
}
#[must_use]
pub(in crate::db::query) fn into_access_and_non_index_reason(
self,
) -> (AccessPlan<Value>, Option<PlannedNonIndexAccessReason>) {
(self.access, self.planned_non_index_reason)
}
#[must_use]
pub(in crate::db::query::plan) fn into_access(self) -> AccessPlan<Value> {
self.access
}
}
#[derive(Debug)]
pub enum PlannerError {
Plan(Box<PlanError>),
Internal(Box<InternalError>),
}
impl From<PlanError> for PlannerError {
fn from(err: PlanError) -> Self {
Self::Plan(Box::new(err))
}
}
impl From<InternalError> for PlannerError {
fn from(err: InternalError) -> Self {
Self::Internal(Box::new(err))
}
}
pub(in crate::db::query) fn plan_access_selection_with_order_and_semantic_indexes(
semantic_candidate_indexes: &[SemanticIndexAccessContract],
schema: &SchemaInfo,
predicate: Option<&Predicate>,
order: Option<&OrderSpec>,
grouped: bool,
budget: &dyn ConstructionBudget,
) -> Result<PlannedAccessSelection, PlannerError> {
plan_access_selection_with_order(
semantic_candidate_indexes,
schema,
predicate,
order,
grouped,
budget,
)
}
fn plan_access_selection_with_order(
visible_indexes: &[SemanticIndexAccessContract],
schema: &SchemaInfo,
predicate: Option<&Predicate>,
order: Option<&OrderSpec>,
grouped: bool,
budget: &dyn ConstructionBudget,
) -> Result<PlannedAccessSelection, PlannerError> {
let Some(predicate) = predicate else {
let true_predicate = Predicate::True;
let eligible_indexes =
eligible_sorted_index_contracts(visible_indexes, schema, &true_predicate, budget)?;
return Ok(order_fallback_selection(
eligible_indexes.as_slice(),
schema,
&true_predicate,
order,
grouped,
budget,
)?);
};
let eligible_indexes =
eligible_sorted_index_contracts(visible_indexes, schema, predicate, budget)?;
let selection = predicate::plan_predicate(
eligible_indexes.as_slice(),
schema,
predicate,
order,
grouped,
budget,
)?;
let (access, planned_non_index_reason) = selection.into_access_and_non_index_reason();
let plan = normalize_access_plan_value(access);
if !plan.is_single_full_scan() {
return Ok(PlannedAccessSelection::new(plan, planned_non_index_reason));
}
Ok(order_select::index_range_from_order_with_semantic_indexes(
eligible_indexes.as_slice(),
schema,
predicate,
order,
grouped,
budget,
)?
.map_or_else(
|| {
PlannedAccessSelection::new(
plan,
Some(PlannedNonIndexAccessReason::PlannerFullScanFallback),
)
},
|access| PlannedAccessSelection::new(access, None),
))
}
fn order_fallback_selection(
eligible_indexes: &[SemanticIndexAccessContract],
schema: &SchemaInfo,
query_predicate: &Predicate,
order: Option<&OrderSpec>,
grouped: bool,
budget: &dyn ConstructionBudget,
) -> Result<PlannedAccessSelection, InternalError> {
Ok(order_select::index_range_from_order_with_semantic_indexes(
eligible_indexes,
schema,
query_predicate,
order,
grouped,
budget,
)?
.map_or_else(
|| {
PlannedAccessSelection::new(
AccessPlan::full_scan(),
Some(PlannedNonIndexAccessReason::PlannerFullScanFallback),
)
},
|access| PlannedAccessSelection::new(access, None),
))
}