use std::collections::HashSet;
use vibesql_ast::IndexColumn;
use vibesql_catalog::TableSchema;
use vibesql_storage::Database;
use vibesql_types::SqlValue;
use crate::{errors::ExecutorError, evaluator::ExpressionEvaluator};
pub fn create_expression_index(
database: &mut Database,
table_name: &str,
index_name: &str,
table_schema: &TableSchema,
columns: &[IndexColumn],
unique: bool,
where_clause: Option<&vibesql_ast::Expression>,
) -> Result<(), ExecutorError> {
let table = database
.get_table(table_name)
.ok_or_else(|| ExecutorError::TableNotFound(table_name.to_string()))?;
let evaluator = ExpressionEvaluator::new(table_schema)
.with_schema_context(crate::evaluator::SchemaExprContext::Index);
let mut keys: Vec<(Vec<SqlValue>, usize)> = Vec::new();
let mut unique_keys: HashSet<Vec<SqlValue>> = HashSet::new();
for (row_idx, row) in table.scan_live() {
if let Some(predicate) = where_clause {
let in_index = match evaluator.eval(predicate, row) {
Ok(v) => crate::partial_index_maintenance::is_predicate_truthy(&v),
Err(e) if e.is_non_deterministic_use() => return Err(e),
Err(_) => false,
};
if !in_index {
continue;
}
}
let mut key_values: Vec<SqlValue> = Vec::new();
for col in columns {
let value = if let Some(expr) = col.get_expression() {
evaluator.eval(expr, row)?
} else if let Some(col_name) = col.column_name() {
let col_idx = table_schema.get_column_index(col_name).ok_or_else(|| {
ExecutorError::ColumnNotFound {
column_name: col_name.to_string(),
table_name: table_name.to_string(),
searched_tables: vec![table_name.to_string()],
available_columns: table_schema
.columns
.iter()
.map(|c| c.name.clone())
.collect(),
}
})?;
row.values[col_idx].clone()
} else {
return Err(ExecutorError::InvalidIndexDefinition(
"Index column must be either a column name or an expression".to_string(),
));
};
key_values.push(value);
}
if unique {
let has_null = key_values.iter().any(|v| matches!(v, SqlValue::Null));
if !has_null {
if unique_keys.contains(&key_values) {
return Err(ExecutorError::ConstraintViolation(format!(
"UNIQUE constraint failed: duplicate key in expression index '{}'",
index_name
)));
}
unique_keys.insert(key_values.clone());
}
}
keys.push((key_values, row_idx));
}
database.create_index_with_keys(
index_name.to_string(),
table_name.to_string(),
unique,
columns.to_vec(),
keys,
)?;
Ok(())
}