#[cfg(test)]
mod grouped_order_tests;
#[cfg(test)]
mod slot_tests;
use crate::{
db::{
QueryError,
query::plan::{
GroupFieldRef, GroupFieldSet,
expr::ast::{Alias, BinaryOp, Expr, FieldId},
},
query::preparation::PreparationWork,
schema::SchemaInfo,
},
error::InternalError,
value::Value,
};
use icydb_diagnostic_code::DiagnosticExecutionBudgetResource as Resource;
use std::cmp::Ordering;
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db) enum ProjectionSelection {
All,
Fields(Vec<FieldId>),
Exprs(Vec<ProjectionField>),
}
impl ProjectionSelection {
pub(in crate::db) fn copy_for_preparation(
&self,
work: &PreparationWork<'_>,
) -> Result<Self, QueryError> {
Ok(match self {
Self::All => Self::All,
Self::Fields(fields) => Self::Fields(work.copy_slice(fields, |field| {
Ok(FieldId::new(work.copy_text(field.as_str())?))
})?),
Self::Exprs(fields) => {
Self::Exprs(work.copy_slice(fields, |field| field.copy_for_preparation(work))?)
}
})
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db) enum ProjectionField {
Scalar { expr: Expr, alias: Option<Alias> },
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub(in crate::db) struct ProjectionSpec {
fields: Vec<ProjectionField>,
}
impl ProjectionSpec {
#[must_use]
pub(in crate::db::query::plan) const fn new(fields: Vec<ProjectionField>) -> Self {
Self { fields }
}
#[must_use]
#[cfg(test)]
pub(in crate::db) const fn from_fields_for_test(fields: Vec<ProjectionField>) -> Self {
Self::new(fields)
}
#[must_use]
pub(in crate::db) const fn len(&self) -> usize {
self.fields.len()
}
pub(in crate::db) fn fields(&self) -> std::slice::Iter<'_, ProjectionField> {
self.fields.iter()
}
pub(in crate::db) fn referenced_slots_for_schema(
&self,
schema: &SchemaInfo,
work: &PreparationWork<'_>,
) -> Result<Vec<usize>, QueryError> {
let mut referenced = Vec::new();
for field in self.fields() {
mark_projection_expr_slots(schema, field.expr(), &mut referenced, work)?;
}
Ok(referenced)
}
pub(in crate::db) fn is_schema_identity_for(
&self,
schema: &SchemaInfo,
work: &PreparationWork<'_>,
) -> Result<bool, QueryError> {
work.charge(Resource::PredicateExpressionSteps, 1)?;
if self.len() != schema.field_count() {
return Ok(false);
}
let mut previous = None;
for field in self.fields() {
work.charge(Resource::PredicateExpressionSteps, 1)?;
let ProjectionField::Scalar {
expr: Expr::Field(field),
alias: None,
} = field
else {
return Ok(false);
};
work.charge(
Resource::PredicateExpressionSteps,
field.as_str().len() as u64,
)?;
let Some(slot) = schema.field_slot_index(field.as_str()) else {
return Ok(false);
};
if let Some(previous) = previous {
work.charge(Resource::PredicateExpressionSteps, 1)?;
if slot <= previous {
return Ok(false);
}
}
previous = Some(slot);
}
Ok(true)
}
}
impl ProjectionField {
pub(in crate::db) fn copy_for_preparation(
&self,
work: &PreparationWork<'_>,
) -> Result<Self, QueryError> {
let Self::Scalar { expr, alias } = self;
Ok(Self::Scalar {
expr: work.copy_expr(expr)?,
alias: alias
.as_ref()
.map(|alias| work.copy_text(alias.as_str()).map(Alias::new))
.transpose()?,
})
}
#[must_use]
pub(in crate::db) const fn expr(&self) -> &Expr {
match self {
Self::Scalar { expr, .. } => expr,
}
}
#[must_use]
pub(in crate::db) const fn direct_field_name(&self) -> Option<&str> {
direct_projection_expr_field_name(self.expr())
}
}
fn mark_projection_expr_slots(
schema: &SchemaInfo,
expr: &Expr,
referenced: &mut Vec<usize>,
work: &PreparationWork<'_>,
) -> Result<(), QueryError> {
expr.try_for_each_tree_expr(&mut |node| {
work.charge(Resource::PredicateExpressionSteps, 1)?;
let field_name = match node {
Expr::Field(field) => field.as_str(),
Expr::FieldPath(path) => path.root().as_str(),
_ => return Ok(()),
};
work.charge(Resource::PredicateExpressionSteps, field_name.len() as u64)?;
let slot = schema
.field_slot_index(field_name)
.ok_or_else(|| QueryError::execute(InternalError::query_invalid_logical_plan()))?;
insert_projection_slot(referenced, slot, work)
})
}
fn insert_projection_slot(
referenced: &mut Vec<usize>,
slot: usize,
work: &PreparationWork<'_>,
) -> Result<(), QueryError> {
let (mut low, mut high) = (0, referenced.len());
while low < high {
work.charge(Resource::PredicateExpressionSteps, 1)?;
let mid = low + (high - low) / 2;
match referenced[mid].cmp(&slot) {
Ordering::Less => low = mid + 1,
Ordering::Greater => high = mid,
Ordering::Equal => return Ok(()),
}
}
work.charge(
Resource::PredicateExpressionSteps,
(referenced.len() - low) as u64,
)?;
work.reserve_vec(referenced, 1)?;
referenced.insert(low, slot);
Ok(())
}
#[must_use]
pub(in crate::db) const fn direct_projection_expr_field_name(expr: &Expr) -> Option<&str> {
match expr {
Expr::Field(field) => Some(field.as_str()),
Expr::Unary { .. }
| Expr::FieldPath(_)
| Expr::Literal(_)
| Expr::FunctionCall { .. }
| Expr::Aggregate(_)
| Expr::Case { .. }
| Expr::Binary { .. } => None,
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db) enum GroupedOrderExprClass {
CanonicalGroupField,
GroupFieldPlusConstant,
GroupFieldMinusConstant,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db) enum GroupedOrderTermAdmissibility {
Preserves(GroupedOrderExprClass),
PrefixMismatch,
UnsupportedExpression,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db) enum GroupedTopKOrderTermAdmissibility {
Admissible,
NonGroupFieldReference,
UnsupportedExpression,
}
pub(in crate::db) fn try_classify_grouped_order_term_for_field<E>(
expr: &Expr,
expected_group_field: GroupFieldRef<'_>,
observe: &mut impl FnMut(u64) -> Result<(), E>,
) -> Result<GroupedOrderTermAdmissibility, E> {
observe(1)?;
let (field, class) = match expr {
Expr::Field(_) | Expr::FieldPath(_) => (expr, GroupedOrderExprClass::CanonicalGroupField),
Expr::Binary { op, left, right }
if matches!(left.as_ref(), Expr::Field(_) | Expr::FieldPath(_))
&& is_numeric_order_offset_literal(right) =>
{
let class = match op {
BinaryOp::Add => GroupedOrderExprClass::GroupFieldPlusConstant,
BinaryOp::Sub => GroupedOrderExprClass::GroupFieldMinusConstant,
_ => return Ok(GroupedOrderTermAdmissibility::UnsupportedExpression),
};
(left.as_ref(), class)
}
_ => return Ok(GroupedOrderTermAdmissibility::UnsupportedExpression),
};
Ok(if expected_group_field.try_matches_expr(field, observe)? {
GroupedOrderTermAdmissibility::Preserves(class)
} else {
GroupedOrderTermAdmissibility::PrefixMismatch
})
}
const fn is_numeric_order_offset_literal(expr: &Expr) -> bool {
matches!(
expr,
Expr::Literal(
Value::Int64(_)
| Value::Int128(_)
| Value::IntBig(_)
| Value::Nat64(_)
| Value::Nat128(_)
| Value::NatBig(_)
| Value::Decimal(_)
| Value::Float32(_)
| Value::Float64(_)
)
)
}
pub(in crate::db) fn try_classify_grouped_top_k_order_term<E>(
expr: &Expr,
group_fields: &GroupFieldSet,
observe: &mut impl FnMut(u64) -> Result<(), E>,
) -> Result<GroupedTopKOrderTermAdmissibility, E> {
let mut contains_aggregate = false;
let mut contains_function = false;
let only_group_fields = expr.try_all_tree_expr(&mut |node| {
observe(1)?;
Ok(match node {
Expr::Field(_) | Expr::FieldPath(_) => group_fields.try_contains_expr(node, observe)?,
Expr::Aggregate(_) => {
contains_aggregate = true;
true
}
Expr::FunctionCall { .. } => {
contains_function = true;
true
}
Expr::Literal(_) | Expr::Binary { .. } | Expr::Unary { .. } | Expr::Case { .. } => true,
})
})?;
if !only_group_fields {
return Ok(GroupedTopKOrderTermAdmissibility::NonGroupFieldReference);
}
if !contains_aggregate && contains_function {
return Ok(GroupedTopKOrderTermAdmissibility::UnsupportedExpression);
}
Ok(GroupedTopKOrderTermAdmissibility::Admissible)
}
pub(in crate::db) fn try_grouped_top_k_order_term_requires_heap<E>(
expr: &Expr,
observe: &mut impl FnMut(u64) -> Result<(), E>,
) -> Result<bool, E> {
Ok(!expr.try_all_tree_expr(&mut |node| {
observe(1)?;
Ok(!matches!(node, Expr::Aggregate(_) | Expr::Case { .. }))
})?)
}
crate::retained::retained_fields!(ProjectionField {
Self::Scalar{expr,alias} => [expr,alias],
});
crate::retained::retained_fields!(ProjectionSelection {
Self::All => [],
Self::Fields(field_0) => [field_0],
Self::Exprs(field_0) => [field_0],
});
crate::retained::retained_fields!(ProjectionSpec {
Self{fields} => [fields],
});