#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum ReadSource {
#[default]
Source,
Auto,
}
impl ReadSource {
pub fn as_str(&self) -> &'static str {
match self {
Self::Source => "source",
Self::Auto => "auto",
}
}
}
impl std::str::FromStr for ReadSource {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"source" => Ok(Self::Source),
"auto" => Ok(Self::Auto),
other => Err(format!(
"unknown read_source: '{other}' (use 'source' or 'auto')"
)),
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum MaterializedConsistency {
#[default]
Eventual,
Strong,
}
impl std::str::FromStr for MaterializedConsistency {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"eventual" => Ok(Self::Eventual),
"strong" => Ok(Self::Strong),
other => Err(format!(
"unknown materialized_consistency: '{other}' (use 'eventual' or 'strong')"
)),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct HtapSession {
pub read_source: ReadSource,
pub materialized_consistency: MaterializedConsistency,
}
impl HtapSession {
pub fn should_use_materialized(&self, is_analytical: bool) -> bool {
self.read_source == ReadSource::Auto && is_analytical
}
pub fn requires_strong_consistency(&self) -> bool {
self.materialized_consistency == MaterializedConsistency::Strong
}
}
pub fn is_analytical_query(sql: &str) -> bool {
let upper = sql.to_uppercase();
upper.contains("GROUP BY")
|| upper.contains("SUM(")
|| upper.contains("AVG(")
|| upper.contains("COUNT(")
|| upper.contains("MIN(")
|| upper.contains("MAX(")
|| (upper.contains("SELECT") && !upper.contains("WHERE") && upper.contains("FROM"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_session() {
let session = HtapSession::default();
assert_eq!(session.read_source, ReadSource::Source);
assert!(!session.should_use_materialized(true));
assert!(!session.should_use_materialized(false));
}
#[test]
fn auto_routing() {
let session = HtapSession {
read_source: ReadSource::Auto,
materialized_consistency: MaterializedConsistency::Eventual,
};
assert!(session.should_use_materialized(true));
assert!(!session.should_use_materialized(false));
}
#[test]
fn strong_consistency() {
let session = HtapSession {
read_source: ReadSource::Auto,
materialized_consistency: MaterializedConsistency::Strong,
};
assert!(session.requires_strong_consistency());
}
#[test]
fn analytical_detection() {
assert!(is_analytical_query(
"SELECT SUM(balance) FROM customers GROUP BY status"
));
assert!(is_analytical_query("SELECT COUNT(*) FROM orders"));
assert!(is_analytical_query("SELECT AVG(score) FROM metrics"));
assert!(!is_analytical_query(
"SELECT * FROM customers WHERE id = 42"
));
assert!(!is_analytical_query(
"INSERT INTO customers VALUES (1, 'a')"
));
}
#[test]
fn read_source_parse() {
assert_eq!("source".parse::<ReadSource>().unwrap(), ReadSource::Source);
assert_eq!("auto".parse::<ReadSource>().unwrap(), ReadSource::Auto);
assert!("bogus".parse::<ReadSource>().is_err());
}
}