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_customer_db() -> Database {
let mut db = Database::new();
db.catalog.set_case_sensitive_identifiers(false);
let mut schema = TableSchema::new(
"customer".to_string(),
vec![
ColumnSchema::new("c_id".to_string(), DataType::Integer, false),
ColumnSchema::new("c_w_id".to_string(), DataType::Integer, false),
ColumnSchema::new("c_d_id".to_string(), DataType::Integer, false),
ColumnSchema::new(
"c_last".to_string(),
DataType::Varchar { max_length: Some(16) },
false,
),
ColumnSchema::new(
"c_first".to_string(),
DataType::Varchar { max_length: Some(16) },
false,
),
ColumnSchema::new(
"c_balance".to_string(),
DataType::Numeric { precision: 12, scale: 2 },
false,
),
],
);
schema.primary_key = Some(vec!["c_w_id".to_string(), "c_d_id".to_string(), "c_id".to_string()]);
db.create_table(schema).unwrap();
let customers = vec![
(1, 1, 1, "SMITH", "Alice", 100.00),
(2, 1, 1, "SMITH", "Bob", 200.00),
(3, 1, 1, "JONES", "Charlie", 300.00),
(4, 1, 2, "SMITH", "Diana", 400.00),
(5, 2, 1, "SMITH", "Eve", 500.00),
(6, 1, 1, "BROWN", "Frank", 600.00),
];
for (c_id, c_w_id, c_d_id, c_last, c_first, c_balance) in customers {
db.insert_row(
"customer",
Row::new(vec![
SqlValue::Integer(c_id),
SqlValue::Integer(c_w_id),
SqlValue::Integer(c_d_id),
SqlValue::Varchar(arcstr::ArcStr::from(c_last)),
SqlValue::Varchar(arcstr::ArcStr::from(c_first)),
SqlValue::Numeric(c_balance),
]),
)
.unwrap();
}
db
}
#[test]
fn test_secondary_index_single_column_lookup() {
let mut db = create_customer_db();
db.create_index(
"idx_customer_last".to_string(),
"customer".to_string(),
false, vec![IndexColumn::Column {
column_name: "c_last".to_string(),
prefix_length: None,
direction: OrderDirection::Asc,
}],
)
.unwrap();
let executor = SelectExecutor::new(&db);
let query = "SELECT c_id, c_first FROM customer WHERE c_last = 'SMITH'";
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(), 4);
let ids: Vec<i64> = result
.iter()
.filter_map(|r| if let SqlValue::Integer(id) = r.values[0] { Some(id) } else { None })
.collect();
assert!(ids.contains(&1));
assert!(ids.contains(&2));
assert!(ids.contains(&4));
assert!(ids.contains(&5));
} else {
panic!("Expected SELECT statement");
}
}
#[test]
fn test_secondary_index_composite_lookup() {
let mut db = create_customer_db();
db.create_index(
"idx_customer_name".to_string(),
"customer".to_string(),
false, vec![
IndexColumn::Column {
column_name: "c_w_id".to_string(),
prefix_length: None,
direction: OrderDirection::Asc,
},
IndexColumn::Column {
column_name: "c_d_id".to_string(),
prefix_length: None,
direction: OrderDirection::Asc,
},
IndexColumn::Column {
column_name: "c_last".to_string(),
prefix_length: None,
direction: OrderDirection::Asc,
},
],
)
.unwrap();
let executor = SelectExecutor::new(&db);
let query = "SELECT c_id, c_first, c_balance FROM customer WHERE c_w_id = 1 AND c_d_id = 1 AND c_last = 'SMITH'";
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()
.filter_map(|r| if let SqlValue::Integer(id) = r.values[0] { Some(id) } else { None })
.collect();
assert!(ids.contains(&1));
assert!(ids.contains(&2));
} else {
panic!("Expected SELECT statement");
}
}
#[test]
fn test_secondary_index_with_order_by() {
let mut db = create_customer_db();
db.create_index(
"idx_customer_name".to_string(),
"customer".to_string(),
false,
vec![
IndexColumn::Column {
column_name: "c_w_id".to_string(),
prefix_length: None,
direction: OrderDirection::Asc,
},
IndexColumn::Column {
column_name: "c_d_id".to_string(),
prefix_length: None,
direction: OrderDirection::Asc,
},
IndexColumn::Column {
column_name: "c_last".to_string(),
prefix_length: None,
direction: OrderDirection::Asc,
},
],
)
.unwrap();
let executor = SelectExecutor::new(&db);
let query = "SELECT c_id, c_first, c_balance FROM customer WHERE c_w_id = 1 AND c_d_id = 1 AND c_last = 'SMITH' ORDER BY c_first";
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);
assert_eq!(result[0].values[1], SqlValue::Varchar(arcstr::ArcStr::from("Alice")));
assert_eq!(result[1].values[1], SqlValue::Varchar(arcstr::ArcStr::from("Bob")));
} else {
panic!("Expected SELECT statement");
}
}
#[test]
fn test_secondary_index_select_star() {
let mut db = create_customer_db();
db.create_index(
"idx_customer_last".to_string(),
"customer".to_string(),
false,
vec![IndexColumn::Column {
column_name: "c_last".to_string(),
prefix_length: None,
direction: OrderDirection::Asc,
}],
)
.unwrap();
let executor = SelectExecutor::new(&db);
let query = "SELECT * FROM customer WHERE c_last = 'BROWN'";
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(6)); assert_eq!(result[0].values[3], SqlValue::Varchar(arcstr::ArcStr::from("BROWN"))); assert_eq!(result[0].values[4], SqlValue::Varchar(arcstr::ArcStr::from("Frank")));
} else {
panic!("Expected SELECT statement");
}
}
#[test]
fn test_secondary_index_no_match() {
let mut db = create_customer_db();
db.create_index(
"idx_customer_last".to_string(),
"customer".to_string(),
false,
vec![IndexColumn::Column {
column_name: "c_last".to_string(),
prefix_length: None,
direction: OrderDirection::Asc,
}],
)
.unwrap();
let executor = SelectExecutor::new(&db);
let query = "SELECT c_id FROM customer WHERE c_last = 'NOTEXIST'";
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(), 0);
} else {
panic!("Expected SELECT statement");
}
}
#[test]
fn test_secondary_index_with_limit() {
let mut db = create_customer_db();
db.create_index(
"idx_customer_last".to_string(),
"customer".to_string(),
false,
vec![IndexColumn::Column {
column_name: "c_last".to_string(),
prefix_length: None,
direction: OrderDirection::Asc,
}],
)
.unwrap();
let executor = SelectExecutor::new(&db);
let query =
"SELECT c_id, c_first FROM customer WHERE c_last = 'SMITH' ORDER BY c_first LIMIT 2";
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_secondary_index_residual_predicate_filter() {
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("col0".to_string(), DataType::Integer, false),
ColumnSchema::new("col3".to_string(), DataType::Integer, false),
],
);
db.create_table(schema).unwrap();
db.create_index(
"idx_tab1_3".to_string(),
"tab1".to_string(),
false,
vec![IndexColumn::Column {
column_name: "col3".to_string(),
prefix_length: None,
direction: OrderDirection::Asc,
}],
)
.unwrap();
db.insert_row(
"tab1",
Row::new(vec![SqlValue::Integer(881), SqlValue::Integer(20123), SqlValue::Integer(81814)]),
)
.unwrap();
let executor = SelectExecutor::new(&db);
let query = "SELECT pk, col0, col3 FROM tab1 WHERE col3 = 81814 AND col0 < 1013";
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(), 0, "Residual predicate col0 < 1013 should filter out the row");
} else {
panic!("Expected SELECT statement");
}
}
#[test]
fn test_secondary_index_residual_predicate_match() {
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("col0".to_string(), DataType::Integer, false),
ColumnSchema::new("col3".to_string(), DataType::Integer, false),
],
);
db.create_table(schema).unwrap();
db.create_index(
"idx_tab1_3".to_string(),
"tab1".to_string(),
false,
vec![IndexColumn::Column {
column_name: "col3".to_string(),
prefix_length: None,
direction: OrderDirection::Asc,
}],
)
.unwrap();
db.insert_row(
"tab1",
Row::new(vec![SqlValue::Integer(881), SqlValue::Integer(20123), SqlValue::Integer(81814)]),
)
.unwrap();
db.insert_row(
"tab1",
Row::new(vec![SqlValue::Integer(882), SqlValue::Integer(500), SqlValue::Integer(81814)]),
)
.unwrap();
let executor = SelectExecutor::new(&db);
let query = "SELECT pk, col0, col3 FROM tab1 WHERE col3 = 81814 AND col0 < 1013";
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, "Should return only the row matching residual predicate");
assert_eq!(result[0].values[0], SqlValue::Integer(882), "Should return pk=882");
assert_eq!(result[0].values[1], SqlValue::Integer(500), "Should return col0=500");
} else {
panic!("Expected SELECT statement");
}
}