use crate::{
db::{
predicate::{MissingRowPolicy, Predicate},
query::plan::{
DeleteLimitSpec, GroupHavingSpec, GroupPlan, GroupSpec, LogicalPlan, OrderDirection,
OrderSpec, PageSpec, QueryMode, ScalarPlan,
},
},
model::entity::EntityModel,
};
#[derive(Debug)]
pub(in crate::db::query) struct LogicalPlanningInputs {
mode: QueryMode,
order: Option<OrderSpec>,
distinct: bool,
group: Option<GroupSpec>,
having: Option<GroupHavingSpec>,
}
impl LogicalPlanningInputs {
#[must_use]
pub(in crate::db::query) const fn new(
mode: QueryMode,
order: Option<OrderSpec>,
distinct: bool,
group: Option<GroupSpec>,
having: Option<GroupHavingSpec>,
) -> Self {
Self {
mode,
order,
distinct,
group,
having,
}
}
}
#[derive(Clone, Debug)]
pub(in crate::db::query) struct LogicalQuery {
pub(in crate::db::query) mode: QueryMode,
pub(in crate::db::query) normalized_predicate: Option<Predicate>,
pub(in crate::db::query) order: Option<OrderSpec>,
pub(in crate::db::query) distinct: bool,
pub(in crate::db::query) group: Option<GroupSpec>,
pub(in crate::db::query) having: Option<GroupHavingSpec>,
pub(in crate::db::query) consistency: MissingRowPolicy,
}
#[must_use]
pub(in crate::db::query) fn logical_query_from_logical_inputs(
inputs: LogicalPlanningInputs,
normalized_predicate: Option<Predicate>,
consistency: MissingRowPolicy,
) -> LogicalQuery {
let LogicalPlanningInputs {
mode,
order,
distinct,
group,
having,
} = inputs;
LogicalQuery {
mode,
normalized_predicate,
order,
distinct,
group,
having,
consistency,
}
}
#[must_use]
pub(in crate::db::query) fn build_logical_plan(
model: &EntityModel,
query: LogicalQuery,
) -> LogicalPlan {
let LogicalQuery {
mode,
normalized_predicate,
order,
distinct,
group,
having,
consistency,
} = query;
let scalar = ScalarPlan {
mode,
predicate: normalized_predicate,
order: canonicalize_order_spec(model, order),
distinct,
delete_limit: match mode {
QueryMode::Delete(spec) => spec.limit.map(|max_rows| DeleteLimitSpec { max_rows }),
QueryMode::Load(_) => None,
},
page: match mode {
QueryMode::Load(spec) => {
if spec.limit.is_some() || spec.offset > 0 {
Some(PageSpec {
limit: spec.limit,
offset: spec.offset,
})
} else {
None
}
}
QueryMode::Delete(_) => None,
},
consistency,
};
if let Some(group) = group {
LogicalPlan::Grouped(GroupPlan {
scalar,
group,
having,
})
} else {
debug_assert!(
having.is_none(),
"HAVING clauses require grouped shape before logical plan assembly"
);
LogicalPlan::Scalar(scalar)
}
}
fn canonicalize_order_spec(model: &EntityModel, order: Option<OrderSpec>) -> Option<OrderSpec> {
let mut order = order?;
let pk = model.primary_key.name;
let mut pk_direction = None;
order.fields.retain(|(field, dir)| {
if field == pk {
pk_direction.get_or_insert(*dir);
false
} else {
true
}
});
order
.fields
.push((pk.to_string(), pk_direction.unwrap_or(OrderDirection::Asc)));
Some(order)
}