use vibesql_ast::SelectStmt;
use vibesql_catalog::TableSchema;
use vibesql_storage::Database;
use vibesql_types::{DataType, SqlValue};
use crate::SelectExecutor;
fn setup_join_test_data(db: &mut Database) {
let dept_schema = TableSchema::new(
"departments".to_string(),
vec![
vibesql_catalog::ColumnSchema::new("dept_id".to_string(), DataType::Integer, false),
vibesql_catalog::ColumnSchema::new(
"dept_name".to_string(),
DataType::Varchar { max_length: Some(50) },
false,
),
],
);
db.create_table(dept_schema).unwrap();
let emp_schema = TableSchema::new(
"employees".to_string(),
vec![
vibesql_catalog::ColumnSchema::new("emp_id".to_string(), DataType::Integer, false),
vibesql_catalog::ColumnSchema::new(
"emp_name".to_string(),
DataType::Varchar { max_length: Some(50) },
false,
),
vibesql_catalog::ColumnSchema::new("dept_id".to_string(), DataType::Integer, false),
vibesql_catalog::ColumnSchema::new("salary".to_string(), DataType::Integer, false),
],
);
db.create_table(emp_schema).unwrap();
db.insert_row(
"departments",
vibesql_storage::Row::new(vec![
SqlValue::Integer(1),
SqlValue::Varchar(arcstr::ArcStr::from("Engineering")),
]),
)
.unwrap();
db.insert_row(
"departments",
vibesql_storage::Row::new(vec![
SqlValue::Integer(2),
SqlValue::Varchar(arcstr::ArcStr::from("Sales")),
]),
)
.unwrap();
db.insert_row(
"departments",
vibesql_storage::Row::new(vec![
SqlValue::Integer(3),
SqlValue::Varchar(arcstr::ArcStr::from("HR")),
]),
)
.unwrap();
db.insert_row(
"employees",
vibesql_storage::Row::new(vec![
SqlValue::Integer(1),
SqlValue::Varchar(arcstr::ArcStr::from("Alice")),
SqlValue::Integer(1), SqlValue::Integer(95000),
]),
)
.unwrap();
db.insert_row(
"employees",
vibesql_storage::Row::new(vec![
SqlValue::Integer(2),
SqlValue::Varchar(arcstr::ArcStr::from("Bob")),
SqlValue::Integer(1), SqlValue::Integer(87000),
]),
)
.unwrap();
db.insert_row(
"employees",
vibesql_storage::Row::new(vec![
SqlValue::Integer(3),
SqlValue::Varchar(arcstr::ArcStr::from("Charlie")),
SqlValue::Integer(2), SqlValue::Integer(75000),
]),
)
.unwrap();
db.insert_row(
"employees",
vibesql_storage::Row::new(vec![
SqlValue::Integer(4),
SqlValue::Varchar(arcstr::ArcStr::from("Diana")),
SqlValue::Integer(2), SqlValue::Integer(72000),
]),
)
.unwrap();
db.insert_row(
"employees",
vibesql_storage::Row::new(vec![
SqlValue::Integer(5),
SqlValue::Varchar(arcstr::ArcStr::from("Eve")),
SqlValue::Integer(3), SqlValue::Integer(65000),
]),
)
.unwrap();
}
#[test]
fn test_inner_join_with_group_by_count() {
let mut db = Database::new();
setup_join_test_data(&mut db);
let executor = SelectExecutor::new(&db);
let select_stmt =
SelectStmt {
into_table: None,
into_variables: None,
with_clause: None,
set_operation: None,
values: None,
distinct: false,
select_list: vec![
vibesql_ast::SelectItem::Expression {
expr: vibesql_ast::Expression::ColumnRef(
vibesql_ast::ColumnIdentifier::qualified("d", false, "dept_name", false),
),
alias: None,
source_text: None,
},
vibesql_ast::SelectItem::Expression {
expr: vibesql_ast::Expression::Function {
name: vibesql_ast::FunctionIdentifier::new("COUNT"),
args: vec![vibesql_ast::Expression::ColumnRef(
vibesql_ast::ColumnIdentifier::qualified("e", false, "emp_id", false),
)],
character_unit: None,
},
alias: Some("emp_count".to_string()),
source_text: None,
},
],
from: Some(vibesql_ast::FromClause::Join {
left: Box::new(vibesql_ast::FromClause::Table {
index_hint: None,
quoted: false,
name: "departments".to_string(),
alias: Some("d".to_string()),
column_aliases: None,
}),
right: Box::new(vibesql_ast::FromClause::Table {
index_hint: None,
quoted: false,
name: "employees".to_string(),
alias: Some("e".to_string()),
column_aliases: None,
}),
join_type: vibesql_ast::JoinType::Inner,
condition: Some(vibesql_ast::Expression::BinaryOp {
left: Box::new(vibesql_ast::Expression::ColumnRef(
vibesql_ast::ColumnIdentifier::qualified("d", false, "dept_id", false),
)),
op: vibesql_ast::BinaryOperator::Equal,
right: Box::new(vibesql_ast::Expression::ColumnRef(
vibesql_ast::ColumnIdentifier::qualified("e", false, "dept_id", false),
)),
}),
using_columns: None,
natural: false,
alias: None,
}),
where_clause: None,
group_by: Some(vibesql_ast::GroupByClause::Simple(vec![
vibesql_ast::Expression::ColumnRef(vibesql_ast::ColumnIdentifier::qualified(
"d",
false,
"dept_name",
false,
)),
])),
having: None,
window_definitions: None,
order_by: None, limit: None,
offset: None,
};
let result = executor.execute_with_columns(&select_stmt).unwrap();
assert_eq!(result.rows.len(), 3);
assert_eq!(result.columns, vec!["dept_name".to_string(), "emp_count".to_string()]);
let eng_row = result
.rows
.iter()
.find(|r| r.get(0).unwrap() == &SqlValue::Varchar(arcstr::ArcStr::from("Engineering")))
.unwrap();
let sales_row = result
.rows
.iter()
.find(|r| r.get(0).unwrap() == &SqlValue::Varchar(arcstr::ArcStr::from("Sales")))
.unwrap();
let hr_row = result
.rows
.iter()
.find(|r| r.get(0).unwrap() == &SqlValue::Varchar(arcstr::ArcStr::from("HR")))
.unwrap();
assert_eq!(eng_row.get(1).unwrap(), &SqlValue::Integer(2)); assert_eq!(sales_row.get(1).unwrap(), &SqlValue::Integer(2)); assert_eq!(hr_row.get(1).unwrap(), &SqlValue::Integer(1)); }
#[test]
fn test_left_join_with_group_by_avg_salary() {
let mut db = Database::new();
setup_join_test_data(&mut db);
let executor = SelectExecutor::new(&db);
let select_stmt =
SelectStmt {
into_table: None,
into_variables: None,
with_clause: None,
set_operation: None,
values: None,
distinct: false,
select_list: vec![
vibesql_ast::SelectItem::Expression {
expr: vibesql_ast::Expression::ColumnRef(
vibesql_ast::ColumnIdentifier::qualified("d", false, "dept_name", false),
),
alias: None,
source_text: None,
},
vibesql_ast::SelectItem::Expression {
expr: vibesql_ast::Expression::Function {
name: vibesql_ast::FunctionIdentifier::new("AVG"),
args: vec![vibesql_ast::Expression::ColumnRef(
vibesql_ast::ColumnIdentifier::qualified("e", false, "salary", false),
)],
character_unit: None,
},
alias: Some("avg_salary".to_string()),
source_text: None,
},
],
from: Some(vibesql_ast::FromClause::Join {
left: Box::new(vibesql_ast::FromClause::Table {
index_hint: None,
quoted: false,
name: "departments".to_string(),
alias: Some("d".to_string()),
column_aliases: None,
}),
right: Box::new(vibesql_ast::FromClause::Table {
index_hint: None,
quoted: false,
name: "employees".to_string(),
alias: Some("e".to_string()),
column_aliases: None,
}),
join_type: vibesql_ast::JoinType::LeftOuter,
condition: Some(vibesql_ast::Expression::BinaryOp {
left: Box::new(vibesql_ast::Expression::ColumnRef(
vibesql_ast::ColumnIdentifier::qualified("d", false, "dept_id", false),
)),
op: vibesql_ast::BinaryOperator::Equal,
right: Box::new(vibesql_ast::Expression::ColumnRef(
vibesql_ast::ColumnIdentifier::qualified("e", false, "dept_id", false),
)),
}),
using_columns: None,
natural: false,
alias: None,
}),
where_clause: None,
group_by: Some(vibesql_ast::GroupByClause::Simple(vec![
vibesql_ast::Expression::ColumnRef(vibesql_ast::ColumnIdentifier::qualified(
"d",
false,
"dept_name",
false,
)),
])),
having: None,
window_definitions: None,
order_by: None, limit: None,
offset: None,
};
let result = executor.execute_with_columns(&select_stmt).unwrap();
assert_eq!(result.rows.len(), 3);
let engineering_row = result
.rows
.iter()
.find(|r| r.get(0).unwrap() == &SqlValue::Varchar(arcstr::ArcStr::from("Engineering")))
.unwrap();
let sales_row = result
.rows
.iter()
.find(|r| r.get(0).unwrap() == &SqlValue::Varchar(arcstr::ArcStr::from("Sales")))
.unwrap();
let hr_row = result
.rows
.iter()
.find(|r| r.get(0).unwrap() == &SqlValue::Varchar(arcstr::ArcStr::from("HR")))
.unwrap();
assert_eq!(engineering_row.get(1).unwrap(), &SqlValue::Double(91000.0));
assert_eq!(sales_row.get(1).unwrap(), &SqlValue::Double(73500.0));
assert_eq!(hr_row.get(1).unwrap(), &SqlValue::Double(65000.0));
}
#[test]
fn test_join_group_by_with_having() {
let mut db = Database::new();
setup_join_test_data(&mut db);
let executor = SelectExecutor::new(&db);
let select_stmt =
SelectStmt {
into_table: None,
into_variables: None,
with_clause: None,
set_operation: None,
values: None,
distinct: false,
select_list: vec![
vibesql_ast::SelectItem::Expression {
expr: vibesql_ast::Expression::ColumnRef(
vibesql_ast::ColumnIdentifier::qualified("d", false, "dept_name", false),
),
alias: None,
source_text: None,
},
vibesql_ast::SelectItem::Expression {
expr: vibesql_ast::Expression::Function {
name: vibesql_ast::FunctionIdentifier::new("COUNT"),
args: vec![vibesql_ast::Expression::ColumnRef(
vibesql_ast::ColumnIdentifier::qualified("e", false, "emp_id", false),
)],
character_unit: None,
},
alias: Some("emp_count".to_string()),
source_text: None,
},
],
from: Some(vibesql_ast::FromClause::Join {
left: Box::new(vibesql_ast::FromClause::Table {
index_hint: None,
quoted: false,
name: "departments".to_string(),
alias: Some("d".to_string()),
column_aliases: None,
}),
right: Box::new(vibesql_ast::FromClause::Table {
index_hint: None,
quoted: false,
name: "employees".to_string(),
alias: Some("e".to_string()),
column_aliases: None,
}),
join_type: vibesql_ast::JoinType::Inner,
condition: Some(vibesql_ast::Expression::BinaryOp {
left: Box::new(vibesql_ast::Expression::ColumnRef(
vibesql_ast::ColumnIdentifier::qualified("d", false, "dept_id", false),
)),
op: vibesql_ast::BinaryOperator::Equal,
right: Box::new(vibesql_ast::Expression::ColumnRef(
vibesql_ast::ColumnIdentifier::qualified("e", false, "dept_id", false),
)),
}),
using_columns: None,
natural: false,
alias: None,
}),
where_clause: None,
group_by: Some(vibesql_ast::GroupByClause::Simple(vec![
vibesql_ast::Expression::ColumnRef(vibesql_ast::ColumnIdentifier::qualified(
"d",
false,
"dept_name",
false,
)),
])),
having: Some(vibesql_ast::Expression::BinaryOp {
left: Box::new(vibesql_ast::Expression::Function {
name: vibesql_ast::FunctionIdentifier::new("COUNT"),
args: vec![vibesql_ast::Expression::ColumnRef(
vibesql_ast::ColumnIdentifier::qualified("e", false, "emp_id", false),
)],
character_unit: None,
}),
op: vibesql_ast::BinaryOperator::GreaterThanOrEqual,
right: Box::new(vibesql_ast::Expression::Literal(SqlValue::Integer(2))),
}),
window_definitions: None,
order_by: None,
limit: None,
offset: None,
};
let result = executor.execute_with_columns(&select_stmt).unwrap();
assert_eq!(result.rows.len(), 2);
for row in &result.rows {
let count = row.get(1).unwrap();
match count {
SqlValue::Integer(c) => assert!(*c >= 2),
_ => panic!("Expected integer count"),
}
}
}
#[test]
fn test_join_group_by_multiple_aggregates() {
let mut db = Database::new();
setup_join_test_data(&mut db);
let executor = SelectExecutor::new(&db);
let select_stmt =
SelectStmt {
into_table: None,
into_variables: None,
with_clause: None,
set_operation: None,
values: None,
distinct: false,
select_list: vec![
vibesql_ast::SelectItem::Expression {
expr: vibesql_ast::Expression::ColumnRef(
vibesql_ast::ColumnIdentifier::qualified("d", false, "dept_name", false),
),
alias: None,
source_text: None,
},
vibesql_ast::SelectItem::Expression {
expr: vibesql_ast::Expression::Function {
name: vibesql_ast::FunctionIdentifier::new("COUNT"),
args: vec![vibesql_ast::Expression::Wildcard],
character_unit: None,
},
alias: None,
source_text: None,
},
vibesql_ast::SelectItem::Expression {
expr: vibesql_ast::Expression::Function {
name: vibesql_ast::FunctionIdentifier::new("MIN"),
args: vec![vibesql_ast::Expression::ColumnRef(
vibesql_ast::ColumnIdentifier::qualified("e", false, "salary", false),
)],
character_unit: None,
},
alias: None,
source_text: None,
},
vibesql_ast::SelectItem::Expression {
expr: vibesql_ast::Expression::Function {
name: vibesql_ast::FunctionIdentifier::new("MAX"),
args: vec![vibesql_ast::Expression::ColumnRef(
vibesql_ast::ColumnIdentifier::qualified("e", false, "salary", false),
)],
character_unit: None,
},
alias: None,
source_text: None,
},
],
from: Some(vibesql_ast::FromClause::Join {
left: Box::new(vibesql_ast::FromClause::Table {
index_hint: None,
quoted: false,
name: "departments".to_string(),
alias: Some("d".to_string()),
column_aliases: None,
}),
right: Box::new(vibesql_ast::FromClause::Table {
index_hint: None,
quoted: false,
name: "employees".to_string(),
alias: Some("e".to_string()),
column_aliases: None,
}),
join_type: vibesql_ast::JoinType::Inner,
condition: Some(vibesql_ast::Expression::BinaryOp {
left: Box::new(vibesql_ast::Expression::ColumnRef(
vibesql_ast::ColumnIdentifier::qualified("d", false, "dept_id", false),
)),
op: vibesql_ast::BinaryOperator::Equal,
right: Box::new(vibesql_ast::Expression::ColumnRef(
vibesql_ast::ColumnIdentifier::qualified("e", false, "dept_id", false),
)),
}),
using_columns: None,
natural: false,
alias: None,
}),
where_clause: None,
group_by: Some(vibesql_ast::GroupByClause::Simple(vec![
vibesql_ast::Expression::ColumnRef(vibesql_ast::ColumnIdentifier::qualified(
"d",
false,
"dept_name",
false,
)),
])),
having: None,
window_definitions: None,
order_by: None,
limit: None,
offset: None,
};
let result = executor.execute_with_columns(&select_stmt).unwrap();
assert_eq!(result.rows.len(), 3);
let engineering_row = result
.rows
.iter()
.find(|row| row.get(0).unwrap() == &SqlValue::Varchar(arcstr::ArcStr::from("Engineering")))
.unwrap();
assert_eq!(engineering_row.get(1).unwrap(), &SqlValue::Integer(2)); assert_eq!(engineering_row.get(2).unwrap(), &SqlValue::Integer(87000)); assert_eq!(engineering_row.get(3).unwrap(), &SqlValue::Integer(95000)); }