mod compare;
mod index_select;
mod order_select;
mod predicate;
mod prefix;
mod range;
mod ranking;
#[cfg(test)]
mod tests;
use crate::{
db::{
access::{AccessPlan, normalize_access_plan_value},
predicate::Predicate,
query::plan::{OrderSpec, PlanError},
schema::SchemaInfo,
},
error::InternalError,
model::{entity::EntityModel, index::IndexModel},
value::Value,
};
use thiserror::Error as ThisError;
pub(in crate::db::query::plan) use index_select::{
index_literal_matches_schema, index_predicate_guarantees_compare, sorted_indexes,
sorted_model_indexes,
};
pub(in crate::db) use index_select::{
residual_query_predicate_after_access_path_bounds,
residual_query_predicate_after_filtered_access,
};
pub(in crate::db::query::plan) use ranking::{
AccessCandidateScore, access_candidate_score_outranks, candidate_satisfies_secondary_order,
};
#[derive(Debug, ThisError)]
pub enum PlannerError {
#[error("{0}")]
Plan(Box<PlanError>),
#[error("{0}")]
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))
}
}
#[cfg(test)]
pub(crate) fn plan_access(
model: &EntityModel,
visible_indexes: &[&'static IndexModel],
schema: &SchemaInfo,
predicate: Option<&Predicate>,
) -> Result<AccessPlan<Value>, PlannerError> {
plan_access_with_order(model, visible_indexes, schema, predicate, None)
}
pub(crate) fn plan_access_with_order(
model: &EntityModel,
visible_indexes: &[&'static IndexModel],
schema: &SchemaInfo,
predicate: Option<&Predicate>,
order: Option<&OrderSpec>,
) -> Result<AccessPlan<Value>, PlannerError> {
let Some(predicate) = predicate else {
let true_predicate = Predicate::True;
let eligible_indexes = sorted_indexes(visible_indexes, &true_predicate);
return Ok(order_fallback_plan(
model,
eligible_indexes.as_slice(),
order,
));
};
let eligible_indexes = sorted_indexes(visible_indexes, predicate);
let plan = normalize_access_plan_value(predicate::plan_predicate(
model,
eligible_indexes.as_slice(),
schema,
predicate,
order,
)?);
if !plan.is_single_full_scan() {
return Ok(plan);
}
Ok(
order_select::index_range_from_order(model, eligible_indexes.as_slice(), order)
.unwrap_or(plan),
)
}
fn order_fallback_plan(
model: &EntityModel,
eligible_indexes: &[&'static IndexModel],
order: Option<&OrderSpec>,
) -> AccessPlan<Value> {
order_select::index_range_from_order(model, eligible_indexes, order)
.unwrap_or_else(AccessPlan::full_scan)
}