use mongreldb_core::schema::{ColumnDef, ColumnFlags, IndexDef, IndexKind, Schema, TypeId};
use mongreldb_core::trace::{IndexRebuild, ScanMode};
use mongreldb_core::{Condition, IndexBuildPolicy, Query, Table, Value};
use tempfile::{tempdir, TempDir};
fn schema() -> Schema {
Schema {
schema_id: 1,
columns: vec![
ColumnDef {
id: 1,
name: "id".into(),
ty: TypeId::Int64,
flags: ColumnFlags::empty().with(ColumnFlags::PRIMARY_KEY),
},
ColumnDef {
id: 2,
name: "cat".into(),
ty: TypeId::Bytes,
flags: ColumnFlags::empty(),
},
ColumnDef {
id: 3,
name: "cost".into(),
ty: TypeId::Float64,
flags: ColumnFlags::empty(),
},
],
indexes: vec![IndexDef {
name: "cat_bitmap".into(),
column_id: 2,
kind: IndexKind::Bitmap,
predicate: None,
}],
colocation: vec![],
constraints: Default::default(),
clustered: false,
}
}
fn rows(n: u64) -> Vec<Vec<(u16, Value)>> {
(0..n)
.map(|i| {
vec![
(1, Value::Int64(i as i64)),
(2, Value::Bytes(format!("cat{}", i % 20).into_bytes())),
(3, Value::Float64(199.99 + i as f64)),
]
})
.collect()
}
fn build_db(n: u64) -> (TempDir, Table) {
let dir = tempdir().unwrap();
let mut db = Table::create(dir.path(), schema(), 1).unwrap();
db.set_index_build_policy(IndexBuildPolicy::Eager);
db.bulk_load(rows(n)).unwrap();
(dir, db)
}
#[test]
fn trace_bitmap_pushdown_uses_fast_path() {
let (_dir, mut db) = build_db(10_000);
let snap = db.snapshot();
let q = Query::new().and(Condition::BitmapEq {
column_id: 2,
value: b"cat5".to_vec(),
});
let proj = [1u16, 3];
let (cols, trace) = db
.query_columns_native_traced(&q.conditions, Some(&proj), snap)
.unwrap();
let cols = cols.expect("bitmap pushdown should be served");
assert_eq!(cols.len(), 2);
assert_eq!(cols[0].1.len(), 500); trace
.assert_mode(ScanMode::NativePushdown)
.assert_no_index_rebuild()
.assert_fast_row_id_map()
.assert_not_materialized();
assert_eq!(trace.conditions_pushed, 1);
assert_eq!(trace.survivor_count, Some(500));
assert_eq!(trace.run_count, 1);
assert!(!trace.result_cache_hit);
}
#[test]
fn trace_count_conditions_uses_survivor_shortcut() {
let (_dir, mut db) = build_db(10_000);
let snap = db.snapshot();
let q = Query::new().and(Condition::BitmapEq {
column_id: 2,
value: b"cat5".to_vec(),
});
let (count, trace) = db.count_conditions_traced(&q.conditions, snap).unwrap();
assert_eq!(count, Some(500));
trace
.assert_mode(ScanMode::CountSurvivors)
.assert_no_index_rebuild()
.assert_not_materialized();
assert_eq!(trace.survivor_count, Some(500));
}
#[test]
fn trace_native_page_cursor_records_cursor_mode() {
let (_dir, db) = build_db(10_000);
let snap = db.snapshot();
let q: Vec<Condition> = vec![Condition::BitmapEq {
column_id: 2,
value: b"cat5".to_vec(),
}];
let (cursor_opt, trace) = db
.native_page_cursor_traced(snap, vec![(1, TypeId::Int64), (3, TypeId::Float64)], &q)
.unwrap();
let mut cursor = cursor_opt.expect("single-run cursor should build");
let batch = cursor.next_batch().unwrap().unwrap();
assert_eq!(batch.len(), 2);
trace
.assert_mode(ScanMode::NativePageCursor)
.assert_no_index_rebuild();
assert_eq!(trace.run_count, 1);
assert_eq!(trace.conditions_pushed, 1);
}
#[test]
fn trace_result_cache_hit_on_second_query() {
let (_dir, mut db) = build_db(10_000);
let snap = db.snapshot();
let q = Query::new().and(Condition::BitmapEq {
column_id: 2,
value: b"cat5".to_vec(),
});
let proj = [1u16];
let (_, trace_miss) = db
.query_columns_native_cached_traced(&q.conditions, Some(&proj), snap)
.unwrap();
assert!(!trace_miss.result_cache_hit);
let (_, trace_hit) = db
.query_columns_native_cached_traced(&q.conditions, Some(&proj), snap)
.unwrap();
trace_hit
.assert_cache_hit()
.assert_mode(ScanMode::NativePushdown);
}
#[test]
fn trace_overlay_uses_cursor_not_materialization() {
let (_dir, mut db) = build_db(10_000);
db.put(vec![
(1, Value::Int64(99_999)),
(2, Value::Bytes(b"cat5".to_vec())),
(3, Value::Float64(42.0)),
])
.unwrap();
let snap = db.snapshot();
let q = Query::new().and(Condition::BitmapEq {
column_id: 2,
value: b"cat5".to_vec(),
});
let proj = [1u16, 3];
let (_, trace) = db
.query_columns_native_traced(&q.conditions, Some(&proj), snap)
.unwrap();
assert_eq!(
trace.scan_mode,
ScanMode::NativePageCursor,
"overlay should use cursor path, not materialization: {trace}"
);
assert!(
!trace.row_materialized,
"overlay should not materialize rows: {trace}"
);
assert!(trace.memtable_rows > 0);
}
#[test]
fn trace_index_rebuild_after_bulk_load() {
let dir = tempdir().unwrap();
let mut db = Table::create(dir.path(), schema(), 1).unwrap();
db.bulk_load(rows(5_000)).unwrap();
let snap = db.snapshot();
let q = Query::new().and(Condition::BitmapEq {
column_id: 2,
value: b"cat5".to_vec(),
});
let (_, trace) = db
.query_columns_native_traced(&q.conditions, Some(&[1u16]), snap)
.unwrap();
assert_eq!(
trace.index_rebuild,
IndexRebuild::Rebuilt,
"first query after a deferred bulk load must rebuild indexes: {trace}"
);
let (_, trace) = db
.query_columns_native_traced(&q.conditions, Some(&[1u16]), snap)
.unwrap();
assert_eq!(
trace.index_rebuild,
IndexRebuild::AlreadyComplete,
"second query must not rebuild again: {trace}"
);
}
#[test]
fn trace_query_method_records_materialization() {
let (_dir, mut db) = build_db(1_000);
let q = Query::new().and(Condition::BitmapEq {
column_id: 2,
value: b"cat5".to_vec(),
});
let (rows, trace) = db.query_traced(&q).unwrap();
assert!(!rows.is_empty());
trace.assert_mode(ScanMode::Materialized);
assert!(
trace.row_materialized,
"query() should set row_materialized: {trace}"
);
}
#[test]
fn trace_multi_run_cursor_records_run_count() {
let dir = tempdir().unwrap();
let mut db = Table::create(dir.path(), schema(), 1).unwrap();
db.set_mutable_run_spill_bytes(1); db.bulk_load(rows(2_000)).unwrap();
db.flush().unwrap();
for i in 0..10i64 {
db.put(vec![
(1, Value::Int64(100_000 + i)),
(2, Value::Bytes(b"cat5".to_vec())),
(3, Value::Float64(1.0)),
])
.unwrap();
}
db.flush().unwrap();
let snap = db.snapshot();
assert!(
db.run_count() >= 2,
"expected multi-run layout, got {} runs",
db.run_count()
);
let q: Vec<Condition> = vec![Condition::BitmapEq {
column_id: 2,
value: b"cat5".to_vec(),
}];
let (cursor_opt, trace) = db
.native_multi_run_cursor_traced(snap, vec![(1, TypeId::Int64), (3, TypeId::Float64)], &q)
.unwrap();
let _cursor = cursor_opt.expect("multi-run cursor should build");
trace.assert_mode(ScanMode::MultiRunCursor);
assert!(
trace.run_count >= 2,
"multi-run should record >= 2 runs: {trace}"
);
}
#[test]
fn trace_is_fast_for_good_paths() {
let (_dir, mut db) = build_db(10_000);
let snap = db.snapshot();
let q = Query::new().and(Condition::BitmapEq {
column_id: 2,
value: b"cat5".to_vec(),
});
let (_, trace) = db
.query_columns_native_traced(&q.conditions, Some(&[1u16]), snap)
.unwrap();
assert!(trace.is_fast(), "pushdown path should be fast: {trace}");
}