use super::PartitionContext;
use crate::{
ast::{PartitionBound, PartitionRangeDatum},
SQLError,
};
use uqa_core::Value;
#[cfg(test)]
mod tests;
#[derive(PartialEq, Eq, PartialOrd, Ord)]
enum RangePoint {
MinValue,
Value(Value),
MaxValue,
}
#[derive(PartialEq, Eq, PartialOrd, Ord)]
enum Position {
Range(Vec<RangePoint>),
List(Value),
Hash(i32, i32),
NullList,
Default,
}
fn position(context: &PartitionContext<'_>, bound: &PartitionBound) -> Result<Position, SQLError> {
let evaluate = |expression| context.expressions.evaluate_bound(expression, &[]);
Ok(match bound {
PartitionBound::Default => Position::Default,
PartitionBound::Range { lower, .. } => Position::Range(
lower
.iter()
.map(|datum| {
Ok(match datum {
PartitionRangeDatum::MinValue => RangePoint::MinValue,
PartitionRangeDatum::Value(expression) => {
RangePoint::Value(evaluate(expression)?)
}
PartitionRangeDatum::MaxValue => RangePoint::MaxValue,
})
})
.collect::<Result<_, SQLError>>()?,
),
PartitionBound::List(values) => {
let mut smallest = None;
for value in values {
let value = evaluate(value)?;
if value != Value::Null && smallest.as_ref().is_none_or(|current| value < *current)
{
smallest = Some(value);
}
}
smallest.map_or(Position::NullList, Position::List)
}
PartitionBound::Hash { modulus, remainder } => Position::Hash(*modulus, *remainder),
})
}
pub fn partition_bound_order(
context: &PartitionContext<'_>,
partitions: Vec<(String, PartitionBound)>,
) -> Result<Vec<String>, SQLError> {
let mut positioned = partitions
.into_iter()
.map(|(name, bound)| Ok((position(context, &bound)?, name)))
.collect::<Result<Vec<_>, SQLError>>()?;
positioned.sort_by(|left, right| left.0.cmp(&right.0));
Ok(positioned.into_iter().map(|(_, name)| name).collect())
}