use vibesql_ast::AnalyzeStmt;
use vibesql_storage::Database;
use crate::errors::ExecutorError;
use crate::sqlite_schema::is_sqlite_schema_table;
pub struct AnalyzeExecutor;
impl AnalyzeExecutor {
pub fn execute(stmt: &AnalyzeStmt, database: &mut Database) -> Result<String, ExecutorError> {
match &stmt.table_name {
None => {
let table_names = database.list_tables();
let count = table_names.len();
for table_name in &table_names {
Self::analyze_table_and_update_stats(database, table_name);
}
Ok(format!("ANALYZE completed - {} table(s) analyzed", count))
}
Some(table_name) => {
if is_sqlite_schema_table(table_name) {
return Ok("ANALYZE sqlite_master completed - sqlite_stat1 ready for queries"
.to_string());
}
if database.get_table(table_name).is_none() {
return Err(ExecutorError::TableNotFound(table_name.clone()));
}
Self::analyze_table_and_update_stats(database, table_name);
let message = if let Some(cols) = &stmt.columns {
format!(
"ANALYZE completed - table '{}' analyzed ({} columns specified, all columns analyzed)",
table_name,
cols.len()
)
} else {
format!("ANALYZE completed - table '{}' analyzed", table_name)
};
Ok(message)
}
}
}
fn analyze_table_and_update_stats(database: &mut Database, table_name: &str) {
if let Some(table) = database.get_table_mut(table_name) {
table.analyze();
}
if let Some(table) = database.get_table_mut(table_name) {
let stats = table.statistics();
let row_count = stats.row_count;
database.insert_sqlite_stat1(table_name.to_string(), None, row_count.to_string());
}
}
}
#[cfg(test)]
mod tests {
use vibesql_ast::{ColumnDef, CreateTableStmt};
use vibesql_types::{DataType, SqlValue};
use super::*;
use crate::CreateTableExecutor;
fn create_test_table(db: &mut Database, table_name: &str) {
let stmt = CreateTableStmt {
temporary: false,
if_not_exists: false,
table_name: table_name.to_string(),
columns: vec![
ColumnDef {
name: "id".to_string(),
data_type: DataType::Integer,
nullable: false,
constraints: vec![],
default_value: None,
comment: None,
generated_expr: None,
is_exact_integer_type: false,
},
ColumnDef {
name: "name".to_string(),
data_type: DataType::Varchar { max_length: Some(100) },
nullable: true,
constraints: vec![],
default_value: None,
comment: None,
generated_expr: None,
is_exact_integer_type: false,
},
ColumnDef {
name: "age".to_string(),
data_type: DataType::Integer,
nullable: true,
constraints: vec![],
default_value: None,
comment: None,
generated_expr: None,
is_exact_integer_type: false,
},
],
table_constraints: vec![],
table_options: vec![],
quoted: false,
name_source: None,
as_query: None,
without_rowid: false,
};
CreateTableExecutor::execute(&stmt, db).unwrap();
}
fn insert_test_data(db: &mut Database, table_name: &str) {
use vibesql_storage::Row;
let table = db.get_table_mut(table_name).unwrap();
table
.insert(Row::new(vec![
SqlValue::Integer(1),
SqlValue::Varchar(arcstr::ArcStr::from("Alice")),
SqlValue::Integer(30),
]))
.unwrap();
table
.insert(Row::new(vec![
SqlValue::Integer(2),
SqlValue::Varchar(arcstr::ArcStr::from("Bob")),
SqlValue::Integer(25),
]))
.unwrap();
table
.insert(Row::new(vec![
SqlValue::Integer(3),
SqlValue::Varchar(arcstr::ArcStr::from("Charlie")),
SqlValue::Integer(35),
]))
.unwrap();
}
#[test]
fn test_analyze_all_tables() {
let mut db = Database::new();
create_test_table(&mut db, "users");
create_test_table(&mut db, "products");
insert_test_data(&mut db, "users");
insert_test_data(&mut db, "products");
let analyze_stmt = AnalyzeStmt { table_name: None, columns: None };
let result = AnalyzeExecutor::execute(&analyze_stmt, &mut db);
assert!(result.is_ok());
let msg = result.unwrap();
assert!(msg.contains("2 table(s)"));
let users_table = db.get_table_mut("users").unwrap();
let stats = users_table.statistics();
assert_eq!(stats.row_count, 3);
}
#[test]
fn test_analyze_specific_table() {
let mut db = Database::new();
create_test_table(&mut db, "users");
insert_test_data(&mut db, "users");
let analyze_stmt = AnalyzeStmt { table_name: Some("users".to_string()), columns: None };
let result = AnalyzeExecutor::execute(&analyze_stmt, &mut db);
assert!(result.is_ok());
assert!(result.unwrap().contains("'users'"));
let table = db.get_table_mut("users").unwrap();
let stats = table.statistics();
assert_eq!(stats.row_count, 3);
assert!(stats.columns.contains_key("id"));
assert!(stats.columns.contains_key("name"));
assert!(stats.columns.contains_key("age"));
}
#[test]
fn test_analyze_with_column_list() {
let mut db = Database::new();
create_test_table(&mut db, "users");
insert_test_data(&mut db, "users");
let analyze_stmt = AnalyzeStmt {
table_name: Some("users".to_string()),
columns: Some(vec!["id".to_string(), "name".to_string()]),
};
let result = AnalyzeExecutor::execute(&analyze_stmt, &mut db);
assert!(result.is_ok());
let msg = result.unwrap();
assert!(msg.contains("'users'"));
assert!(msg.contains("2 columns specified"));
let table = db.get_table_mut("users").unwrap();
let stats = table.statistics();
assert!(stats.columns.contains_key("id"));
assert!(stats.columns.contains_key("name"));
assert!(stats.columns.contains_key("age")); }
#[test]
fn test_analyze_empty_table() {
let mut db = Database::new();
create_test_table(&mut db, "empty_table");
let analyze_stmt =
AnalyzeStmt { table_name: Some("empty_table".to_string()), columns: None };
let result = AnalyzeExecutor::execute(&analyze_stmt, &mut db);
assert!(result.is_ok());
let table = db.get_table_mut("empty_table").unwrap();
let stats = table.statistics();
assert_eq!(stats.row_count, 0);
}
#[test]
fn test_analyze_nonexistent_table() {
let mut db = Database::new();
let analyze_stmt =
AnalyzeStmt { table_name: Some("nonexistent".to_string()), columns: None };
let result = AnalyzeExecutor::execute(&analyze_stmt, &mut db);
assert!(result.is_err());
assert!(matches!(result, Err(ExecutorError::TableNotFound(_))));
}
#[test]
fn test_analyze_updates_stale_statistics() {
let mut db = Database::new();
create_test_table(&mut db, "users");
insert_test_data(&mut db, "users");
let analyze_stmt = AnalyzeStmt { table_name: Some("users".to_string()), columns: None };
AnalyzeExecutor::execute(&analyze_stmt, &mut db).unwrap();
{
let table = db.get_table_mut("users").unwrap();
let initial_stats = table.statistics();
assert_eq!(initial_stats.row_count, 3);
}
{
use vibesql_storage::Row;
let table = db.get_table_mut("users").unwrap();
table
.insert(Row::new(vec![
SqlValue::Integer(4),
SqlValue::Varchar(arcstr::ArcStr::from("Diana")),
SqlValue::Integer(28),
]))
.unwrap();
}
AnalyzeExecutor::execute(&analyze_stmt, &mut db).unwrap();
{
let table = db.get_table_mut("users").unwrap();
let updated_stats = table.statistics();
assert_eq!(updated_stats.row_count, 4);
}
}
}