#[derive(Debug, Clone)]
pub struct DimensionTable {
pub table: String,
pub join_on: String,
pub filter: Option<String>,
}
#[derive(Debug, Clone)]
pub struct StarSchema {
pub fact_table: String,
pub dimensions: Vec<DimensionTable>,
pub is_star: bool,
}
#[derive(Debug)]
pub struct StarSchemaOptimizer {
max_dimensions: usize,
}
impl StarSchemaOptimizer {
pub fn new() -> Self {
Self { max_dimensions: 10 }
}
pub fn with_max_dimensions(mut self, max: usize) -> Self {
self.max_dimensions = max;
self
}
pub fn identify(&self, fact_table: &str, dimensions: Vec<DimensionTable>) -> StarSchema {
let is_star = !dimensions.is_empty() && dimensions.len() <= self.max_dimensions;
StarSchema {
fact_table: fact_table.to_string(),
dimensions,
is_star,
}
}
pub fn optimize(&self, schema: &StarSchema) -> String {
if !schema.is_star {
return format!("SELECT * FROM {}", schema.fact_table);
}
let mut sql = format!("SELECT * FROM {}", schema.fact_table);
let filtered: Vec<&DimensionTable> = schema
.dimensions
.iter()
.filter(|d| d.filter.is_some())
.collect();
let unfiltered: Vec<&DimensionTable> = schema
.dimensions
.iter()
.filter(|d| d.filter.is_none())
.collect();
for d in filtered.iter().chain(unfiltered.iter()) {
sql.push_str(&format!(" JOIN {} ON {}", d.table, d.join_on));
}
sql
}
pub fn filtered_dimension_count(&self, schema: &StarSchema) -> usize {
schema
.dimensions
.iter()
.filter(|d| d.filter.is_some())
.count()
}
}
impl Default for StarSchemaOptimizer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn dim(table: &str, on: &str) -> DimensionTable {
DimensionTable {
table: table.into(),
join_on: on.into(),
filter: None,
}
}
fn dim_with_filter(table: &str, on: &str, filter: &str) -> DimensionTable {
DimensionTable {
table: table.into(),
join_on: on.into(),
filter: Some(filter.into()),
}
}
#[test]
fn identify_star_schema() {
let opt = StarSchemaOptimizer::new();
let schema = opt.identify(
"sales",
vec![
dim("dept", "sales.dept_id = dept.id"),
dim("region", "sales.region_id = region.id"),
],
);
assert!(schema.is_star);
assert_eq!(schema.dimensions.len(), 2);
}
#[test]
fn not_star_with_too_many_dimensions() {
let opt = StarSchemaOptimizer::new().with_max_dimensions(2);
let schema = opt.identify("fact", vec![dim("d1", "1"), dim("d2", "2"), dim("d3", "3")]);
assert!(!schema.is_star);
}
#[test]
fn not_star_with_no_dimensions() {
let opt = StarSchemaOptimizer::new();
let schema = opt.identify("fact", vec![]);
assert!(!schema.is_star);
}
#[test]
fn optimize_filters_first() {
let opt = StarSchemaOptimizer::new();
let schema = opt.identify(
"sales",
vec![
dim("dept", "sales.dept_id = dept.id"),
dim_with_filter(
"region",
"sales.region_id = region.id",
"region.name = 'East'",
),
],
);
let sql = opt.optimize(&schema);
let region_pos = sql.find("JOIN region").unwrap();
let dept_pos = sql.find("JOIN dept").unwrap();
assert!(
region_pos < dept_pos,
"filtered dimension should come first"
);
}
#[test]
fn filtered_count() {
let opt = StarSchemaOptimizer::new();
let schema = opt.identify(
"sales",
vec![
dim("d1", "1"),
dim_with_filter("d2", "2", "x"),
dim_with_filter("d3", "3", "y"),
],
);
assert_eq!(opt.filtered_dimension_count(&schema), 2);
}
}