use vibesql_ast::{IndexColumn, OrderDirection};
use vibesql_catalog::{ColumnSchema, TableSchema};
use vibesql_parser::Parser;
use vibesql_storage::{Database, Row};
use vibesql_types::{DataType, SqlValue};
use crate::select::SelectExecutor;
fn create_test_db() -> Database {
let mut db = Database::new();
db.catalog.set_case_sensitive_identifiers(false);
let users_schema = TableSchema::new(
"users".to_string(),
vec![
ColumnSchema::new("id".to_string(), DataType::Integer, false),
ColumnSchema::new(
"email".to_string(),
DataType::Varchar { max_length: Some(100) },
false,
),
ColumnSchema::new("age".to_string(), DataType::Integer, false),
ColumnSchema::new(
"city".to_string(),
DataType::Varchar { max_length: Some(50) },
false,
),
],
);
db.create_table(users_schema).unwrap();
db.insert_row(
"users",
Row::new(vec![
SqlValue::Integer(1),
SqlValue::Varchar(arcstr::ArcStr::from("alice@example.com")),
SqlValue::Integer(25),
SqlValue::Varchar(arcstr::ArcStr::from("Boston")),
]),
)
.unwrap();
db.insert_row(
"users",
Row::new(vec![
SqlValue::Integer(2),
SqlValue::Varchar(arcstr::ArcStr::from("bob@example.com")),
SqlValue::Integer(30),
SqlValue::Varchar(arcstr::ArcStr::from("New York")),
]),
)
.unwrap();
db.insert_row(
"users",
Row::new(vec![
SqlValue::Integer(3),
SqlValue::Varchar(arcstr::ArcStr::from("charlie@example.com")),
SqlValue::Integer(25),
SqlValue::Varchar(arcstr::ArcStr::from("Boston")),
]),
)
.unwrap();
db.insert_row(
"users",
Row::new(vec![
SqlValue::Integer(4),
SqlValue::Varchar(arcstr::ArcStr::from("diana@example.com")),
SqlValue::Integer(35),
SqlValue::Varchar(arcstr::ArcStr::from("Chicago")),
]),
)
.unwrap();
db
}
#[test]
fn test_index_scan_with_email_index() {
let mut db = create_test_db();
db.create_index(
"idx_users_email".to_string(),
"users".to_string(),
false, vec![IndexColumn::Column {
column_name: "email".to_string(),
prefix_length: None,
direction: OrderDirection::Asc,
}],
)
.unwrap();
let executor = SelectExecutor::new(&db);
let query = "SELECT * FROM users WHERE email = 'alice@example.com'";
let stmt = Parser::parse_sql(query).unwrap();
if let vibesql_ast::Statement::Select(select_stmt) = stmt {
let result = executor.execute(&select_stmt).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0].values[0], SqlValue::Integer(1));
assert_eq!(
result[0].values[1],
SqlValue::Varchar(arcstr::ArcStr::from("alice@example.com"))
);
} else {
panic!("Expected SELECT statement");
}
}
#[test]
fn test_index_scan_with_age_index() {
let mut db = create_test_db();
db.create_index(
"idx_users_age".to_string(),
"users".to_string(),
false,
vec![IndexColumn::Column {
column_name: "age".to_string(),
prefix_length: None,
direction: OrderDirection::Asc,
}],
)
.unwrap();
let executor = SelectExecutor::new(&db);
let query = "SELECT id, email FROM users WHERE age = 25";
let stmt = Parser::parse_sql(query).unwrap();
if let vibesql_ast::Statement::Select(select_stmt) = stmt {
let result = executor.execute(&select_stmt).unwrap();
assert_eq!(result.len(), 2);
let ids: Vec<i64> = result
.iter()
.map(|row| match &row.values[0] {
SqlValue::Integer(id) => *id,
_ => panic!("Expected integer ID"),
})
.collect();
assert!(ids.contains(&1)); assert!(ids.contains(&3)); } else {
panic!("Expected SELECT statement");
}
}
#[test]
fn test_table_scan_without_index() {
let db = create_test_db();
let executor = SelectExecutor::new(&db);
let query = "SELECT * FROM users WHERE city = 'Boston'";
let stmt = Parser::parse_sql(query).unwrap();
if let vibesql_ast::Statement::Select(select_stmt) = stmt {
let result = executor.execute(&select_stmt).unwrap();
assert_eq!(result.len(), 2); } else {
panic!("Expected SELECT statement");
}
}
#[test]
fn test_index_scan_with_comparison_operator() {
let mut db = create_test_db();
db.create_index(
"idx_users_age".to_string(),
"users".to_string(),
false,
vec![IndexColumn::Column {
column_name: "age".to_string(),
prefix_length: None,
direction: OrderDirection::Asc,
}],
)
.unwrap();
let executor = SelectExecutor::new(&db);
let query = "SELECT id FROM users WHERE age > 28";
let stmt = Parser::parse_sql(query).unwrap();
if let vibesql_ast::Statement::Select(select_stmt) = stmt {
let result = executor.execute(&select_stmt).unwrap();
assert_eq!(result.len(), 2);
let ids: Vec<i64> = result
.iter()
.map(|row| match &row.values[0] {
SqlValue::Integer(id) => *id,
_ => panic!("Expected integer ID"),
})
.collect();
assert!(ids.contains(&2)); assert!(ids.contains(&4)); } else {
panic!("Expected SELECT statement");
}
}
#[test]
fn test_index_scan_with_and_condition() {
let mut db = create_test_db();
db.create_index(
"idx_users_age".to_string(),
"users".to_string(),
false,
vec![IndexColumn::Column {
column_name: "age".to_string(),
prefix_length: None,
direction: OrderDirection::Asc,
}],
)
.unwrap();
let executor = SelectExecutor::new(&db);
let query = "SELECT * FROM users WHERE age = 25 AND city = 'Boston'";
let stmt = Parser::parse_sql(query).unwrap();
if let vibesql_ast::Statement::Select(select_stmt) = stmt {
let result = executor.execute(&select_stmt).unwrap();
assert_eq!(result.len(), 2);
} else {
panic!("Expected SELECT statement");
}
}
#[test]
fn test_unique_index_enforcement() {
let mut db = create_test_db();
let result = db.create_index(
"idx_users_email_unique".to_string(),
"users".to_string(),
true, vec![IndexColumn::Column {
column_name: "email".to_string(),
prefix_length: None,
direction: OrderDirection::Asc,
}],
);
assert!(result.is_ok());
let duplicate_result = db.insert_row(
"users",
Row::new(vec![
SqlValue::Integer(5),
SqlValue::Varchar(arcstr::ArcStr::from("alice@example.com")), SqlValue::Integer(40),
SqlValue::Varchar(arcstr::ArcStr::from("Seattle")),
]),
);
assert!(duplicate_result.is_err(), "Unique indexes should prevent duplicate values");
}
#[test]
fn test_index_scan_where_order_by_no_double_filtering() {
let mut db = Database::new();
db.catalog.set_case_sensitive_identifiers(false);
let schema = TableSchema::new(
"tab1".to_string(),
vec![
ColumnSchema::new("pk".to_string(), DataType::Integer, false),
ColumnSchema::new("col1".to_string(), DataType::Real, false),
ColumnSchema::new("col2".to_string(), DataType::Real, false),
ColumnSchema::new("col3".to_string(), DataType::Real, false),
],
);
db.create_table(schema).unwrap();
db.create_index(
"idx_col3".to_string(),
"tab1".to_string(),
false,
vec![IndexColumn::Column {
column_name: "col3".to_string(),
prefix_length: None,
direction: OrderDirection::Asc,
}],
)
.unwrap();
let test_data = vec![
(0, 846.0),
(1, 562.0),
(2, 652.0),
(3, 51.0),
(4, 256.0),
(5, 957.0),
(6, 656.0),
(7, 294.0),
(8, 878.0),
(9, 105.0),
];
for (pk, col3_val) in test_data {
db.insert_row(
"tab1",
Row::new(vec![
SqlValue::Integer(pk),
SqlValue::Real(pk as f64 * 10.0),
SqlValue::Real(pk as f64 * 20.0),
SqlValue::Real(col3_val as f64),
]),
)
.unwrap();
}
let executor = SelectExecutor::new(&db);
let query = "SELECT pk FROM tab1 WHERE col3 > 221.0 ORDER BY pk DESC";
let stmt = Parser::parse_sql(query).unwrap();
if let vibesql_ast::Statement::Select(select_stmt) = stmt {
let result = executor.execute(&select_stmt).unwrap();
assert_eq!(result.len(), 8, "Should return 8 rows where col3 > 221");
let pks: Vec<i64> = result
.iter()
.map(|row| match &row.values[0] {
SqlValue::Integer(pk) => *pk,
_ => panic!("Expected integer PK"),
})
.collect();
assert_eq!(pks, vec![8, 7, 6, 5, 4, 2, 1, 0], "PKs should be in DESC order");
assert!(
pks.contains(&2),
"Row with pk=2 (col3=652.0) must be present - was lost in double filtering bug"
);
} else {
panic!("Expected SELECT statement");
}
}