use fraiseql_core::compiler::{
fact_table::{Additivity, FactTableMetadata},
time_reduction::Reduction,
};
use fraiseql_db::postgres::IndexInfo;
use super::pagination_index_advice::keys_match;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SeekAdvice {
pub measures: Vec<String>,
pub key: Vec<String>,
pub ddl: String,
}
fn seeks(additivity: &Additivity) -> bool {
Reduction::of(additivity).is_some_and(Reduction::carries_forward)
}
#[must_use]
pub fn advise(fact: &FactTableMetadata, relation: &str, indexes: &[IndexInfo]) -> Vec<SeekAdvice> {
let mut advice: Vec<SeekAdvice> = Vec::new();
for measure in &fact.measures {
let (true, Additivity::SemiAdditive { over, entity, .. }) =
(seeks(&measure.additivity), &measure.additivity)
else {
continue;
};
let key: Vec<String> = entity.iter().chain(std::iter::once(over)).cloned().collect();
let served = indexes.iter().any(|ix| {
ix.keys.len() >= key.len() && key.iter().zip(&ix.keys).all(|(k, i)| keys_match(k, i))
});
if served {
continue;
}
if let Some(found) = advice.iter_mut().find(|a| a.key == key) {
found.measures.push(measure.name.clone());
} else {
advice.push(SeekAdvice {
measures: vec![measure.name.clone()],
ddl: format!("CREATE INDEX ON {relation} ({});", key.join(", ")),
key,
});
}
}
advice
}
#[must_use]
pub fn has_carried_forward_measure(fact: &FactTableMetadata) -> bool {
fact.measures.iter().any(|m| seeks(&m.additivity))
}