use std::cmp::Ordering;
use rudb_common::Result;
use rudb_common::bounds::{Bound, End, Frequencies, Remainder, Spread, Test, Zones, kept};
use rudb_common::stat::{Direction, Provenance};
use rudb_common::{Stat, Value};
use rudb_storage::Probe;
use crate::Reader;
#[derive(Debug, Clone)]
pub struct Stripes {
reader: Reader,
}
impl Stripes {
#[must_use]
pub fn new(reader: Reader) -> Self {
Self { reader }
}
}
impl Zones for Stripes {
fn column(&self, name: &str) -> Option<usize> {
self.reader.table().fields().iter().position(|field| field.name == name)
}
fn surviving(&self, tests: &[Test]) -> Option<u64> {
let probes = probes(tests);
let mut total: u64 = 0;
for (at, stripe) in self.reader.table().stripes().iter().enumerate() {
if self.reader.stripe_skips(at, &probes) {
continue;
}
total = total.checked_add(u64::try_from(stripe.rows()).ok()?)?;
}
Some(total)
}
fn spread(&self, tests: &[Test]) -> Option<Spread> {
let mut passing = 0.0_f64;
let mut whole = 0.0_f64;
let mut read = 0;
for stripe in self.reader.table().stripes() {
let rows = rows(stripe.rows());
let spread = fraction(tests, stripe.zone());
whole += rows;
passing += rows * spread.fraction;
read = read.max(spread.read);
}
(read > 0 && whole > 0.0)
.then(|| Spread { fraction: (passing / whole).clamp(0.0, 1.0), read })
}
fn nulls(&self, column: usize) -> Stat<u64> {
self.reader
.null_count(column)
.map_or(Stat::Unknown, |nulls| Stat::exact(nulls, Provenance::NullCount))
}
fn extreme(&self, column: usize, end: End) -> Stat<Bound> {
match self.reader.exact_extremes(column) {
Ok(Some((low, high))) => {
Stat::exact(if end == End::Low { low } else { high }, Provenance::ZoneMap)
}
_ => Stat::Unknown,
}
}
}
pub fn distincts(reader: &Reader) -> Result<Vec<(String, Stat<u64>)>> {
let table = reader.table();
let rows = u64::try_from(table.rows()).unwrap_or(u64::MAX);
let mut counted = Vec::new();
for (at, field) in table.fields().iter().enumerate() {
if let Some(exact) = reader.distinct_values(at)? {
counted.push((field.name.clone(), Stat::exact(exact, Provenance::Dictionary)));
continue;
}
let Some((Bound::Int(low), Bound::Int(high))) = reader.exact_extremes(at)? else {
continue;
};
let Some(span) = high.checked_sub(low).and_then(|span| u64::try_from(span).ok()) else {
continue;
};
let Some(span) = span.checked_add(1).filter(|&span| span < rows) else {
continue;
};
counted.push((
field.name.clone(),
Stat::certified(span, 1.0, Direction::AtMost, Provenance::ZoneMap),
));
}
Ok(counted)
}
#[must_use]
pub fn ascending(reader: &Reader) -> Vec<String> {
reader
.table()
.fields()
.iter()
.enumerate()
.filter(|&(at, _)| {
crate::stats::held_summary(reader, at).is_some_and(|summary| {
summary.nulls == 0 && summary.order == rudb_stats::summary::Order::Ascending
})
})
.map(|(_, field)| field.name.clone())
.collect()
}
#[must_use]
pub fn widths(reader: &Reader) -> Vec<(String, u64)> {
reader
.table()
.fields()
.iter()
.enumerate()
.filter(|(_, field)| field.ty.physical() == rudb_common::PhysicalType::Varlen)
.filter_map(|(at, field)| {
let summary = crate::stats::held_summary(reader, at)?;
let values = summary.rows.checked_sub(summary.nulls).filter(|&values| values > 0)?;
Some((field.name.clone(), summary.bytes.div_ceil(values)))
})
.collect()
}
#[derive(Debug, Clone)]
pub struct Common {
reader: Reader,
}
impl Common {
#[must_use]
pub fn new(reader: Reader) -> Self {
Self { reader }
}
}
impl Frequencies for Common {
fn column(&self, name: &str) -> Option<usize> {
self.reader.table().fields().iter().position(|field| field.name == name)
}
fn rows(&self) -> u64 {
u64::try_from(self.reader.table().rows()).unwrap_or(u64::MAX)
}
fn rows_with(&self, column: usize, value: &Bound) -> Stat<u64> {
let Ok(Some((entries, omitted_max))) = self.reader.held_prefix(column) else {
return Stat::Unknown;
};
let mut comparable = false;
for (held, count) in entries.iter() {
let Some(bound) = Bound::of_value(held) else {
continue;
};
match bound.order(value) {
Some(Ordering::Equal) => return Stat::exact(*count, Provenance::FrequencySynopsis),
Some(_) => comparable = true,
None => {}
}
}
if omitted_max == 0 && comparable {
Stat::exact(0, Provenance::FrequencySynopsis)
} else {
Stat::Unknown
}
}
fn remainder(&self, column: usize) -> Option<Remainder> {
let Ok(Some((entries, omitted_max))) = self.reader.held_prefix(column) else {
return None;
};
if omitted_max == 0 {
return None;
}
let mut held: u64 = 0;
let mut listed: u64 = 0;
for (value, count) in entries.iter() {
held = held.saturating_add(*count);
if !matches!(value, Value::Null) {
listed += 1;
}
}
let rows = Frequencies::rows(self).saturating_sub(held);
Some(Remainder { rows, listed, most: omitted_max })
}
}
fn probes(tests: &[Test]) -> Vec<Probe> {
tests
.iter()
.map(|test| Probe { column: test.column, op: test.op, value: test.value.clone() })
.collect()
}
#[expect(clippy::cast_precision_loss, reason = "a row count is a weight here and not an identity")]
fn rows(count: usize) -> f64 {
count as f64
}
fn fraction(tests: &[Test], zone: &rudb_storage::Zone) -> Spread {
let mut spread = Spread { fraction: 1.0, read: 0 };
for (position, test) in tests.iter().enumerate() {
if tests[..position].iter().any(|earlier| earlier.column == test.column) {
continue;
}
let Some(range) = zone.column(test.column) else { continue };
let (Some(low), Some(high)) = (range.low.as_ref(), range.high.as_ref()) else { continue };
let Some(kept) = kept(tests, test.column, low, high) else { continue };
spread.fraction *= kept.fraction;
spread.read += kept.read;
}
spread
}