use std::collections::HashSet;
use vibesql_ast::{CreateIndexStmt, IndexColumn};
use vibesql_catalog::TableSchema;
use vibesql_storage::Database;
use super::expression_index::create_expression_index;
use crate::{
errors::ExecutorError, evaluator::ExpressionEvaluator,
partial_index_maintenance::is_predicate_truthy,
};
pub fn create_btree_index(
database: &mut Database,
stmt: &CreateIndexStmt,
table_name: &str,
qualified_table_name: &str,
table_schema: &TableSchema,
unique: bool,
) -> Result<String, ExecutorError> {
let index_name = &stmt.index_name;
let has_expression = stmt.columns.iter().any(|col| col.is_expression());
let column_indices: Vec<u32> = stmt
.columns
.iter()
.map(|col| {
if let Some(name) = col.column_name() {
table_schema.get_column_index(name).map(|idx| idx as u32).unwrap_or(0xFFFFFFFF)
} else {
0xFFFFFFFF }
})
.collect();
let catalog_columns = convert_to_catalog_columns(&stmt.columns);
let index_metadata = vibesql_catalog::IndexMetadata::new(
index_name.clone(),
table_name.to_string(),
vibesql_catalog::IndexType::BTree,
catalog_columns,
unique,
)
.with_schema(super::schema_of_qualified(qualified_table_name))
.with_where_clause(stmt.where_clause.as_ref().map(|expr| (**expr).clone()));
database.catalog.add_index(index_metadata)?;
let build_result = build_btree_index_body(
database,
stmt,
table_name,
index_name,
table_schema,
unique,
has_expression,
);
if let Err(e) = build_result {
let _ = database.catalog.drop_index(qualified_table_name, index_name);
return Err(e);
}
database.emit_wal_create_index(
index_name_to_id(index_name),
index_name,
qualified_table_name,
column_indices,
unique,
);
Ok(format!("Index '{}' created successfully on table '{}'", index_name, qualified_table_name))
}
fn build_btree_index_body(
database: &mut Database,
stmt: &CreateIndexStmt,
table_name: &str,
index_name: &str,
table_schema: &TableSchema,
unique: bool,
has_expression: bool,
) -> Result<(), ExecutorError> {
if has_expression {
create_expression_index(
database,
table_name,
index_name,
table_schema,
&stmt.columns,
unique,
stmt.where_clause.as_deref(),
)?;
} else if let Some(where_expr) = stmt.where_clause.as_deref() {
let table_rows: Vec<(usize, vibesql_storage::Row)> = match database.get_table(table_name) {
Some(table) => table.scan_live().map(|(idx, row)| (idx, row.clone())).collect(),
None => Vec::new(),
};
let evaluator = ExpressionEvaluator::new(table_schema)
.with_schema_context(crate::evaluator::SchemaExprContext::Index);
let mut included: HashSet<usize> = HashSet::with_capacity(table_rows.len());
for (row_idx, row) in table_rows.iter().map(|(idx, row)| (*idx, row)) {
match evaluator.eval(where_expr, row) {
Ok(v) if is_predicate_truthy(&v) => {
included.insert(row_idx);
}
Ok(_) => {}
Err(e) if e.is_non_deterministic_use() => return Err(e),
Err(e) => {
log::warn!(
"Failed to evaluate partial-index '{}' predicate on row {}: {:?}; \
treating row as not-in-index",
index_name,
row_idx,
e
);
}
}
}
database.create_index_partial(
index_name.to_string(),
table_name.to_string(),
unique,
stmt.columns.clone(),
Box::new(where_expr.clone()),
&included,
)?;
} else {
database.create_index(
index_name.to_string(),
table_name.to_string(),
unique,
stmt.columns.clone(),
)?;
}
Ok(())
}
fn convert_to_catalog_columns(columns: &[IndexColumn]) -> Vec<vibesql_catalog::IndexedColumn> {
columns
.iter()
.map(|col| {
let order = match col.direction() {
vibesql_ast::OrderDirection::Asc => vibesql_catalog::SortOrder::Ascending,
vibesql_ast::OrderDirection::Desc => vibesql_catalog::SortOrder::Descending,
};
if let Some(expr) = col.get_expression() {
vibesql_catalog::IndexedColumn::new_expression(expr.clone(), order)
} else if let Some(prefix_len) = col.prefix_length() {
vibesql_catalog::IndexedColumn::new_column_with_prefix(
col.column_name().unwrap().to_string(),
order,
prefix_len,
)
} else {
vibesql_catalog::IndexedColumn::new_column(
col.column_name().unwrap().to_string(),
order,
)
}
})
.collect()
}
pub fn index_name_to_id(name: &str) -> u32 {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
name.hash(&mut hasher);
hasher.finish() as u32
}