use crate::format::record::encode_record;
use crate::value::Value;
use alloc::string::ToString;
use alloc::vec::Vec;
const STAT4_SAMPLES: usize = 24;
pub(crate) struct Stat4Entry {
pub sample: Vec<Value>,
}
pub(crate) struct Stat4Sample {
pub neq: Vec<u64>,
pub nlt: Vec<u64>,
pub ndlt: Vec<u64>,
pub sample: Vec<u8>,
}
impl Stat4Sample {
pub fn stat_string(counts: &[u64]) -> alloc::string::String {
let mut s = alloc::string::String::new();
for (i, c) in counts.iter().enumerate() {
if i > 0 {
s.push(' ');
}
s.push_str(&c.to_string());
}
s
}
}
#[derive(Clone)]
struct Sample {
an_eq: Vec<u64>,
an_lt: Vec<u64>,
an_dlt: Vec<u64>,
sample_values: Vec<Value>,
is_psample: bool,
i_col: usize,
i_hash: u32,
}
impl Sample {
fn new(n_col: usize) -> Self {
Sample {
an_eq: alloc::vec![0; n_col],
an_lt: alloc::vec![0; n_col],
an_dlt: alloc::vec![0; n_col],
sample_values: Vec::new(),
is_psample: false,
i_col: 0,
i_hash: 0,
}
}
}
struct StatAccum {
n_col: usize,
n_row: u64,
n_psample: u64,
mx_sample: usize,
i_prn: u32,
current: Sample,
a_best: Vec<Sample>,
i_min: usize,
n_max_eq_zero: usize,
a: Vec<Sample>,
}
impl StatAccum {
fn new(n_col: usize, n_est: u64) -> Self {
let mx_sample = STAT4_SAMPLES;
let n_psample = n_est / (mx_sample as u64 / 3 + 1) + 1;
let i_prn =
0x689e_962du32.wrapping_mul(n_col as u32) ^ 0xd094_4565u32.wrapping_mul(n_est as u32);
let mut current = Sample::new(n_col);
current.i_col = 0;
let a_best = (0..n_col)
.map(|i| {
let mut s = Sample::new(n_col);
s.i_col = i;
s
})
.collect();
StatAccum {
n_col,
n_row: 0,
n_psample,
mx_sample,
i_prn,
current,
a_best,
i_min: 0,
n_max_eq_zero: 0,
a: Vec::new(),
}
}
fn sample_is_better_post(new: &Sample, old: &Sample, n_col: usize) -> bool {
debug_assert_eq!(new.i_col, old.i_col);
for i in (new.i_col + 1)..n_col {
if new.an_eq[i] > old.an_eq[i] {
return true;
}
if new.an_eq[i] < old.an_eq[i] {
return false;
}
}
new.i_hash > old.i_hash
}
fn sample_is_better(new: &Sample, old: &Sample, n_col: usize) -> bool {
let n_eq_new = new.an_eq[new.i_col];
let n_eq_old = old.an_eq[old.i_col];
if n_eq_new > n_eq_old {
return true;
}
if n_eq_new == n_eq_old {
if new.i_col < old.i_col {
return true;
}
return new.i_col == old.i_col && Self::sample_is_better_post(new, old, n_col);
}
false
}
fn sample_insert(&mut self, new: &Sample, n_eq_zero: usize) {
if n_eq_zero > self.n_max_eq_zero {
self.n_max_eq_zero = n_eq_zero;
}
if !new.is_psample {
debug_assert!(new.an_eq[new.i_col] > 0);
let mut upgrade: Option<usize> = None;
for i in (0..self.a.len()).rev() {
if self.a[i].an_eq[new.i_col] == 0 {
if self.a[i].is_psample {
return;
}
match upgrade {
None => upgrade = Some(i),
Some(u) => {
if Self::sample_is_better(&self.a[i], &self.a[u], self.n_col) {
upgrade = Some(i);
}
}
}
}
}
if let Some(u) = upgrade {
self.a[u].i_col = new.i_col;
let col = self.a[u].i_col;
self.a[u].an_eq[col] = new.an_eq[col];
self.find_new_min();
return;
}
}
if self.a.len() >= self.mx_sample {
self.a.remove(self.i_min);
}
let mut s = new.clone();
for e in s.an_eq.iter_mut().take(n_eq_zero) {
*e = 0;
}
self.a.push(s);
self.find_new_min();
}
fn find_new_min(&mut self) {
if self.a.len() >= self.mx_sample {
let mut i_min: Option<usize> = None;
for i in 0..self.a.len() {
if self.a[i].is_psample {
continue;
}
match i_min {
None => i_min = Some(i),
Some(m) => {
if Self::sample_is_better(&self.a[m], &self.a[i], self.n_col) {
i_min = Some(i);
}
}
}
}
if let Some(m) = i_min {
self.i_min = m;
}
}
}
fn sample_push_previous(&mut self, i_chng: usize) {
for i in (i_chng..=(self.n_col.saturating_sub(2))).rev() {
if self.n_col < 2 {
break;
}
self.a_best[i].an_eq[i] = self.current.an_eq[i];
let better = self.a.len() < self.mx_sample
|| Self::sample_is_better(&self.a_best[i], &self.a[self.i_min], self.n_col);
if better {
let best = self.a_best[i].clone();
self.sample_insert(&best, i);
}
}
if i_chng < self.n_max_eq_zero {
for i in (0..self.a.len()).rev() {
for j in i_chng..self.n_col {
if self.a[i].an_eq[j] == 0 {
self.a[i].an_eq[j] = self.current.an_eq[j];
}
}
}
self.n_max_eq_zero = i_chng;
}
}
fn push(&mut self, i_chng: usize, sample_values: Vec<Value>) {
if self.n_row == 0 {
for e in self.current.an_eq.iter_mut() {
*e = 1;
}
} else {
self.sample_push_previous(i_chng);
for i in 0..i_chng {
self.current.an_eq[i] += 1;
}
for i in i_chng..self.n_col {
self.current.an_dlt[i] += 1;
self.current.an_lt[i] += self.current.an_eq[i];
self.current.an_eq[i] = 1;
}
}
self.n_row += 1;
self.current.sample_values = sample_values;
self.i_prn = self.i_prn.wrapping_mul(1103515245).wrapping_add(12345);
self.current.i_hash = self.i_prn;
let n_lt = self.current.an_lt[self.n_col - 1];
if n_lt / self.n_psample != (n_lt + 1) / self.n_psample {
self.current.is_psample = true;
self.current.i_col = 0;
let cur = self.current.clone();
self.sample_insert(&cur, self.n_col - 1);
self.current.is_psample = false;
}
for i in 0..(self.n_col - 1) {
self.current.i_col = i;
if i >= i_chng
|| Self::sample_is_better_post(&self.current, &self.a_best[i], self.n_col)
{
let mut b = self.current.clone();
b.i_col = i;
self.a_best[i] = b;
}
}
}
fn finish(mut self) -> Vec<Stat4Sample> {
self.sample_push_previous(0);
self.a
.into_iter()
.map(|s| Stat4Sample {
neq: s.an_eq,
nlt: s.an_lt,
ndlt: s.an_dlt,
sample: encode_record(&s.sample_values),
})
.collect()
}
}
pub(crate) struct LoadedSample {
pub n_lt: Vec<u64>,
pub n_eq: Vec<u64>,
pub n_dlt: Vec<u64>,
pub sample: Vec<Value>,
}
struct Stat4Index {
samples: Vec<LoadedSample>,
n_sample_col: usize,
n_row_est0: u64,
a_avg_eq: Vec<u64>,
}
impl Stat4Index {
fn compute_avg_eq(&mut self, ai_row_est: &[u64], n_key_col: usize) {
let n_sample = self.samples.len();
if n_sample == 0 {
return;
}
let final_idx = n_sample - 1;
let mut n_col = 1usize;
if self.n_sample_col > 1 {
n_col = self.n_sample_col - 1;
if let Some(slot) = self.a_avg_eq.get_mut(n_col) {
*slot = 1;
}
}
for i_col in 0..n_col {
let mut n_sample_i = n_sample;
let n_row: u64;
let n_dist100: i64;
let row_est_next = if i_col < n_key_col {
ai_row_est.get(i_col + 1).copied().unwrap_or(0)
} else {
0
};
if ai_row_est.is_empty() || i_col >= n_key_col || row_est_next == 0 {
n_row = self.samples[final_idx].n_lt[i_col];
n_dist100 = 100 * self.samples[final_idx].n_dlt[i_col] as i64;
n_sample_i -= 1;
} else {
n_row = ai_row_est[0];
n_dist100 = (100 * ai_row_est[0] as i64) / row_est_next as i64;
}
self.n_row_est0 = n_row;
let mut sum_eq: u64 = 0;
let mut n_sum100: i64 = 0;
for i in 0..n_sample_i {
if i == n_sample - 1
|| self.samples[i].n_dlt[i_col] != self.samples[i + 1].n_dlt[i_col]
{
sum_eq += self.samples[i].n_eq[i_col];
n_sum100 += 100;
}
}
let mut avg_eq: u64 = 0;
if n_dist100 > n_sum100 && sum_eq < n_row {
avg_eq = (100 * (n_row - sum_eq)) / (n_dist100 - n_sum100) as u64;
}
if avg_eq == 0 {
avg_eq = 1;
}
if let Some(slot) = self.a_avg_eq.get_mut(i_col) {
*slot = avg_eq;
}
}
}
}
fn record_compare(
sample: &[Value],
rec: &[Value],
n: usize,
colls: &[crate::value::Collation],
descs: &[bool],
) -> core::cmp::Ordering {
for i in 0..n {
let coll = colls.get(i).copied().unwrap_or_default();
let mut ord = crate::value::cmp_values_coll(&sample[i], &rec[i], coll);
if descs.get(i).copied().unwrap_or(false) {
ord = ord.reverse();
}
if ord != core::cmp::Ordering::Equal {
return ord;
}
}
core::cmp::Ordering::Equal
}
fn where_key_stats(
idx: &Stat4Index,
rec: &[Value],
colls: &[crate::value::Collation],
descs: &[bool],
n_field: usize,
round_up: bool,
) -> (u64, u64, usize) {
let a_sample = &idx.samples;
let n_sample = a_sample.len();
let mut i_col = 0usize;
let mut i_min = 0i64;
let mut i_sample = (n_sample * n_field) as i64;
let mut i_lower: u64 = 0;
let mut res;
loop {
let i_test = (i_min + i_sample) / 2;
let i_samp = (i_test / n_field as i64) as usize;
let n = if i_samp > 0 {
let mut nn = (i_test as usize % n_field) + 1;
while nn < n_field {
if a_sample[i_samp - 1].n_lt[nn - 1] != a_sample[i_samp].n_lt[nn - 1] {
break;
}
nn += 1;
}
nn
} else {
i_test as usize + 1
};
res = match record_compare(&a_sample[i_samp].sample, rec, n, colls, descs) {
core::cmp::Ordering::Less => -1i32,
core::cmp::Ordering::Equal => 0,
core::cmp::Ordering::Greater => 1,
};
if res < 0 {
i_lower = a_sample[i_samp].n_lt[n - 1] + a_sample[i_samp].n_eq[n - 1];
i_min = i_test + 1;
} else if res == 0 && n < n_field {
i_lower = a_sample[i_samp].n_lt[n - 1];
i_min = i_test + 1;
res = -1;
} else {
i_sample = i_test;
i_col = n - 1;
}
if res == 0 || i_min >= i_sample {
break;
}
}
let i = (i_sample / n_field as i64) as usize;
if res == 0 {
(a_sample[i].n_lt[i_col], a_sample[i].n_eq[i_col], i)
} else {
let i_upper = if i >= n_sample {
idx.n_row_est0
} else {
a_sample[i].n_lt[i_col]
};
let i_gap = i_upper.saturating_sub(i_lower);
let i_gap = if round_up { (i_gap * 2) / 3 } else { i_gap / 3 };
let a1 = idx.a_avg_eq.get(n_field - 1).copied().unwrap_or(1);
(i_lower + i_gap, a1, i)
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn equal_scan_est(
samples: Vec<LoadedSample>,
n_sample_col: usize,
n_key_col: usize,
ai_row_est: &[u64],
rec: &[Value],
colls: &[crate::value::Collation],
descs: &[bool],
) -> Option<u64> {
if samples.is_empty() {
return None;
}
let n_field = rec.len().min(n_sample_col);
if n_field == 0 {
return None;
}
let n_row0 = ai_row_est.first().copied().unwrap_or(0);
let mut idx = Stat4Index {
samples,
n_sample_col,
n_row_est0: n_row0,
a_avg_eq: alloc::vec![1; n_sample_col],
};
idx.compute_avg_eq(ai_row_est, n_key_col);
if n_field >= n_sample_col {
return Some(1);
}
let (_, a1, _) = where_key_stats(&idx, rec, colls, descs, n_field, false);
Some(a1)
}
pub(crate) struct RangeStat4 {
pub i_lower: u64,
pub i_upper: u64,
pub same_sample: bool,
pub n_row_est0: u64,
pub lower_extracted: bool,
pub upper_extracted: bool,
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn range_scan_est(
samples: Vec<LoadedSample>,
n_sample_col: usize,
n_key_col: usize,
ai_row_est: &[u64],
lower: Option<(Value, bool)>,
upper: Option<(Value, bool)>,
colls: &[crate::value::Collation],
descs: &[bool],
) -> Option<RangeStat4> {
if samples.is_empty() {
return None;
}
if n_sample_col == 0 {
return None;
}
let n_row0 = ai_row_est.first().copied().unwrap_or(0);
let mut idx = Stat4Index {
samples,
n_sample_col,
n_row_est0: n_row0,
a_avg_eq: alloc::vec![1; n_sample_col],
};
idx.compute_avg_eq(ai_row_est, n_key_col);
let mut i_lower: u64 = 0;
let mut i_upper: u64 = idx.n_row_est0;
let mut i_lwr_idx: i64 = -2;
let mut i_upr_idx: i64 = -1;
let mut lower_extracted = false;
let mut upper_extracted = false;
if let Some((v, inclusive)) = lower {
let rec = [v];
let (a0, a1, i) = where_key_stats(&idx, &rec, colls, descs, 1, false);
let i_new = a0 + if !inclusive { a1 } else { 0 };
if i_new > i_lower {
i_lower = i_new;
}
i_lwr_idx = i as i64;
lower_extracted = true;
}
if let Some((v, inclusive)) = upper {
let rec = [v];
let (a0, a1, i) = where_key_stats(&idx, &rec, colls, descs, 1, true);
let i_new = a0 + if inclusive { a1 } else { 0 };
if i_new < i_upper {
i_upper = i_new;
}
i_upr_idx = i as i64;
upper_extracted = true;
}
Some(RangeStat4 {
i_lower,
i_upper,
same_sample: i_lwr_idx == i_upr_idx,
n_row_est0: idx.n_row_est0,
lower_extracted,
upper_extracted,
})
}
pub(crate) fn collect_samples(
entries: &[Stat4Entry],
n_col: usize,
n_col_test: usize,
cmp: impl Fn(&[Value], &[Value], usize) -> core::cmp::Ordering,
) -> Vec<Stat4Sample> {
if entries.is_empty() {
return Vec::new();
}
let mut acc = StatAccum::new(n_col, entries.len() as u64);
let mut prev: Option<&Stat4Entry> = None;
for e in entries {
let i_chng = match prev {
None => 0,
Some(p) => {
let mut c = n_col_test;
for i in 0..n_col_test {
if cmp(&p.sample, &e.sample, i + 1) != core::cmp::Ordering::Equal {
c = i;
break;
}
}
c
}
};
acc.push(i_chng, e.sample.clone());
prev = Some(e);
}
acc.finish()
}