use crate::EngineError;
use crate::eval;
use alloc::string::String;
use alloc::vec::Vec;
use spg_storage::{Table, Value};
pub(crate) struct ExprKeyPlan {
exprs: Vec<Option<spg_sql::ast::Expr>>,
collations: Vec<Option<alloc::string::String>>,
key_columns: Vec<usize>,
}
impl ExprKeyPlan {
pub(crate) fn for_table(table: &Table) -> Result<Option<Self>, EngineError> {
let collations: Vec<Option<alloc::string::String>> = table
.indices()
.iter()
.map(|i| table.index_collation(i).map(alloc::string::String::from))
.collect();
if !table.indices().iter().any(|i| i.expression.is_some())
&& collations.iter().all(Option::is_none)
{
return Ok(None);
}
let mut exprs = Vec::with_capacity(table.indices().len());
for idx in table.indices() {
let parsed = match &idx.expression {
Some(src) => Some(spg_sql::parser::parse_expression(src).map_err(|e| {
EngineError::Unsupported(alloc::format!(
"index {:?} expression {src:?} failed to re-parse: {e:?}",
idx.name
))
})?),
None => None,
};
exprs.push(parsed);
}
let key_columns: Vec<usize> = table.indices().iter().map(|i| i.column_position).collect();
Ok(Some(Self {
exprs,
collations,
key_columns,
}))
}
pub(crate) fn keys_for(
&self,
values: &[Value<'static>],
ctx: &eval::EvalContext<'_>,
) -> Result<Vec<Option<Value<'static>>>, EngineError> {
let row = spg_storage::Row {
values: values.to_vec(),
};
let mut out = Vec::with_capacity(self.exprs.len());
for (slot, expr) in self.exprs.iter().enumerate() {
if let Some(Some(coll)) = self.collations.get(slot) {
let pos = self.key_columns[slot];
out.push(
crate::collate::Collated::resolve(coll)
.and_then(|c| collated_key(&c, values.get(pos))),
);
continue;
}
out.push(match expr {
Some(e) => {
let v = eval::eval_expr(e, &row, ctx)
.map_err(|e| EngineError::Unsupported(alloc::format!("{e:?}")))?;
(!v.is_null()).then_some(v)
}
None => None,
});
}
Ok(out)
}
}
fn collated_key(
c: &crate::collate::Collated,
v: Option<&Value<'static>>,
) -> Option<Value<'static>> {
let text = match v? {
Value::Text(t) => t.as_ref(),
Value::BpChar(t) => t.as_ref(),
_ => return None,
};
c.sort_key_of(text)
.map(|k| Value::Bytes(alloc::borrow::Cow::Owned(k)))
}
pub(crate) fn refresh(table: &mut Table) -> Result<(), EngineError> {
for (name, pos, coll) in table.stale_collated_indices() {
let Some(resolved) = crate::collate::Collated::resolve(&coll) else {
continue;
};
let mut keys: Vec<Option<Value<'static>>> = Vec::with_capacity(table.stored_row_count());
for i in 0..table.stored_row_count() {
let Some(values) = table.row_values_at(i) else {
return Ok(());
};
keys.push(collated_key(&resolved, values.get(pos)));
}
table
.rebuild_expression_index(&name, &keys)
.map_err(EngineError::Storage)?;
}
let stale = table.stale_expression_indices();
if stale.is_empty() {
return Ok(());
}
let schema = table.schema().clone();
let ctx = eval::EvalContext::new(&schema.columns, None);
for (name, src) in stale {
let expr = spg_sql::parser::parse_expression(&src).map_err(|e| {
EngineError::Unsupported(alloc::format!(
"index {name:?} expression {src:?} failed to re-parse: {e:?}"
))
})?;
let mut keys: Vec<Option<Value<'static>>> = Vec::with_capacity(table.stored_row_count());
for i in 0..table.stored_row_count() {
let Some(values) = table.row_values_at(i) else {
return Ok(());
};
let row = spg_storage::Row {
values: values.to_vec(),
};
let v = eval::eval_expr(&expr, &row, &ctx)
.map_err(|e| EngineError::Unsupported(alloc::format!("{e:?}")))?;
keys.push((!v.is_null()).then_some(v));
}
table
.rebuild_expression_index(&name, &keys)
.map_err(EngineError::Storage)?;
}
Ok(())
}
pub(crate) fn index_for_expression(table: &Table, expr: &spg_sql::ast::Expr) -> Option<String> {
let wanted = alloc::format!("{expr}");
table
.indices()
.iter()
.find(|i| {
i.expression.as_deref() == Some(wanted.as_str())
&& table.expr_index_is_complete(&i.name)
})
.map(|i| i.name.clone())
}
pub(crate) fn refresh_named(
engine_catalog: &mut spg_storage::Catalog,
name: &str,
) -> Result<(), EngineError> {
let Some(table) = engine_catalog.get_mut(name) else {
return Ok(());
};
if table.stale_expression_indices().is_empty() && table.stale_collated_indices().is_empty() {
return Ok(());
}
refresh(table)
}
pub(crate) fn rebuild_all(cat: &mut spg_storage::Catalog) {
let names: Vec<alloc::string::String> = cat
.table_names()
.into_iter()
.filter(|n| {
cat.get(n).is_some_and(|t| {
!t.stale_expression_indices().is_empty() || !t.stale_collated_indices().is_empty()
})
})
.collect();
for name in names {
if let Some(table) = cat.get_mut(&name) {
let _ = refresh(table);
}
}
}