use vibesql_catalog::{ColumnSchema, TableSchema};
use vibesql_storage::Row;
use vibesql_types::{DataType, SqlValue};
use super::{
build::*,
inner::{hash_join_inner, hash_join_inner_multi},
outer::hash_join_left_outer,
FromResult,
};
use crate::schema::CombinedSchema;
fn create_test_from_result(
table_name: &str,
columns: Vec<(&str, DataType)>,
rows: Vec<Vec<SqlValue>>,
) -> FromResult {
let schema = TableSchema::new(
table_name.to_string(),
columns
.iter()
.map(|(name, dtype)| {
ColumnSchema::new(
name.to_string(),
dtype.clone(),
true, )
})
.collect(),
);
let combined_schema = CombinedSchema::from_table(table_name.to_string(), schema);
let rows = rows.into_iter().map(Row::new).collect();
FromResult::from_rows(combined_schema, rows)
}
#[test]
fn test_hash_join_simple() {
let left = create_test_from_result(
"users",
vec![("id", DataType::Integer), ("name", DataType::Varchar { max_length: Some(50) })],
vec![
vec![SqlValue::Integer(1), SqlValue::Varchar(arcstr::ArcStr::from("Alice"))],
vec![SqlValue::Integer(2), SqlValue::Varchar(arcstr::ArcStr::from("Bob"))],
vec![SqlValue::Integer(3), SqlValue::Varchar(arcstr::ArcStr::from("Charlie"))],
],
);
let right = create_test_from_result(
"orders",
vec![("user_id", DataType::Integer), ("amount", DataType::Integer)],
vec![
vec![SqlValue::Integer(1), SqlValue::Integer(100)],
vec![SqlValue::Integer(2), SqlValue::Integer(200)],
vec![SqlValue::Integer(1), SqlValue::Integer(150)],
],
);
let mut result = hash_join_inner(left, right, 0, 0).unwrap();
assert_eq!(result.rows().len(), 3);
for row in result.rows() {
assert_eq!(row.values.len(), 4);
}
let alice_orders: Vec<_> =
result.rows().iter().filter(|r| r.values[0] == SqlValue::Integer(1)).collect();
assert_eq!(alice_orders.len(), 2);
let bob_orders: Vec<_> =
result.rows().iter().filter(|r| r.values[0] == SqlValue::Integer(2)).collect();
assert_eq!(bob_orders.len(), 1);
let charlie_orders: Vec<_> =
result.rows().iter().filter(|r| r.values[0] == SqlValue::Integer(3)).collect();
assert_eq!(charlie_orders.len(), 0);
}
#[test]
fn test_hash_join_null_values() {
let left = create_test_from_result(
"users",
vec![("id", DataType::Integer), ("name", DataType::Varchar { max_length: Some(50) })],
vec![
vec![SqlValue::Integer(1), SqlValue::Varchar(arcstr::ArcStr::from("Alice"))],
vec![SqlValue::Null, SqlValue::Varchar(arcstr::ArcStr::from("Unknown"))],
],
);
let right = create_test_from_result(
"orders",
vec![("user_id", DataType::Integer), ("amount", DataType::Integer)],
vec![
vec![SqlValue::Integer(1), SqlValue::Integer(100)],
vec![SqlValue::Null, SqlValue::Integer(200)],
],
);
let mut result = hash_join_inner(left, right, 0, 0).unwrap();
assert_eq!(result.rows().len(), 1);
assert_eq!(result.rows()[0].values[0], SqlValue::Integer(1)); assert_eq!(result.rows()[0].values[1], SqlValue::Varchar(arcstr::ArcStr::from("Alice"))); assert_eq!(result.rows()[0].values[2], SqlValue::Integer(1)); assert_eq!(result.rows()[0].values[3], SqlValue::Integer(100)); }
#[test]
fn test_hash_join_no_matches() {
let left = create_test_from_result(
"users",
vec![("id", DataType::Integer)],
vec![vec![SqlValue::Integer(1)], vec![SqlValue::Integer(2)]],
);
let right = create_test_from_result(
"orders",
vec![("user_id", DataType::Integer)],
vec![vec![SqlValue::Integer(3)], vec![SqlValue::Integer(4)]],
);
let mut result = hash_join_inner(left, right, 0, 0).unwrap();
assert_eq!(result.rows().len(), 0);
}
#[test]
fn test_hash_join_empty_tables() {
let left = create_test_from_result("users", vec![("id", DataType::Integer)], vec![]);
let right = create_test_from_result("orders", vec![("user_id", DataType::Integer)], vec![]);
let mut result = hash_join_inner(left, right, 0, 0).unwrap();
assert_eq!(result.rows().len(), 0);
}
#[test]
fn test_hash_join_duplicate_keys() {
let left = create_test_from_result(
"users",
vec![("id", DataType::Integer), ("type", DataType::Varchar { max_length: Some(10) })],
vec![
vec![SqlValue::Integer(1), SqlValue::Varchar(arcstr::ArcStr::from("admin"))],
vec![SqlValue::Integer(1), SqlValue::Varchar(arcstr::ArcStr::from("user"))],
],
);
let right = create_test_from_result(
"orders",
vec![("user_id", DataType::Integer), ("amount", DataType::Integer)],
vec![
vec![SqlValue::Integer(1), SqlValue::Integer(100)],
vec![SqlValue::Integer(1), SqlValue::Integer(200)],
],
);
let mut result = hash_join_inner(left, right, 0, 0).unwrap();
assert_eq!(result.rows().len(), 4);
for row in result.rows() {
assert_eq!(row.values[0], SqlValue::Integer(1));
}
}
fn create_test_rows(count: usize) -> Vec<vibesql_storage::Row> {
(0..count)
.map(|i| {
vibesql_storage::Row::from_vec(vec![
SqlValue::Integer(i as i64 % 100), SqlValue::Varchar(arcstr::ArcStr::from(format!("value{}", i))),
])
})
.collect()
}
#[test]
fn test_build_hash_table_sequential_basic() {
let build_rows = create_test_rows(100);
let hash_table = build_hash_table_sequential(&build_rows, 0);
assert_eq!(hash_table.len(), 100);
for (key, row_indices) in hash_table.iter() {
assert_eq!(row_indices.len(), 1);
assert_eq!(build_rows[row_indices[0]].values[0], *key);
}
}
#[test]
fn test_build_hash_table_sequential_with_duplicates() {
let rows = create_test_rows(1000); let hash_table = build_hash_table_sequential(&rows, 0);
assert_eq!(hash_table.len(), 100);
for (_, rows) in hash_table.iter() {
assert_eq!(rows.len(), 10);
}
}
#[test]
fn test_build_hash_table_sequential_null_values() {
let rows = vec![
vibesql_storage::Row::from_vec(vec![
SqlValue::Integer(1),
SqlValue::Varchar(arcstr::ArcStr::from("one")),
]),
vibesql_storage::Row::from_vec(vec![
SqlValue::Null,
SqlValue::Varchar(arcstr::ArcStr::from("null1")),
]),
vibesql_storage::Row::from_vec(vec![
SqlValue::Integer(2),
SqlValue::Varchar(arcstr::ArcStr::from("two")),
]),
vibesql_storage::Row::from_vec(vec![
SqlValue::Null,
SqlValue::Varchar(arcstr::ArcStr::from("null2")),
]),
];
let hash_table = build_hash_table_sequential(&rows, 0);
assert_eq!(hash_table.len(), 2);
assert!(hash_table.contains_key(&SqlValue::Integer(1)));
assert!(hash_table.contains_key(&SqlValue::Integer(2)));
assert!(!hash_table.contains_key(&SqlValue::Null));
}
#[test]
fn test_parallel_sequential_equivalence_small() {
let rows = create_test_rows(100);
let seq_table = build_hash_table_sequential(&rows, 0);
let par_table = build_hash_table_parallel(&rows, 0);
assert_eq!(seq_table.len(), par_table.len());
for (key, seq_rows) in seq_table.iter() {
let par_rows = par_table.get(key).expect("Key should exist in parallel table");
assert_eq!(seq_rows.len(), par_rows.len());
}
}
#[test]
fn test_parallel_sequential_equivalence_large() {
let build_rows = create_test_rows(10000);
let seq_table = build_hash_table_sequential(&build_rows, 0);
let par_table = build_hash_table_parallel(&build_rows, 0);
assert_eq!(seq_table.len(), par_table.len());
for (key, seq_indices) in seq_table.iter() {
let par_indices = par_table.get(key).expect("Key should exist in parallel table");
assert_eq!(seq_indices.len(), par_indices.len(), "Row count mismatch for key {:?}", key);
for &seq_idx in seq_indices {
assert!(
par_indices
.iter()
.any(|&par_idx| build_rows[par_idx].values == build_rows[seq_idx].values),
"Row not found in parallel table"
);
}
}
}
#[test]
fn test_parallel_with_null_values() {
let rows = vec![
vibesql_storage::Row::from_vec(vec![
SqlValue::Integer(1),
SqlValue::Varchar(arcstr::ArcStr::from("one")),
]),
vibesql_storage::Row::from_vec(vec![
SqlValue::Null,
SqlValue::Varchar(arcstr::ArcStr::from("null1")),
]),
vibesql_storage::Row::from_vec(vec![
SqlValue::Integer(2),
SqlValue::Varchar(arcstr::ArcStr::from("two")),
]),
vibesql_storage::Row::from_vec(vec![
SqlValue::Null,
SqlValue::Varchar(arcstr::ArcStr::from("null2")),
]),
vibesql_storage::Row::from_vec(vec![
SqlValue::Integer(1),
SqlValue::Varchar(arcstr::ArcStr::from("one_dup")),
]),
];
let par_table = build_hash_table_parallel(&rows, 0);
assert_eq!(par_table.len(), 2);
assert!(par_table.contains_key(&SqlValue::Integer(1)));
assert!(par_table.contains_key(&SqlValue::Integer(2)));
assert!(!par_table.contains_key(&SqlValue::Null));
assert_eq!(par_table.get(&SqlValue::Integer(1)).unwrap().len(), 2);
}
#[test]
fn test_parallel_hash_join_integration() {
let left_rows: Vec<Vec<SqlValue>> = (0..6000)
.map(|i| {
vec![
SqlValue::Integer(i % 100),
SqlValue::Varchar(arcstr::ArcStr::from(format!("left{}", i))),
]
})
.collect();
let left = create_test_from_result(
"large_left",
vec![("id", DataType::Integer), ("data", DataType::Varchar { max_length: Some(50) })],
left_rows,
);
let right_rows: Vec<Vec<SqlValue>> = (0..6000)
.map(|i| {
vec![
SqlValue::Integer(i % 100),
SqlValue::Varchar(arcstr::ArcStr::from(format!("right{}", i))),
]
})
.collect();
let right = create_test_from_result(
"large_right",
vec![("id", DataType::Integer), ("data", DataType::Varchar { max_length: Some(50) })],
right_rows,
);
let mut result = hash_join_inner(left, right, 0, 0).unwrap();
assert_eq!(result.rows().len(), 360_000);
for row in result.rows() {
assert_eq!(row.values.len(), 4); }
}
#[test]
fn test_hash_join_left_outer_basic() {
let left = create_test_from_result(
"users",
vec![("id", DataType::Integer), ("name", DataType::Varchar { max_length: Some(50) })],
vec![
vec![SqlValue::Integer(1), SqlValue::Varchar(arcstr::ArcStr::from("Alice"))],
vec![SqlValue::Integer(2), SqlValue::Varchar(arcstr::ArcStr::from("Bob"))],
vec![SqlValue::Integer(3), SqlValue::Varchar(arcstr::ArcStr::from("Charlie"))],
],
);
let right = create_test_from_result(
"orders",
vec![("user_id", DataType::Integer), ("amount", DataType::Integer)],
vec![
vec![SqlValue::Integer(1), SqlValue::Integer(100)],
vec![SqlValue::Integer(2), SqlValue::Integer(200)],
],
);
let mut result = hash_join_left_outer(left, right, 0, 0).unwrap();
assert_eq!(result.rows().len(), 3);
let alice_row = result.rows().iter().find(|r| r.values[0] == SqlValue::Integer(1)).unwrap();
assert_eq!(alice_row.values[1], SqlValue::Varchar(arcstr::ArcStr::from("Alice")));
assert_eq!(alice_row.values[2], SqlValue::Integer(1)); assert_eq!(alice_row.values[3], SqlValue::Integer(100));
let bob_row = result.rows().iter().find(|r| r.values[0] == SqlValue::Integer(2)).unwrap();
assert_eq!(bob_row.values[1], SqlValue::Varchar(arcstr::ArcStr::from("Bob")));
assert_eq!(bob_row.values[2], SqlValue::Integer(2)); assert_eq!(bob_row.values[3], SqlValue::Integer(200));
let charlie_row = result.rows().iter().find(|r| r.values[0] == SqlValue::Integer(3)).unwrap();
assert_eq!(charlie_row.values[1], SqlValue::Varchar(arcstr::ArcStr::from("Charlie")));
assert_eq!(charlie_row.values[2], SqlValue::Null); assert_eq!(charlie_row.values[3], SqlValue::Null); }
#[test]
fn test_hash_join_left_outer_unmatched_left_rows() {
let left = create_test_from_result(
"users",
vec![("id", DataType::Integer), ("name", DataType::Varchar { max_length: Some(50) })],
vec![
vec![SqlValue::Integer(1), SqlValue::Varchar(arcstr::ArcStr::from("Alice"))],
vec![SqlValue::Integer(2), SqlValue::Varchar(arcstr::ArcStr::from("Bob"))],
],
);
let right = create_test_from_result(
"orders",
vec![("user_id", DataType::Integer), ("amount", DataType::Integer)],
vec![
vec![SqlValue::Integer(99), SqlValue::Integer(100)],
vec![SqlValue::Integer(98), SqlValue::Integer(200)],
],
);
let mut result = hash_join_left_outer(left, right, 0, 0).unwrap();
assert_eq!(result.rows().len(), 2);
for row in result.rows() {
assert_eq!(row.values.len(), 4);
assert_eq!(row.values[2], SqlValue::Null); assert_eq!(row.values[3], SqlValue::Null); }
}
#[test]
fn test_hash_join_left_outer_multiple_matches() {
let left = create_test_from_result(
"users",
vec![("id", DataType::Integer), ("name", DataType::Varchar { max_length: Some(50) })],
vec![
vec![SqlValue::Integer(1), SqlValue::Varchar(arcstr::ArcStr::from("Alice"))],
vec![SqlValue::Integer(2), SqlValue::Varchar(arcstr::ArcStr::from("Bob"))],
],
);
let right = create_test_from_result(
"orders",
vec![("user_id", DataType::Integer), ("amount", DataType::Integer)],
vec![
vec![SqlValue::Integer(1), SqlValue::Integer(100)],
vec![SqlValue::Integer(1), SqlValue::Integer(150)],
vec![SqlValue::Integer(1), SqlValue::Integer(200)],
],
);
let mut result = hash_join_left_outer(left, right, 0, 0).unwrap();
assert_eq!(result.rows().len(), 4);
let alice_orders: Vec<_> =
result.rows().iter().filter(|r| r.values[0] == SqlValue::Integer(1)).collect();
assert_eq!(alice_orders.len(), 3);
let amounts: Vec<_> = alice_orders.iter().map(|r| r.values[3].clone()).collect();
assert!(amounts.contains(&SqlValue::Integer(100)));
assert!(amounts.contains(&SqlValue::Integer(150)));
assert!(amounts.contains(&SqlValue::Integer(200)));
let bob_rows: Vec<_> =
result.rows().iter().filter(|r| r.values[0] == SqlValue::Integer(2)).collect();
assert_eq!(bob_rows.len(), 1);
assert_eq!(bob_rows[0].values[2], SqlValue::Null);
assert_eq!(bob_rows[0].values[3], SqlValue::Null);
}
#[test]
fn test_hash_join_left_outer_null_keys() {
let left = create_test_from_result(
"users",
vec![("id", DataType::Integer), ("name", DataType::Varchar { max_length: Some(50) })],
vec![
vec![SqlValue::Integer(1), SqlValue::Varchar(arcstr::ArcStr::from("Alice"))],
vec![SqlValue::Null, SqlValue::Varchar(arcstr::ArcStr::from("Unknown1"))],
vec![SqlValue::Null, SqlValue::Varchar(arcstr::ArcStr::from("Unknown2"))],
],
);
let right = create_test_from_result(
"orders",
vec![("user_id", DataType::Integer), ("amount", DataType::Integer)],
vec![
vec![SqlValue::Integer(1), SqlValue::Integer(100)],
vec![SqlValue::Null, SqlValue::Integer(200)], ],
);
let mut result = hash_join_left_outer(left, right, 0, 0).unwrap();
assert_eq!(result.rows().len(), 3);
let alice_rows: Vec<_> =
result.rows().iter().filter(|r| r.values[0] == SqlValue::Integer(1)).collect();
assert_eq!(alice_rows.len(), 1);
assert_eq!(alice_rows[0].values[3], SqlValue::Integer(100));
let null_key_rows: Vec<_> =
result.rows().iter().filter(|r| r.values[0] == SqlValue::Null).collect();
assert_eq!(null_key_rows.len(), 2);
for row in null_key_rows {
assert_eq!(row.values[2], SqlValue::Null); assert_eq!(row.values[3], SqlValue::Null); }
}
#[test]
fn test_hash_join_left_outer_empty_right_table() {
let left = create_test_from_result(
"users",
vec![("id", DataType::Integer), ("name", DataType::Varchar { max_length: Some(50) })],
vec![
vec![SqlValue::Integer(1), SqlValue::Varchar(arcstr::ArcStr::from("Alice"))],
vec![SqlValue::Integer(2), SqlValue::Varchar(arcstr::ArcStr::from("Bob"))],
],
);
let right = create_test_from_result(
"orders",
vec![("user_id", DataType::Integer), ("amount", DataType::Integer)],
vec![],
);
let mut result = hash_join_left_outer(left, right, 0, 0).unwrap();
assert_eq!(result.rows().len(), 2);
for row in result.rows() {
assert_eq!(row.values.len(), 4);
assert_eq!(row.values[2], SqlValue::Null);
assert_eq!(row.values[3], SqlValue::Null);
}
}
#[test]
fn test_hash_join_left_outer_empty_left_table() {
let left = create_test_from_result(
"users",
vec![("id", DataType::Integer), ("name", DataType::Varchar { max_length: Some(50) })],
vec![],
);
let right = create_test_from_result(
"orders",
vec![("user_id", DataType::Integer), ("amount", DataType::Integer)],
vec![
vec![SqlValue::Integer(1), SqlValue::Integer(100)],
vec![SqlValue::Integer(2), SqlValue::Integer(200)],
],
);
let mut result = hash_join_left_outer(left, right, 0, 0).unwrap();
assert_eq!(result.rows().len(), 0);
}
#[test]
fn test_hash_join_inner_multi_two_columns() {
let left = create_test_from_result(
"sales",
vec![
("region_id", DataType::Integer),
("product_id", DataType::Integer),
("amount", DataType::Integer),
],
vec![
vec![SqlValue::Integer(1), SqlValue::Integer(100), SqlValue::Integer(500)],
vec![SqlValue::Integer(1), SqlValue::Integer(200), SqlValue::Integer(300)],
vec![SqlValue::Integer(2), SqlValue::Integer(100), SqlValue::Integer(400)],
vec![SqlValue::Integer(2), SqlValue::Integer(300), SqlValue::Integer(200)],
],
);
let right = create_test_from_result(
"inventory",
vec![
("region_id", DataType::Integer),
("product_id", DataType::Integer),
("stock", DataType::Integer),
],
vec![
vec![SqlValue::Integer(1), SqlValue::Integer(100), SqlValue::Integer(50)],
vec![SqlValue::Integer(1), SqlValue::Integer(200), SqlValue::Integer(30)],
vec![SqlValue::Integer(2), SqlValue::Integer(200), SqlValue::Integer(20)], ],
);
let mut result = hash_join_inner_multi(
left,
right,
&[0, 1], &[0, 1], )
.unwrap();
assert_eq!(result.rows().len(), 2);
for row in result.rows() {
assert_eq!(row.values.len(), 6);
}
let match_1_100: Vec<_> = result
.rows()
.iter()
.filter(|r| r.values[0] == SqlValue::Integer(1) && r.values[1] == SqlValue::Integer(100))
.collect();
assert_eq!(match_1_100.len(), 1);
assert_eq!(match_1_100[0].values[2], SqlValue::Integer(500)); assert_eq!(match_1_100[0].values[5], SqlValue::Integer(50));
let match_1_200: Vec<_> = result
.rows()
.iter()
.filter(|r| r.values[0] == SqlValue::Integer(1) && r.values[1] == SqlValue::Integer(200))
.collect();
assert_eq!(match_1_200.len(), 1);
assert_eq!(match_1_200[0].values[2], SqlValue::Integer(300)); assert_eq!(match_1_200[0].values[5], SqlValue::Integer(30)); }
#[test]
fn test_hash_join_inner_multi_null_in_composite_key() {
let left = create_test_from_result(
"left",
vec![("a", DataType::Integer), ("b", DataType::Integer)],
vec![
vec![SqlValue::Integer(1), SqlValue::Integer(2)],
vec![SqlValue::Integer(1), SqlValue::Null], vec![SqlValue::Null, SqlValue::Integer(2)], ],
);
let right = create_test_from_result(
"right",
vec![("x", DataType::Integer), ("y", DataType::Integer)],
vec![
vec![SqlValue::Integer(1), SqlValue::Integer(2)],
vec![SqlValue::Integer(1), SqlValue::Null], vec![SqlValue::Null, SqlValue::Integer(2)], ],
);
let mut result = hash_join_inner_multi(left, right, &[0, 1], &[0, 1]).unwrap();
assert_eq!(result.rows().len(), 1);
assert_eq!(result.rows()[0].values[0], SqlValue::Integer(1));
assert_eq!(result.rows()[0].values[1], SqlValue::Integer(2));
}
#[test]
fn test_hash_join_inner_multi_duplicate_composite_keys() {
let left = create_test_from_result(
"left",
vec![
("a", DataType::Integer),
("b", DataType::Integer),
("data", DataType::Varchar { max_length: Some(10) }),
],
vec![
vec![
SqlValue::Integer(1),
SqlValue::Integer(2),
SqlValue::Varchar(arcstr::ArcStr::from("L1")),
],
vec![
SqlValue::Integer(1),
SqlValue::Integer(2),
SqlValue::Varchar(arcstr::ArcStr::from("L2")),
], ],
);
let right = create_test_from_result(
"right",
vec![
("x", DataType::Integer),
("y", DataType::Integer),
("info", DataType::Varchar { max_length: Some(10) }),
],
vec![
vec![
SqlValue::Integer(1),
SqlValue::Integer(2),
SqlValue::Varchar(arcstr::ArcStr::from("R1")),
],
vec![
SqlValue::Integer(1),
SqlValue::Integer(2),
SqlValue::Varchar(arcstr::ArcStr::from("R2")),
], ],
);
let mut result = hash_join_inner_multi(left, right, &[0, 1], &[0, 1]).unwrap();
assert_eq!(result.rows().len(), 4);
let result_data: Vec<_> =
result.rows().iter().map(|r| (r.values[2].clone(), r.values[5].clone())).collect();
assert!(result_data.contains(&(
SqlValue::Varchar(arcstr::ArcStr::from("L1")),
SqlValue::Varchar(arcstr::ArcStr::from("R1"))
)));
assert!(result_data.contains(&(
SqlValue::Varchar(arcstr::ArcStr::from("L1")),
SqlValue::Varchar(arcstr::ArcStr::from("R2"))
)));
assert!(result_data.contains(&(
SqlValue::Varchar(arcstr::ArcStr::from("L2")),
SqlValue::Varchar(arcstr::ArcStr::from("R1"))
)));
assert!(result_data.contains(&(
SqlValue::Varchar(arcstr::ArcStr::from("L2")),
SqlValue::Varchar(arcstr::ArcStr::from("R2"))
)));
}
#[test]
fn test_composite_key_build_and_hash() {
let rows = vec![
Row::new(vec![
SqlValue::Integer(1),
SqlValue::Varchar(arcstr::ArcStr::from("a")),
SqlValue::Integer(100),
]),
Row::new(vec![
SqlValue::Integer(2),
SqlValue::Varchar(arcstr::ArcStr::from("b")),
SqlValue::Integer(200),
]),
Row::new(vec![
SqlValue::Integer(1),
SqlValue::Varchar(arcstr::ArcStr::from("a")),
SqlValue::Integer(300),
]), ];
let hash_table = build_hash_table_composite_sequential(&rows, &[0, 1]);
assert_eq!(hash_table.len(), 2);
let key_1_a =
CompositeKey(vec![SqlValue::Integer(1), SqlValue::Varchar(arcstr::ArcStr::from("a"))]);
let indices = hash_table.get(&key_1_a).unwrap();
assert_eq!(indices.len(), 2);
assert!(indices.contains(&0));
assert!(indices.contains(&2));
let key_2_b =
CompositeKey(vec![SqlValue::Integer(2), SqlValue::Varchar(arcstr::ArcStr::from("b"))]);
let indices = hash_table.get(&key_2_b).unwrap();
assert_eq!(indices.len(), 1);
assert!(indices.contains(&1));
}
#[test]
fn test_composite_key_with_nulls() {
let rows = vec![
Row::new(vec![SqlValue::Integer(1), SqlValue::Varchar(arcstr::ArcStr::from("a"))]),
Row::new(vec![SqlValue::Null, SqlValue::Varchar(arcstr::ArcStr::from("b"))]),
Row::new(vec![SqlValue::Integer(2), SqlValue::Null]), Row::new(vec![SqlValue::Integer(3), SqlValue::Varchar(arcstr::ArcStr::from("c"))]),
];
let hash_table = build_hash_table_composite_sequential(&rows, &[0, 1]);
assert_eq!(hash_table.len(), 2);
}