use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use spg_sql::ast::Expr;
use spg_storage::{Catalog, PartitionBound, PartitionRole, Value};
use crate::EngineError;
use crate::conversions::literal_expr_to_value;
pub(crate) fn is_partition_parent(catalog: &Catalog, table_name: &str) -> bool {
catalog
.get(table_name)
.map(|t| {
matches!(
t.schema().partition_role,
Some(PartitionRole::Parent { .. })
)
})
.unwrap_or(false)
}
pub(crate) fn children_of_parent(catalog: &Catalog, parent: &str) -> Vec<String> {
let mut out = Vec::new();
for name in catalog.table_names() {
let Some(t) = catalog.get(&name) else {
continue;
};
match &t.schema().partition_role {
Some(PartitionRole::Range { parent_name, .. })
| Some(PartitionRole::Default { parent_name })
if parent_name == parent =>
{
out.push(name);
}
_ => {}
}
}
out
}
pub(crate) fn evaluate_partition_bound(expr: Expr) -> Result<PartitionBound, EngineError> {
if let Expr::FunctionCall { name, args } = &expr
&& args.is_empty()
{
let upper = name.to_ascii_uppercase();
if upper == "MINVALUE" {
return Ok(PartitionBound::MinValue);
}
if upper == "MAXVALUE" {
return Ok(PartitionBound::MaxValue);
}
}
let value = literal_expr_to_value(expr)?;
match value {
Value::Timestamp(micros) => Ok(PartitionBound::TimestampTz(micros)),
Value::Date(days) => {
let micros = i64::from(days) * 86_400i64 * 1_000_000i64;
Ok(PartitionBound::TimestampTz(micros))
}
Value::Text(s) => {
match crate::eval::parse_timestamp_literal(&s) {
Some(micros) => Ok(PartitionBound::TimestampTz(micros)),
None => Err(EngineError::Unsupported(format!(
"PARTITION OF: bound literal {s:?} not recognised \
as a TIMESTAMPTZ"
))),
}
}
other => Err(EngineError::Unsupported(format!(
"PARTITION OF: bound must be TIMESTAMPTZ literal or \
MINVALUE/MAXVALUE, got {other:?}"
))),
}
}
fn bound_cmp(a: &PartitionBound, b: &PartitionBound) -> core::cmp::Ordering {
use PartitionBound::{MaxValue, MinValue, TimestampTz};
use core::cmp::Ordering;
match (a, b) {
(MinValue, MinValue) | (MaxValue, MaxValue) => Ordering::Equal,
(MinValue, _) => Ordering::Less,
(_, MinValue) => Ordering::Greater,
(MaxValue, _) => Ordering::Greater,
(_, MaxValue) => Ordering::Less,
(TimestampTz(x), TimestampTz(y)) => x.cmp(y),
}
}
pub(crate) fn ranges_overlap(
a_lo: &PartitionBound,
a_hi: &PartitionBound,
b_lo: &PartitionBound,
b_hi: &PartitionBound,
) -> bool {
use core::cmp::Ordering;
bound_cmp(a_lo, b_hi) == Ordering::Less && bound_cmp(b_lo, a_hi) == Ordering::Less
}
#[allow(dead_code)] pub(crate) fn value_in_range(
value_micros: i64,
lower: &PartitionBound,
upper: &PartitionBound,
) -> bool {
let lower_ok = match lower {
PartitionBound::MinValue => true,
PartitionBound::MaxValue => false,
PartitionBound::TimestampTz(m) => value_micros >= *m,
};
let upper_ok = match upper {
PartitionBound::MinValue => false,
PartitionBound::MaxValue => true,
PartitionBound::TimestampTz(m) => value_micros < *m,
};
lower_ok && upper_ok
}
pub(crate) fn bound_to_diag(b: &PartitionBound) -> String {
match b {
PartitionBound::MinValue => "MINVALUE".to_string(),
PartitionBound::MaxValue => "MAXVALUE".to_string(),
PartitionBound::TimestampTz(m) => format!("'{m}'::timestamptz"),
}
}
#[cfg(test)]
mod tests {
use super::*;
use PartitionBound::{MaxValue, MinValue, TimestampTz};
#[test]
fn bound_cmp_min_max_sentinels() {
use core::cmp::Ordering;
assert_eq!(bound_cmp(&MinValue, &MinValue), Ordering::Equal);
assert_eq!(bound_cmp(&MaxValue, &MaxValue), Ordering::Equal);
assert_eq!(bound_cmp(&MinValue, &TimestampTz(0)), Ordering::Less);
assert_eq!(bound_cmp(&TimestampTz(0), &MaxValue), Ordering::Less);
assert_eq!(
bound_cmp(&TimestampTz(100), &TimestampTz(99)),
Ordering::Greater
);
}
#[test]
fn ranges_overlap_half_open() {
let a_lo = TimestampTz(0);
let a_hi = TimestampTz(100);
let b_lo = TimestampTz(100);
let b_hi = TimestampTz(200);
let c_lo = TimestampTz(50);
let c_hi = TimestampTz(150);
assert!(!ranges_overlap(&a_lo, &a_hi, &b_lo, &b_hi));
assert!(ranges_overlap(&a_lo, &a_hi, &c_lo, &c_hi));
assert!(ranges_overlap(&b_lo, &b_hi, &c_lo, &c_hi));
assert!(ranges_overlap(&a_lo, &a_hi, &a_lo, &a_hi));
assert!(!ranges_overlap(&a_lo, &a_lo, &a_lo, &a_lo));
assert!(ranges_overlap(&MinValue, &MaxValue, &a_lo, &a_hi));
}
#[test]
fn value_in_range_inclusive_lower_exclusive_upper() {
assert!(value_in_range(50, &TimestampTz(0), &TimestampTz(100)));
assert!(value_in_range(0, &TimestampTz(0), &TimestampTz(100)));
assert!(!value_in_range(100, &TimestampTz(0), &TimestampTz(100)));
assert!(!value_in_range(-1, &TimestampTz(0), &TimestampTz(100)));
assert!(value_in_range(i64::MIN, &MinValue, &MaxValue));
assert!(value_in_range(i64::MAX - 1, &MinValue, &MaxValue));
assert!(!value_in_range(0, &MaxValue, &MaxValue));
assert!(!value_in_range(0, &MinValue, &MinValue));
}
}