use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use crate::executor::streaming::runtime::ExecutionRuntime;
pub(crate) fn use_columnar_path(runtime: &Option<Arc<ExecutionRuntime>>) -> bool {
let query_override = runtime
.as_ref()
.map(|runtime| runtime.columnar_override())
.unwrap_or_else(QueryColumnarOverride::inherit);
runtime
.as_ref()
.and_then(|runtime| runtime.columnar_policy())
.is_none_or(|policy| policy.should_use_columnar_with(query_override))
}
#[derive(Debug)]
pub struct ColumnarPolicy {
hits: AtomicU64,
misses: AtomicU64,
threshold: f64,
min_samples: u64,
}
impl ColumnarPolicy {
pub fn new(threshold: f64, min_samples: u64) -> Self {
Self {
hits: AtomicU64::new(0),
misses: AtomicU64::new(0),
threshold: threshold.clamp(0.0, 1.0),
min_samples: min_samples.max(1),
}
}
pub fn should_use_columnar(&self) -> bool {
self.should_use_columnar_with(QueryColumnarOverride::inherit())
}
pub fn should_use_columnar_with(&self, query_override: QueryColumnarOverride) -> bool {
if let Some(forced) = query_override.force_columnar {
return forced;
}
let total = self.hits.load(Ordering::Relaxed) + self.misses.load(Ordering::Relaxed);
if total < self.min_samples {
return true;
}
self.hits.load(Ordering::Relaxed) as f64 / total as f64 >= self.threshold
}
pub fn record(&self, hit: bool) {
if hit {
self.record_hit();
} else {
self.record_miss();
}
}
pub fn record_hit(&self) {
self.hits.fetch_add(1, Ordering::Relaxed);
}
pub fn record_miss(&self) {
self.misses.fetch_add(1, Ordering::Relaxed);
}
pub fn merge(&self, hits: u64, misses: u64) {
if hits > 0 {
self.hits.fetch_add(hits, Ordering::Relaxed);
}
if misses > 0 {
self.misses.fetch_add(misses, Ordering::Relaxed);
}
}
pub fn snapshot(&self) -> (u64, u64) {
(
self.hits.load(Ordering::Relaxed),
self.misses.load(Ordering::Relaxed),
)
}
pub fn hit_rate(&self) -> f64 {
let (hits, misses) = self.snapshot();
let total = hits + misses;
if total == 0 {
1.0
} else {
hits as f64 / total as f64
}
}
pub fn threshold(&self) -> f64 {
self.threshold
}
pub fn min_samples(&self) -> u64 {
self.min_samples
}
}
impl Default for ColumnarPolicy {
fn default() -> Self {
Self::new(0.8, 100)
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct QueryColumnarOverride {
pub force_columnar: Option<bool>,
}
impl QueryColumnarOverride {
pub fn inherit() -> Self {
Self {
force_columnar: None,
}
}
pub fn force(enabled: bool) -> Self {
Self {
force_columnar: Some(enabled),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_on_below_min_samples() {
let policy = ColumnarPolicy::new(0.8, 100);
assert!(policy.should_use_columnar());
for _ in 0..50 {
policy.record_miss();
}
assert!(policy.should_use_columnar());
}
#[test]
fn test_flips_to_row_after_enough_fallbacks() {
let policy = ColumnarPolicy::new(0.8, 100);
for _ in 0..15 {
policy.record_hit();
}
for _ in 0..135 {
policy.record_miss();
}
assert!(!policy.should_use_columnar());
assert_eq!(policy.snapshot(), (15, 135));
}
#[test]
fn test_recovers_to_columnar_after_hits() {
let policy = ColumnarPolicy::new(0.8, 100);
for _ in 0..100 {
policy.record_miss();
}
assert!(!policy.should_use_columnar());
for _ in 0..400 {
policy.record_hit();
}
assert!(policy.should_use_columnar());
}
#[test]
fn test_merge_accumulates_deltas() {
let policy = ColumnarPolicy::new(0.8, 100);
policy.merge(60, 40);
policy.merge(60, 40);
assert_eq!(policy.snapshot(), (120, 80));
assert!(policy.hit_rate() > 0.5);
assert!(!policy.should_use_columnar());
for _ in 0..8 {
policy.merge(60, 0);
}
assert!(policy.should_use_columnar());
}
#[test]
fn test_query_override_pins_decision() {
let policy = ColumnarPolicy::new(0.8, 10);
for _ in 0..10 {
policy.record_miss();
}
assert!(!policy.should_use_columnar());
assert!(policy.should_use_columnar_with(QueryColumnarOverride::force(true)));
assert!(!policy.should_use_columnar_with(QueryColumnarOverride::force(false)));
assert!(!policy.should_use_columnar_with(QueryColumnarOverride::inherit()));
}
#[test]
fn test_threshold_boundary() {
let policy = ColumnarPolicy::new(0.5, 100);
for _ in 0..50 {
policy.record_hit();
policy.record_miss();
}
assert!(policy.should_use_columnar());
}
}