@@ -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