use crate::{
db::{
predicate::{MissingRowPolicy, Predicate},
query::plan::{
DeleteLimitSpec, GroupPlan, GroupSpec, LogicalPlan, OrderDirection, OrderSpec,
PageSpec, QueryMode, ScalarPlan, expr::Expr,
},
},
model::entity::EntityModel,
};
#[derive(Debug)]
pub(in crate::db::query) struct LogicalPlanningInputs {
mode: QueryMode,
order: Option<OrderSpec>,
distinct: bool,
group: Option<GroupSpec>,
having_expr: Option<Expr>,
}
impl LogicalPlanningInputs {
#[must_use]
pub(in crate::db::query) const fn new(
mode: QueryMode,
order: Option<OrderSpec>,
distinct: bool,
group: Option<GroupSpec>,
having_expr: Option<Expr>,
) -> Self {
Self {
mode,
order,
distinct,
group,
having_expr,
}
}
}
#[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_expr: Option<Expr>,
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_expr,
} = inputs;
LogicalQuery {
mode,
normalized_predicate,
order,
distinct,
group,
having_expr,
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_expr,
consistency,
} = query;
let scalar = ScalarPlan {
mode,
predicate: normalized_predicate,
order: canonicalize_order_spec(model, order),
distinct,
delete_limit: match mode {
QueryMode::Delete(spec) if spec.limit.is_some() || spec.offset() > 0 => {
Some(DeleteLimitSpec {
limit: spec.limit(),
offset: spec.offset(),
})
}
QueryMode::Load(_) | QueryMode::Delete(_) => None,
},
page: match mode {
QueryMode::Load(spec) if spec.limit.is_some() || spec.offset > 0 => Some(PageSpec {
limit: spec.limit,
offset: spec.offset,
}),
QueryMode::Load(_) | QueryMode::Delete(_) => None,
},
consistency,
};
if let Some(group) = group {
LogicalPlan::Grouped(GroupPlan {
scalar,
group,
having_expr,
})
} else {
debug_assert!(
having_expr.is_none(),
"HAVING clauses require grouped shape before logical plan assembly"
);
LogicalPlan::Scalar(scalar)
}
}
#[must_use]
pub(in crate::db::query) 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)
}