#[cfg(any(test, feature = "sql"))]
use crate::db::query::plan::expr::normalized_bool_expr_from_predicate;
use crate::db::{
predicate::Predicate,
query::{
intent::{KeyAccessState, build_access_plan_from_keys},
plan::{
AccessPlanningInputs, DeleteSpec, GroupSpec, GroupedExecutionConfig, LoadSpec,
LogicalPlanningInputs, OrderSpec, QueryMode,
expr::{
BinaryOp, Expr, ProjectionSelection, derive_normalized_bool_expr_predicate_subset,
is_normalized_bool_expr, normalize_bool_expr,
},
has_explicit_order,
},
},
};
#[derive(Clone, Debug)]
pub(in crate::db::query::intent) struct NormalizedFilter {
expr: Expr,
predicate_subset: Option<Predicate>,
predicate_subset_covers_expr: bool,
filter_expr_visible: bool,
}
impl NormalizedFilter {
#[must_use]
pub(in crate::db::query::intent) fn from_normalized_expr(expr: Expr) -> Self {
debug_assert!(
is_normalized_bool_expr(&expr),
"intent-owned filter expressions must be normalized before storage",
);
let predicate_subset = derive_normalized_bool_expr_predicate_subset(&expr);
let predicate_subset_covers_expr = predicate_subset.is_some();
Self {
expr,
predicate_subset,
predicate_subset_covers_expr,
filter_expr_visible: true,
}
}
#[must_use]
#[cfg(any(test, feature = "sql"))]
pub(in crate::db::query::intent) fn from_normalized_expr_and_predicate_subset(
expr: Expr,
predicate_subset: Predicate,
) -> Self {
debug_assert!(
is_normalized_bool_expr(&expr),
"intent-owned filter expressions must be normalized before storage",
);
Self {
expr,
predicate_subset: Some(predicate_subset),
predicate_subset_covers_expr: true,
filter_expr_visible: true,
}
}
#[must_use]
#[cfg(any(test, feature = "sql"))]
pub(in crate::db::query::intent) fn from_normalized_predicate(predicate: Predicate) -> Self {
let expr = normalized_bool_expr_from_predicate(&predicate);
Self {
expr,
predicate_subset: Some(predicate),
predicate_subset_covers_expr: false,
filter_expr_visible: false,
}
}
#[must_use]
pub(in crate::db::query::intent) const fn logical_filter_expr(&self) -> Option<&Expr> {
if self.filter_expr_visible {
Some(&self.expr)
} else {
None
}
}
#[must_use]
pub(in crate::db::query::intent) const fn predicate_subset(&self) -> Option<&Predicate> {
self.predicate_subset.as_ref()
}
#[must_use]
pub(in crate::db::query::intent) const fn predicate_subset_covers_expr(&self) -> bool {
self.filter_expr_visible && self.predicate_subset_covers_expr
}
pub(in crate::db::query::intent) fn append(&mut self, filter: Self) {
let existing_covers_expr = self.predicate_subset_covers_expr;
let filter_covers_expr = filter.predicate_subset_covers_expr;
match (self.filter_expr_visible, filter.filter_expr_visible) {
(true, true) => {
self.expr = normalize_bool_expr(Expr::Binary {
op: BinaryOp::And,
left: Box::new(self.expr.clone()),
right: Box::new(filter.expr),
});
self.predicate_subset_covers_expr = existing_covers_expr && filter_covers_expr;
}
(true, false) => {}
(false, true) => {
self.expr = filter.expr;
self.filter_expr_visible = true;
self.predicate_subset_covers_expr = filter_covers_expr;
}
(false, false) => {
self.expr = normalize_bool_expr(Expr::Binary {
op: BinaryOp::And,
left: Box::new(self.expr.clone()),
right: Box::new(filter.expr),
});
self.predicate_subset_covers_expr = false;
}
}
debug_assert!(
is_normalized_bool_expr(&self.expr),
"combined intent-owned filter expressions must stay normalized",
);
self.predicate_subset =
combine_predicate_subset(self.predicate_subset.take(), filter.predicate_subset);
self.predicate_subset_covers_expr =
self.predicate_subset_covers_expr && self.predicate_subset.is_some();
}
}
fn combine_predicate_subset(
existing: Option<Predicate>,
appended: Option<Predicate>,
) -> Option<Predicate> {
match (existing, appended) {
(Some(existing), Some(appended)) => Some(Predicate::And(vec![existing, appended])),
(Some(existing), None) => Some(existing),
(None, Some(appended)) => Some(appended),
(None, None) => None,
}
}
#[derive(Clone, Debug)]
pub(in crate::db::query::intent) struct ScalarIntent<K> {
pub(in crate::db::query::intent) filter: Option<NormalizedFilter>,
pub(in crate::db::query::intent) key_access: Option<KeyAccessState<K>>,
pub(in crate::db::query::intent) key_access_conflict: bool,
pub(in crate::db::query::intent) order: Option<OrderSpec>,
pub(in crate::db::query::intent) distinct: bool,
pub(in crate::db::query::intent) projection_selection: ProjectionSelection,
}
impl<K> ScalarIntent<K> {
#[must_use]
pub(in crate::db::query::intent) const fn new() -> Self {
Self {
filter: None,
key_access: None,
key_access_conflict: false,
order: None,
distinct: false,
projection_selection: ProjectionSelection::All,
}
}
}
#[derive(Clone, Debug)]
pub(in crate::db::query::intent) struct GroupedIntent<K> {
pub(in crate::db::query::intent) scalar: ScalarIntent<K>,
pub(in crate::db::query::intent) group: GroupSpec,
pub(in crate::db::query::intent) having_expr: Option<Expr>,
}
impl<K> GroupedIntent<K> {
#[must_use]
pub(in crate::db::query::intent) const fn from_scalar(scalar: ScalarIntent<K>) -> Self {
Self {
scalar,
group: GroupSpec {
group_fields: Vec::new(),
aggregates: Vec::new(),
execution: GroupedExecutionConfig::unbounded(),
},
having_expr: None,
}
}
}
#[derive(Clone, Debug)]
enum QueryShape<K> {
Scalar(ScalarIntent<K>),
Grouped(GroupedIntent<K>),
}
#[derive(Clone, Debug)]
pub(in crate::db::query::intent) struct LoadIntentState<K> {
spec: LoadSpec,
offset_requested: bool,
shape: QueryShape<K>,
}
impl<K> LoadIntentState<K> {
#[must_use]
const fn new() -> Self {
Self {
spec: LoadSpec::new(),
offset_requested: false,
shape: QueryShape::Scalar(ScalarIntent::new()),
}
}
}
#[derive(Clone, Copy, Debug)]
struct DeletePolicyState {
grouping_requested: bool,
}
#[derive(Clone, Debug)]
pub(in crate::db::query::intent) struct DeleteIntentState<K> {
spec: DeleteSpec,
scalar: ScalarIntent<K>,
policy: DeletePolicyState,
}
impl<K> DeleteIntentState<K> {
#[must_use]
const fn new(scalar: ScalarIntent<K>, policy: DeletePolicyState) -> Self {
Self {
spec: DeleteSpec::new(),
scalar,
policy,
}
}
}
#[derive(Clone, Debug)]
pub(in crate::db::query::intent) enum QueryIntent<K> {
Load(LoadIntentState<K>),
Delete(DeleteIntentState<K>),
}
impl<K> QueryIntent<K> {
#[must_use]
pub(in crate::db::query::intent) const fn new() -> Self {
Self::Load(LoadIntentState::new())
}
#[must_use]
pub(in crate::db::query::intent) const fn mode(&self) -> QueryMode {
match self {
Self::Load(load) => QueryMode::Load(load.spec),
Self::Delete(delete) => QueryMode::Delete(delete.spec),
}
}
#[must_use]
pub(in crate::db::query::intent) const fn is_grouped(&self) -> bool {
match self {
Self::Load(load) => matches!(load.shape, QueryShape::Grouped(_)),
Self::Delete(delete) => delete.policy.grouping_requested,
}
}
#[must_use]
pub(in crate::db::query::intent) fn has_explicit_order(&self) -> bool {
has_explicit_order(self.scalar().order.as_ref())
}
#[must_use]
pub(in crate::db::query::intent) fn set_delete_mode(self) -> Self {
match self {
Self::Delete(delete) => Self::Delete(delete),
Self::Load(load) => {
let (scalar, grouping_requested) = match load.shape {
QueryShape::Scalar(scalar) => (scalar, false),
QueryShape::Grouped(grouped) => (grouped.scalar, true),
};
let policy = DeletePolicyState { grouping_requested };
Self::Delete(DeleteIntentState::new(scalar, policy))
}
}
}
#[must_use]
pub(in crate::db::query::intent) const fn apply_limit(mut self, limit: u32) -> Self {
match &mut self {
Self::Load(load) => {
load.spec.limit = Some(limit);
}
Self::Delete(delete) => {
delete.spec.limit = Some(limit);
}
}
self
}
#[must_use]
pub(in crate::db::query::intent) const fn apply_offset(mut self, offset: u32) -> Self {
match &mut self {
Self::Load(load) => {
load.offset_requested = true;
load.spec.offset = offset;
}
Self::Delete(delete) => {
delete.spec.offset = offset;
}
}
self
}
#[must_use]
pub(in crate::db::query::intent) const fn scalar(&self) -> &ScalarIntent<K> {
match self {
Self::Load(load) => match &load.shape {
QueryShape::Scalar(scalar) => scalar,
QueryShape::Grouped(grouped) => &grouped.scalar,
},
Self::Delete(delete) => &delete.scalar,
}
}
#[must_use]
pub(in crate::db::query::intent) const fn scalar_mut(&mut self) -> &mut ScalarIntent<K> {
match self {
Self::Load(load) => match &mut load.shape {
QueryShape::Scalar(scalar) => scalar,
QueryShape::Grouped(grouped) => &mut grouped.scalar,
},
Self::Delete(delete) => &mut delete.scalar,
}
}
#[must_use]
pub(in crate::db::query::intent) const fn grouped(&self) -> Option<&GroupedIntent<K>> {
match self {
Self::Load(load) => match &load.shape {
QueryShape::Grouped(grouped) => Some(grouped),
QueryShape::Scalar(_) => None,
},
Self::Delete(_) => None,
}
}
#[must_use]
pub(in crate::db::query::intent) const fn grouped_mut(
&mut self,
) -> Option<&mut GroupedIntent<K>> {
match self {
Self::Load(load) => match &mut load.shape {
QueryShape::Grouped(grouped) => Some(grouped),
QueryShape::Scalar(_) => None,
},
Self::Delete(_) => None,
}
}
pub(in crate::db::query::intent) fn ensure_grouped_mut(&mut self) -> &mut GroupedIntent<K> {
let Self::Load(load) = self else {
panic!("grouped shape cannot be materialized in delete mode");
};
if matches!(load.shape, QueryShape::Scalar(_)) {
let scalar =
match std::mem::replace(&mut load.shape, QueryShape::Scalar(ScalarIntent::new())) {
QueryShape::Scalar(scalar) => scalar,
QueryShape::Grouped(_) => unreachable!("shape checked above"),
};
load.shape = QueryShape::Grouped(GroupedIntent::from_scalar(scalar));
}
match &mut load.shape {
QueryShape::Grouped(grouped) => grouped,
QueryShape::Scalar(_) => unreachable!("scalar shape lifted to grouped"),
}
}
pub(in crate::db::query::intent) const fn mark_delete_grouping_requested(&mut self) {
if let Self::Delete(delete) = self {
delete.policy.grouping_requested = true;
}
}
#[must_use]
pub(in crate::db::query::intent) fn planning_logical_inputs(&self) -> LogicalPlanningInputs {
let (group, having_expr) = match self.grouped() {
Some(grouped) => (Some(grouped.group.clone()), grouped.having_expr.clone()),
None => (None, None),
};
LogicalPlanningInputs::new(
self.mode(),
self.scalar()
.filter
.as_ref()
.and_then(NormalizedFilter::logical_filter_expr)
.cloned(),
self.scalar()
.filter
.as_ref()
.is_some_and(NormalizedFilter::predicate_subset_covers_expr),
self.scalar().order.clone(),
self.scalar().distinct,
group,
having_expr,
)
}
}
impl<K: crate::traits::KeyValueCodec> QueryIntent<K> {
#[must_use]
pub(in crate::db::query::intent) fn planning_access_inputs(&self) -> AccessPlanningInputs<'_> {
let scalar = self.scalar();
let key_access_override = scalar
.key_access
.as_ref()
.map(|state| build_access_plan_from_keys(&state.access));
AccessPlanningInputs::new(
scalar
.filter
.as_ref()
.and_then(NormalizedFilter::predicate_subset),
scalar.order.as_ref(),
key_access_override,
)
}
}
#[cfg(test)]
mod tests;