use std::cmp::Ordering as CmpOrdering;
use vibesql_storage::Row;
use vibesql_types::{DataType, SqlValue};
use super::config::{DEFAULT_MORSEL_SIZE, MAX_MORSEL_SIZE, MIN_MORSEL_SIZE};
use super::{
create_morsels, global_config, morsel_filter, morsel_map, morsel_parallel_filter,
morsel_parallel_group, morsel_parallel_map, morsel_parallel_reduce, morsel_parallel_sort,
morsel_sort_by, Morsel, MorselConfig,
};
fn create_test_rows(count: usize) -> Vec<Row> {
(0..count)
.map(|i| {
Row::from_vec(vec![
SqlValue::Integer(i as i64),
SqlValue::Varchar(arcstr::ArcStr::from(format!("row{}", i))),
])
})
.collect()
}
#[test]
fn test_morsel_creation() {
let morsel = Morsel::new(100, 50);
assert_eq!(morsel.start_idx(), 100);
assert_eq!(morsel.row_count(), 50);
assert_eq!(morsel.end_idx(), 150);
}
#[test]
fn test_morsel_rows_extraction() {
let rows = create_test_rows(100);
let morsel = Morsel::new(10, 20);
let extracted = morsel.rows(&rows);
assert_eq!(extracted.len(), 20);
assert!(matches!(extracted[0].values[0], SqlValue::Integer(10)));
assert!(matches!(extracted[19].values[0], SqlValue::Integer(29)));
}
#[test]
fn test_create_morsels() {
let morsels = create_morsels(1000, 300);
assert_eq!(morsels.len(), 4);
assert_eq!(morsels[0].start_idx(), 0);
assert_eq!(morsels[0].row_count(), 300);
assert_eq!(morsels[3].start_idx(), 900);
assert_eq!(morsels[3].row_count(), 100);
}
#[test]
fn test_morsel_filter_small_dataset() {
let config = MorselConfig::new(100);
let rows = create_test_rows(50);
let filtered = morsel_parallel_filter(
&rows,
&config,
|row| matches!(row.values[0], SqlValue::Integer(x) if x % 2 == 0),
);
assert_eq!(filtered.len(), 25); }
#[test]
fn test_morsel_filter_large_dataset() {
let config = MorselConfig::new(100);
let rows = create_test_rows(1000);
let filtered = morsel_parallel_filter(
&rows,
&config,
|row| matches!(row.values[0], SqlValue::Integer(x) if x % 2 == 0),
);
assert_eq!(filtered.len(), 500);
for (i, row) in filtered.iter().enumerate() {
let expected = (i * 2) as i64;
assert!(matches!(row.values[0], SqlValue::Integer(x) if x == expected));
}
}
#[test]
fn test_morsel_map() {
let config = MorselConfig::new(100);
let rows = create_test_rows(500);
let transformed = morsel_parallel_map(&rows, &config, |row| {
let mut new_row = row.clone();
if let SqlValue::Integer(x) = row.values[0] {
new_row.values[0] = SqlValue::Integer(x * 2);
}
new_row
});
assert_eq!(transformed.len(), 500);
for (i, row) in transformed.iter().enumerate() {
let expected = (i * 2) as i64;
assert!(matches!(row.values[0], SqlValue::Integer(x) if x == expected));
}
}
#[test]
fn test_morsel_reduce() {
let config = MorselConfig::new(100);
let rows = create_test_rows(500);
let sum = morsel_parallel_reduce(
&rows,
&config,
|morsel_rows| {
morsel_rows
.iter()
.map(|r| if let SqlValue::Integer(x) = r.values[0] { x } else { 0 })
.sum::<i64>()
},
|a, b| a + b,
0i64,
);
assert_eq!(sum, 124750);
}
#[test]
fn test_morsel_filter_empty_input() {
let config = MorselConfig::new(100);
let rows: Vec<Row> = Vec::new();
let filtered = morsel_parallel_filter(&rows, &config, |_| true);
assert!(filtered.is_empty());
}
#[test]
fn test_global_config() {
let config = global_config();
assert!(config.morsel_size >= 1000);
}
#[test]
fn test_convenience_functions() {
let rows = create_test_rows(100);
let filtered =
morsel_filter(&rows, |row| matches!(row.values[0], SqlValue::Integer(x) if x < 10));
assert_eq!(filtered.len(), 10);
let mapped = morsel_map(&rows, |row| row.clone());
assert_eq!(mapped.len(), 100);
}
#[test]
fn test_morsel_parallel_group() {
use ahash::AHashMap;
let config = MorselConfig::new(100);
let rows = create_test_rows(500);
let groups = morsel_parallel_group(
&rows,
&config,
|row| {
if let SqlValue::Integer(x) = row.values[0] {
vec![SqlValue::Integer(x % 10)]
} else {
vec![SqlValue::Null]
}
},
|a: AHashMap<Vec<SqlValue>, Vec<Row>>,
b: AHashMap<Vec<SqlValue>, Vec<Row>>|
-> AHashMap<Vec<SqlValue>, Vec<Row>> {
let mut result = a;
for (key, mut rows) in b {
result.entry(key).or_default().append(&mut rows);
}
result
},
);
assert_eq!(groups.len(), 10);
for (_, group_rows) in groups.iter() {
assert_eq!(group_rows.len(), 50);
}
}
#[test]
fn test_morsel_sort_empty_input() {
let config = MorselConfig::new(100);
let rows: Vec<Row> = Vec::new();
let sorted = morsel_parallel_sort(&rows, &config, |a, b| match (&a.values[0], &b.values[0]) {
(SqlValue::Integer(x), SqlValue::Integer(y)) => x.cmp(y),
_ => CmpOrdering::Equal,
});
assert!(sorted.is_empty());
}
#[test]
fn test_morsel_sort_small_dataset() {
let config = MorselConfig::new(100);
let rows: Vec<Row> = (0..50)
.rev()
.map(|i| {
Row::from_vec(vec![
SqlValue::Integer(i as i64),
SqlValue::Varchar(arcstr::ArcStr::from(format!("row{}", i))),
])
})
.collect();
let sorted = morsel_parallel_sort(&rows, &config, |a, b| match (&a.values[0], &b.values[0]) {
(SqlValue::Integer(x), SqlValue::Integer(y)) => x.cmp(y),
_ => CmpOrdering::Equal,
});
assert_eq!(sorted.len(), 50);
for (i, row) in sorted.iter().enumerate() {
assert!(matches!(row.values[0], SqlValue::Integer(x) if x == i as i64));
}
}
#[test]
fn test_morsel_sort_large_dataset() {
let config = MorselConfig::new(100); let rows: Vec<Row> = (0..1000)
.rev()
.map(|i| {
Row::from_vec(vec![
SqlValue::Integer(i as i64),
SqlValue::Varchar(arcstr::ArcStr::from(format!("row{}", i))),
])
})
.collect();
let sorted = morsel_parallel_sort(&rows, &config, |a, b| match (&a.values[0], &b.values[0]) {
(SqlValue::Integer(x), SqlValue::Integer(y)) => x.cmp(y),
_ => CmpOrdering::Equal,
});
assert_eq!(sorted.len(), 1000);
for (i, row) in sorted.iter().enumerate() {
assert!(matches!(row.values[0], SqlValue::Integer(x) if x == i as i64));
}
}
#[test]
fn test_morsel_sort_descending() {
let config = MorselConfig::new(100);
let rows = create_test_rows(500);
let sorted = morsel_parallel_sort(&rows, &config, |a, b| {
match (&a.values[0], &b.values[0]) {
(SqlValue::Integer(x), SqlValue::Integer(y)) => y.cmp(x),
_ => CmpOrdering::Equal,
}
});
assert_eq!(sorted.len(), 500);
for (i, row) in sorted.iter().enumerate() {
let expected = (499 - i) as i64;
assert!(matches!(row.values[0], SqlValue::Integer(x) if x == expected));
}
}
#[test]
fn test_morsel_sort_with_nulls() {
let config = MorselConfig::new(100);
let mut rows: Vec<Row> = Vec::new();
for i in 0..200 {
if i % 10 == 0 {
rows.push(Row::from_vec(vec![SqlValue::Null]));
} else {
rows.push(Row::from_vec(vec![SqlValue::Integer(i as i64)]));
}
}
let sorted = morsel_parallel_sort(&rows, &config, |a, b| {
match (&a.values[0], &b.values[0]) {
(SqlValue::Null, SqlValue::Null) => CmpOrdering::Equal,
(SqlValue::Null, _) => CmpOrdering::Greater, (_, SqlValue::Null) => CmpOrdering::Less,
(SqlValue::Integer(x), SqlValue::Integer(y)) => x.cmp(y),
_ => CmpOrdering::Equal,
}
});
assert_eq!(sorted.len(), 200);
let null_count = sorted.iter().filter(|r| r.values[0] == SqlValue::Null).count();
assert_eq!(null_count, 20);
for row in sorted.iter().skip(180) {
assert_eq!(row.values[0], SqlValue::Null);
}
let mut last_val = -1i64;
for row in sorted.iter().take(180) {
if let SqlValue::Integer(x) = row.values[0] {
assert!(x > last_val, "Values should be ascending: {} > {}", x, last_val);
last_val = x;
}
}
}
#[test]
fn test_morsel_sort_multi_key() {
let config = MorselConfig::new(50);
let mut rows: Vec<Row> = Vec::new();
for i in 0..100 {
rows.push(Row::from_vec(vec![
SqlValue::Integer((i % 10) as i64), SqlValue::Integer(i as i64), ]));
}
let sorted = morsel_parallel_sort(&rows, &config, |a, b| {
let group_a = match &a.values[0] {
SqlValue::Integer(x) => *x,
_ => 0,
};
let group_b = match &b.values[0] {
SqlValue::Integer(x) => *x,
_ => 0,
};
let val_a = match &a.values[1] {
SqlValue::Integer(x) => *x,
_ => 0,
};
let val_b = match &b.values[1] {
SqlValue::Integer(x) => *x,
_ => 0,
};
match group_a.cmp(&group_b) {
CmpOrdering::Equal => val_b.cmp(&val_a), other => other,
}
});
assert_eq!(sorted.len(), 100);
let mut current_group = 0i64;
let mut last_val_in_group = i64::MAX;
for row in sorted.iter() {
let group = match &row.values[0] {
SqlValue::Integer(x) => *x,
_ => 0,
};
let val = match &row.values[1] {
SqlValue::Integer(x) => *x,
_ => 0,
};
if group != current_group {
assert!(group > current_group, "Groups should be ascending");
current_group = group;
last_val_in_group = i64::MAX;
}
assert!(val < last_val_in_group, "Values within group should be descending");
last_val_in_group = val;
}
}
#[test]
fn test_morsel_sort_by_convenience() {
let rows: Vec<Row> =
(0..100).rev().map(|i| Row::from_vec(vec![SqlValue::Integer(i as i64)])).collect();
let sorted = morsel_sort_by(&rows, |a, b| match (&a.values[0], &b.values[0]) {
(SqlValue::Integer(x), SqlValue::Integer(y)) => x.cmp(y),
_ => CmpOrdering::Equal,
});
assert_eq!(sorted.len(), 100);
for (i, row) in sorted.iter().enumerate() {
assert!(matches!(row.values[0], SqlValue::Integer(x) if x == i as i64));
}
}
#[test]
fn test_morsel_sort_single_morsel() {
let config = MorselConfig::new(100);
let rows: Vec<Row> =
(0..100).rev().map(|i| Row::from_vec(vec![SqlValue::Integer(i as i64)])).collect();
let sorted = morsel_parallel_sort(&rows, &config, |a, b| match (&a.values[0], &b.values[0]) {
(SqlValue::Integer(x), SqlValue::Integer(y)) => x.cmp(y),
_ => CmpOrdering::Equal,
});
assert_eq!(sorted.len(), 100);
for (i, row) in sorted.iter().enumerate() {
assert!(matches!(row.values[0], SqlValue::Integer(x) if x == i as i64));
}
}
#[test]
fn test_morsel_sort_all_equal() {
let config = MorselConfig::new(50);
let rows: Vec<Row> = (0..200).map(|_| Row::from_vec(vec![SqlValue::Integer(42)])).collect();
let sorted = morsel_parallel_sort(&rows, &config, |a, b| match (&a.values[0], &b.values[0]) {
(SqlValue::Integer(x), SqlValue::Integer(y)) => x.cmp(y),
_ => CmpOrdering::Equal,
});
assert_eq!(sorted.len(), 200);
for row in sorted.iter() {
assert!(matches!(row.values[0], SqlValue::Integer(42)));
}
}
#[test]
fn test_for_row_width_wide_rows() {
let config = MorselConfig::for_row_width(500);
assert_eq!(config.morsel_size, MIN_MORSEL_SIZE);
}
#[test]
fn test_for_row_width_narrow_rows() {
let config = MorselConfig::for_row_width(20);
assert_eq!(config.morsel_size, MAX_MORSEL_SIZE);
}
#[test]
fn test_for_row_width_medium_rows() {
let config = MorselConfig::for_row_width(100);
assert_eq!(config.morsel_size, 20_971);
}
#[test]
fn test_for_row_width_zero_bytes() {
let config = MorselConfig::for_row_width(0);
assert_eq!(config.morsel_size, MAX_MORSEL_SIZE);
}
#[test]
fn test_for_schema_narrow() {
let schema = [DataType::Integer, DataType::Integer];
let config = MorselConfig::for_schema(&schema);
assert!(config.morsel_size > 40_000 && config.morsel_size < 50_000);
}
#[test]
fn test_for_schema_wide() {
let schema = [
DataType::Integer,
DataType::Varchar { max_length: Some(200) },
DataType::Varchar { max_length: Some(200) },
];
let config = MorselConfig::for_schema(&schema);
assert!(config.morsel_size <= DEFAULT_MORSEL_SIZE);
}
#[test]
fn test_for_schema_empty() {
let schema: [DataType; 0] = [];
let config = MorselConfig::for_schema(&schema);
assert_eq!(config.morsel_size, DEFAULT_MORSEL_SIZE);
}
#[test]
fn test_for_selectivity_low() {
let config = MorselConfig::for_selectivity(0.01);
assert_eq!(config.morsel_size, MAX_MORSEL_SIZE * 2);
}
#[test]
fn test_for_selectivity_high() {
let config = MorselConfig::for_selectivity(0.90);
assert_eq!(config.morsel_size, DEFAULT_MORSEL_SIZE);
}
#[test]
fn test_for_selectivity_medium() {
let config = MorselConfig::for_selectivity(0.05);
assert_eq!(config.morsel_size, MAX_MORSEL_SIZE * 2);
}
#[test]
fn test_for_selectivity_boundary() {
let config = MorselConfig::for_selectivity(0.10);
assert_eq!(config.morsel_size, DEFAULT_MORSEL_SIZE);
let config = MorselConfig::for_selectivity(0.09);
assert!(config.morsel_size > DEFAULT_MORSEL_SIZE);
}
#[test]
fn test_adaptive_schema_only() {
let schema = [DataType::Integer, DataType::Bigint];
let config = MorselConfig::adaptive(&schema, None);
let expected = MorselConfig::for_schema(&schema);
assert_eq!(config.morsel_size, expected.morsel_size);
}
#[test]
fn test_adaptive_with_selectivity() {
let schema = [DataType::Integer, DataType::Bigint];
let config = MorselConfig::adaptive(&schema, Some(0.01));
let schema_only = MorselConfig::for_schema(&schema);
assert!(config.morsel_size > schema_only.morsel_size);
}
#[test]
fn test_adaptive_high_selectivity() {
let schema = [DataType::Integer, DataType::Bigint];
let config = MorselConfig::adaptive(&schema, Some(0.90));
let schema_only = MorselConfig::for_schema(&schema);
assert_eq!(config.morsel_size, schema_only.morsel_size);
}
#[test]
fn test_data_type_size_estimates() {
assert_eq!(DataType::Integer.estimated_size_bytes(), 8 + 4); assert_eq!(DataType::Bigint.estimated_size_bytes(), 8 + 8);
assert_eq!(DataType::Boolean.estimated_size_bytes(), 8 + 1);
let varchar = DataType::Varchar { max_length: Some(100) };
assert_eq!(varchar.estimated_size_bytes(), 8 + 16 + 100);
let vector = DataType::Vector { dimensions: 128 };
assert_eq!(vector.estimated_size_bytes(), 8 + 24 + 128 * 4); }