Skip to content

Commit 8472254

Browse files
committed
test: improve test coverage
1 parent 4a2b955 commit 8472254

12 files changed

Lines changed: 1322 additions & 4 deletions

src/db.rs

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,10 +1157,268 @@ mod tests {
11571157
db.insert("test", serde_to_jsonb(json!({ "fill": i })))
11581158
.unwrap();
11591159
}
1160+
db.wait_for_flush("test").unwrap();
11601161

11611162
let doc = db.get("test", &id).unwrap().unwrap();
11621163
assert_eq!(doc, serde_to_jsonb(json!({ "a": 1 })));
11631164

11641165
assert!(db.get("test", "non-existent").unwrap().is_none());
11651166
}
1167+
1168+
#[test]
1169+
fn test_scan_disk_predicate_and_projection() {
1170+
use crate::expression::{BinaryOperator, Expression};
1171+
1172+
let dir = tempdir().unwrap();
1173+
let mut db = DB::new(
1174+
dir.path().to_str().unwrap(),
1175+
MEMTABLE_THRESHOLD,
1176+
JSTABLE_THRESHOLD,
1177+
INDEX_THRESHOLD,
1178+
Some(1024 * 1024),
1179+
);
1180+
db.create_collection("test").unwrap();
1181+
1182+
db.insert("test", serde_to_jsonb(json!({"a": 5, "b": 10})))
1183+
.unwrap();
1184+
db.insert("test", serde_to_jsonb(json!({"a": 15, "b": 20})))
1185+
.unwrap();
1186+
1187+
// Flush to disk
1188+
for i in 0..MEMTABLE_THRESHOLD {
1189+
db.insert("test", serde_to_jsonb(json!({ "fill": i })))
1190+
.unwrap();
1191+
}
1192+
db.wait_for_flush("test").unwrap();
1193+
1194+
// Predicate: a > 10
1195+
let predicate = Expression::Binary {
1196+
left: Box::new(Expression::FieldReference(vec!["a"], "a")),
1197+
op: BinaryOperator::Gt,
1198+
right: Box::new(Expression::Literal(serde_to_jsonb(json!(10)))),
1199+
};
1200+
1201+
// Projection: b
1202+
let projections = vec![Expression::FieldReference(vec!["b"], "b")];
1203+
1204+
let results: Vec<Value> = db
1205+
.scan("test", Some(predicate), Some(projections))
1206+
.unwrap()
1207+
.map(|r| r.get_value())
1208+
.collect();
1209+
1210+
assert_eq!(results.len(), 1);
1211+
let doc = &results[0];
1212+
// Result should be {"b": 20}
1213+
if let Value::Object(obj) = doc {
1214+
assert_eq!(obj.get("b"), Some(&serde_to_jsonb(json!(20))));
1215+
assert!(!obj.contains_key("a"));
1216+
} else {
1217+
panic!("Expected object");
1218+
}
1219+
}
1220+
1221+
#[test]
1222+
fn test_scan_disk_tombstone() {
1223+
let dir = tempdir().unwrap();
1224+
let mut db = DB::new(
1225+
dir.path().to_str().unwrap(),
1226+
MEMTABLE_THRESHOLD,
1227+
JSTABLE_THRESHOLD,
1228+
INDEX_THRESHOLD,
1229+
Some(1024 * 1024),
1230+
);
1231+
db.create_collection("test").unwrap();
1232+
1233+
let id = db.insert("test", serde_to_jsonb(json!({"a": 1}))).unwrap();
1234+
1235+
// Flush insert
1236+
for i in 0..MEMTABLE_THRESHOLD {
1237+
db.insert("test", serde_to_jsonb(json!({ "fill": i })))
1238+
.unwrap();
1239+
}
1240+
db.wait_for_flush("test").unwrap();
1241+
1242+
// Delete and flush again (tombstone on disk)
1243+
db.delete("test", &id).unwrap();
1244+
for i in 0..MEMTABLE_THRESHOLD {
1245+
db.insert("test", serde_to_jsonb(json!({ "fill_2": i })))
1246+
.unwrap();
1247+
}
1248+
db.wait_for_flush("test").unwrap();
1249+
1250+
let results: HashMap<String, Value> = db
1251+
.scan("test", None, None)
1252+
.unwrap()
1253+
.map(|r| (r.id().to_string(), r.get_value()))
1254+
.collect();
1255+
1256+
assert!(!results.contains_key(&id));
1257+
}
1258+
1259+
#[test]
1260+
fn test_scan_shadowing() {
1261+
let dir = tempdir().unwrap();
1262+
let mut db = DB::new(
1263+
dir.path().to_str().unwrap(),
1264+
MEMTABLE_THRESHOLD,
1265+
JSTABLE_THRESHOLD,
1266+
INDEX_THRESHOLD,
1267+
Some(1024 * 1024),
1268+
);
1269+
db.create_collection("test").unwrap();
1270+
1271+
// 1. Insert and Flush
1272+
let doc = serde_to_jsonb(json!({"val": 10}));
1273+
let id = db.insert("test", doc).unwrap();
1274+
1275+
// Force flush
1276+
for i in 0..MEMTABLE_THRESHOLD {
1277+
db.insert("test", serde_to_jsonb(json!({ "fill": i })))
1278+
.unwrap();
1279+
}
1280+
db.wait_for_flush("test").unwrap();
1281+
1282+
// 2. Update (in MemTable)
1283+
let doc_updated = serde_to_jsonb(json!({"val": 20}));
1284+
db.update("test", &id, doc_updated.clone()).unwrap();
1285+
1286+
// 3. Scan and Verify
1287+
let results: HashMap<String, Value> = db
1288+
.scan("test", None, None)
1289+
.unwrap()
1290+
.map(|r| (r.id().to_string(), r.get_value()))
1291+
.collect();
1292+
1293+
assert_eq!(results.get(&id), Some(&doc_updated));
1294+
}
1295+
1296+
#[test]
1297+
fn test_scan_tombstone() {
1298+
let dir = tempdir().unwrap();
1299+
let mut db = DB::new(
1300+
dir.path().to_str().unwrap(),
1301+
MEMTABLE_THRESHOLD,
1302+
JSTABLE_THRESHOLD,
1303+
INDEX_THRESHOLD,
1304+
Some(1024 * 1024),
1305+
);
1306+
db.create_collection("test").unwrap();
1307+
1308+
let doc = serde_to_jsonb(json!({"val": 10}));
1309+
let id = db.insert("test", doc).unwrap();
1310+
1311+
// Force flush
1312+
for i in 0..MEMTABLE_THRESHOLD {
1313+
db.insert("test", serde_to_jsonb(json!({ "fill": i })))
1314+
.unwrap();
1315+
}
1316+
db.wait_for_flush("test").unwrap();
1317+
1318+
// Delete (Tombstone in MemTable)
1319+
db.delete("test", &id).unwrap();
1320+
1321+
let results: HashMap<String, Value> = db
1322+
.scan("test", None, None)
1323+
.unwrap()
1324+
.map(|r| (r.id().to_string(), r.get_value()))
1325+
.collect();
1326+
1327+
assert!(!results.contains_key(&id));
1328+
}
1329+
1330+
#[test]
1331+
fn test_scan_predicate_and_projection() {
1332+
use crate::expression::{BinaryOperator, Expression};
1333+
1334+
let dir = tempdir().unwrap();
1335+
let mut db = DB::new(
1336+
dir.path().to_str().unwrap(),
1337+
MEMTABLE_THRESHOLD,
1338+
JSTABLE_THRESHOLD,
1339+
INDEX_THRESHOLD,
1340+
Some(1024 * 1024),
1341+
);
1342+
db.create_collection("test").unwrap();
1343+
1344+
db.insert("test", serde_to_jsonb(json!({"a": 5, "b": 10})))
1345+
.unwrap();
1346+
db.insert("test", serde_to_jsonb(json!({"a": 15, "b": 20})))
1347+
.unwrap();
1348+
1349+
// Predicate: a > 10
1350+
let predicate = Expression::Binary {
1351+
left: Box::new(Expression::FieldReference(vec!["a"], "a")),
1352+
op: BinaryOperator::Gt,
1353+
right: Box::new(Expression::Literal(serde_to_jsonb(json!(10)))),
1354+
};
1355+
1356+
// Projection: b
1357+
let projections = vec![Expression::FieldReference(vec!["b"], "b")];
1358+
1359+
let results: Vec<Value> = db
1360+
.scan("test", Some(predicate), Some(projections))
1361+
.unwrap()
1362+
.map(|r| r.get_value())
1363+
.collect();
1364+
1365+
assert_eq!(results.len(), 1);
1366+
let doc = &results[0];
1367+
// Result should be {"b": 20}
1368+
if let Value::Object(obj) = doc {
1369+
assert_eq!(obj.get("b"), Some(&serde_to_jsonb(json!(20))));
1370+
assert!(!obj.contains_key("a"));
1371+
} else {
1372+
panic!("Expected object");
1373+
}
1374+
}
1375+
1376+
#[test]
1377+
fn test_recovery_comprehensive() {
1378+
let dir = tempdir().unwrap();
1379+
let db_path = dir.path().to_str().unwrap();
1380+
1381+
{
1382+
let mut db = DB::new(
1383+
db_path,
1384+
MEMTABLE_THRESHOLD,
1385+
JSTABLE_THRESHOLD,
1386+
INDEX_THRESHOLD,
1387+
Some(1024 * 1024),
1388+
);
1389+
db.create_collection("test").unwrap();
1390+
let id = db
1391+
.insert("test", serde_to_jsonb(json!({"val": 1})))
1392+
.unwrap();
1393+
db.update("test", &id, serde_to_jsonb(json!({"val": 2})))
1394+
.unwrap();
1395+
db.delete("test", &id).unwrap();
1396+
}
1397+
1398+
{
1399+
let db = DB::new(
1400+
db_path,
1401+
MEMTABLE_THRESHOLD,
1402+
JSTABLE_THRESHOLD,
1403+
INDEX_THRESHOLD,
1404+
Some(1024 * 1024),
1405+
);
1406+
let results: HashMap<String, Value> = db
1407+
.scan("test", None, None)
1408+
.unwrap()
1409+
.map(|r| (r.id().to_string(), r.get_value()))
1410+
.collect();
1411+
assert!(results.is_empty());
1412+
}
1413+
}
1414+
1415+
#[test]
1416+
fn test_sanitize_filename_edge_cases() {
1417+
assert_eq!(sanitize_filename("valid123"), "valid123");
1418+
// implementation replaces non-alphanumeric with _hex
1419+
// '/' is non-alphanumeric.
1420+
let sanitized = sanitize_filename("test/path");
1421+
assert!(sanitized.contains('_'));
1422+
assert!(!sanitized.contains('/'));
1423+
}
11661424
}

0 commit comments

Comments
 (0)