@@ -1347,6 +1347,211 @@ mod tests {
13471347 ) ;
13481348 }
13491349
1350+ // ==================== Multi-table Integration Tests ====================
1351+
1352+ #[ test]
1353+ fn test_multi_table_cross_join ( ) {
1354+ let dir = tempdir ( ) . unwrap ( ) ;
1355+
1356+ // Create file for table "a"
1357+ let file_path_a = dir. path ( ) . join ( "a.jsonl" ) ;
1358+ let mut file_a = File :: create ( file_path_a. clone ( ) ) . unwrap ( ) ;
1359+ writeln ! ( file_a, r#"{{"x": 1}}"# ) . unwrap ( ) ;
1360+ writeln ! ( file_a, r#"{{"x": 2}}"# ) . unwrap ( ) ;
1361+ file_a. sync_all ( ) . unwrap ( ) ;
1362+ drop ( file_a) ;
1363+
1364+ // Create file for table "b"
1365+ let file_path_b = dir. path ( ) . join ( "b.jsonl" ) ;
1366+ let mut file_b = File :: create ( file_path_b. clone ( ) ) . unwrap ( ) ;
1367+ writeln ! ( file_b, r#"{{"y": 10}}"# ) . unwrap ( ) ;
1368+ writeln ! ( file_b, r#"{{"y": 20}}"# ) . unwrap ( ) ;
1369+ file_b. sync_all ( ) . unwrap ( ) ;
1370+ drop ( file_b) ;
1371+
1372+ let mut data_sources = common:: types:: DataSourceRegistry :: new ( ) ;
1373+ data_sources. insert ( "a" . to_string ( ) , common:: types:: DataSource :: File ( file_path_a, "jsonl" . to_string ( ) , "a" . to_string ( ) ) ) ;
1374+ data_sources. insert ( "b" . to_string ( ) , common:: types:: DataSource :: File ( file_path_b, "jsonl" . to_string ( ) , "b" . to_string ( ) ) ) ;
1375+
1376+ let result = run ( r#"SELECT a.x, b.y FROM a CROSS JOIN b"# , data_sources, OutputMode :: Csv ) ;
1377+ assert_eq ! ( result, Ok ( ( ) ) ) ;
1378+
1379+ dir. close ( ) . unwrap ( ) ;
1380+ }
1381+
1382+ #[ test]
1383+ fn test_multi_table_left_join ( ) {
1384+ let dir = tempdir ( ) . unwrap ( ) ;
1385+
1386+ // Create file for table "a"
1387+ let file_path_a = dir. path ( ) . join ( "a.jsonl" ) ;
1388+ let mut file_a = File :: create ( file_path_a. clone ( ) ) . unwrap ( ) ;
1389+ writeln ! ( file_a, r#"{{"id": 1, "x": "hello"}}"# ) . unwrap ( ) ;
1390+ writeln ! ( file_a, r#"{{"id": 2, "x": "world"}}"# ) . unwrap ( ) ;
1391+ writeln ! ( file_a, r#"{{"id": 3, "x": "foo"}}"# ) . unwrap ( ) ;
1392+ file_a. sync_all ( ) . unwrap ( ) ;
1393+ drop ( file_a) ;
1394+
1395+ // Create file for table "b"
1396+ let file_path_b = dir. path ( ) . join ( "b.jsonl" ) ;
1397+ let mut file_b = File :: create ( file_path_b. clone ( ) ) . unwrap ( ) ;
1398+ writeln ! ( file_b, r#"{{"id": 1, "y": "alpha"}}"# ) . unwrap ( ) ;
1399+ writeln ! ( file_b, r#"{{"id": 3, "y": "beta"}}"# ) . unwrap ( ) ;
1400+ file_b. sync_all ( ) . unwrap ( ) ;
1401+ drop ( file_b) ;
1402+
1403+ let mut data_sources = common:: types:: DataSourceRegistry :: new ( ) ;
1404+ data_sources. insert ( "a" . to_string ( ) , common:: types:: DataSource :: File ( file_path_a, "jsonl" . to_string ( ) , "a" . to_string ( ) ) ) ;
1405+ data_sources. insert ( "b" . to_string ( ) , common:: types:: DataSource :: File ( file_path_b, "jsonl" . to_string ( ) , "b" . to_string ( ) ) ) ;
1406+
1407+ let result = run ( r#"SELECT a.x, b.y FROM a LEFT JOIN b ON a.id = b.id"# , data_sources, OutputMode :: Csv ) ;
1408+ assert_eq ! ( result, Ok ( ( ) ) ) ;
1409+
1410+ dir. close ( ) . unwrap ( ) ;
1411+ }
1412+
1413+ #[ test]
1414+ fn test_multi_table_comma_join ( ) {
1415+ let dir = tempdir ( ) . unwrap ( ) ;
1416+
1417+ // Create file for table "a"
1418+ let file_path_a = dir. path ( ) . join ( "a.jsonl" ) ;
1419+ let mut file_a = File :: create ( file_path_a. clone ( ) ) . unwrap ( ) ;
1420+ writeln ! ( file_a, r#"{{"x": 1}}"# ) . unwrap ( ) ;
1421+ writeln ! ( file_a, r#"{{"x": 2}}"# ) . unwrap ( ) ;
1422+ file_a. sync_all ( ) . unwrap ( ) ;
1423+ drop ( file_a) ;
1424+
1425+ // Create file for table "b"
1426+ let file_path_b = dir. path ( ) . join ( "b.jsonl" ) ;
1427+ let mut file_b = File :: create ( file_path_b. clone ( ) ) . unwrap ( ) ;
1428+ writeln ! ( file_b, r#"{{"y": 10}}"# ) . unwrap ( ) ;
1429+ writeln ! ( file_b, r#"{{"y": 20}}"# ) . unwrap ( ) ;
1430+ file_b. sync_all ( ) . unwrap ( ) ;
1431+ drop ( file_b) ;
1432+
1433+ let mut data_sources = common:: types:: DataSourceRegistry :: new ( ) ;
1434+ data_sources. insert ( "a" . to_string ( ) , common:: types:: DataSource :: File ( file_path_a, "jsonl" . to_string ( ) , "a" . to_string ( ) ) ) ;
1435+ data_sources. insert ( "b" . to_string ( ) , common:: types:: DataSource :: File ( file_path_b, "jsonl" . to_string ( ) , "b" . to_string ( ) ) ) ;
1436+
1437+ let result = run ( r#"SELECT a.x, b.y FROM a, b"# , data_sources, OutputMode :: Csv ) ;
1438+ assert_eq ! ( result, Ok ( ( ) ) ) ;
1439+
1440+ dir. close ( ) . unwrap ( ) ;
1441+ }
1442+
1443+ #[ test]
1444+ fn test_multi_table_unknown_table_error ( ) {
1445+ let dir = tempdir ( ) . unwrap ( ) ;
1446+
1447+ let file_path_a = dir. path ( ) . join ( "a.jsonl" ) ;
1448+ let mut file_a = File :: create ( file_path_a. clone ( ) ) . unwrap ( ) ;
1449+ writeln ! ( file_a, r#"{{"x": 1}}"# ) . unwrap ( ) ;
1450+ file_a. sync_all ( ) . unwrap ( ) ;
1451+ drop ( file_a) ;
1452+
1453+ let mut data_sources = common:: types:: DataSourceRegistry :: new ( ) ;
1454+ data_sources. insert ( "a" . to_string ( ) , common:: types:: DataSource :: File ( file_path_a, "jsonl" . to_string ( ) , "a" . to_string ( ) ) ) ;
1455+
1456+ let result = run ( r#"SELECT * FROM unknown_table"# , data_sources, OutputMode :: Csv ) ;
1457+ assert ! ( result. is_err( ) , "Expected error for unknown table, got Ok" ) ;
1458+
1459+ dir. close ( ) . unwrap ( ) ;
1460+ }
1461+
1462+ #[ test]
1463+ fn test_multi_table_backward_compat ( ) {
1464+ let dir = tempdir ( ) . unwrap ( ) ;
1465+
1466+ let file_path = dir. path ( ) . join ( "compat.jsonl" ) ;
1467+ let mut file = File :: create ( file_path. clone ( ) ) . unwrap ( ) ;
1468+ writeln ! ( file, r#"{{"x": 1}}"# ) . unwrap ( ) ;
1469+ writeln ! ( file, r#"{{"x": 2}}"# ) . unwrap ( ) ;
1470+ file. sync_all ( ) . unwrap ( ) ;
1471+ drop ( file) ;
1472+
1473+ let data_source = common:: types:: DataSource :: File ( file_path, "jsonl" . to_string ( ) , "it" . to_string ( ) ) ;
1474+ let data_sources: common:: types:: DataSourceRegistry = vec ! [ ( "it" . to_string( ) , data_source) ] . into_iter ( ) . collect ( ) ;
1475+
1476+ let result = run ( r#"SELECT * FROM it LIMIT 1"# , data_sources, OutputMode :: Csv ) ;
1477+ assert_eq ! ( result, Ok ( ( ) ) ) ;
1478+
1479+ dir. close ( ) . unwrap ( ) ;
1480+ }
1481+
1482+ #[ test]
1483+ fn test_multi_table_stdin_in_join_right_side_error ( ) {
1484+ let dir = tempdir ( ) . unwrap ( ) ;
1485+
1486+ let file_path_a = dir. path ( ) . join ( "a.jsonl" ) ;
1487+ let mut file_a = File :: create ( file_path_a. clone ( ) ) . unwrap ( ) ;
1488+ writeln ! ( file_a, r#"{{"x": 1}}"# ) . unwrap ( ) ;
1489+ file_a. sync_all ( ) . unwrap ( ) ;
1490+ drop ( file_a) ;
1491+
1492+ let mut data_sources = common:: types:: DataSourceRegistry :: new ( ) ;
1493+ data_sources. insert ( "a" . to_string ( ) , common:: types:: DataSource :: File ( file_path_a, "jsonl" . to_string ( ) , "a" . to_string ( ) ) ) ;
1494+ data_sources. insert ( "b" . to_string ( ) , common:: types:: DataSource :: Stdin ( "jsonl" . to_string ( ) , "b" . to_string ( ) ) ) ;
1495+
1496+ // "b" is stdin and used as the right side of a join — should produce an error
1497+ let result = run ( r#"SELECT a.x, b.y FROM a CROSS JOIN b"# , data_sources, OutputMode :: Csv ) ;
1498+ assert ! ( result. is_err( ) , "Expected error when stdin is on the right side of a join" ) ;
1499+
1500+ dir. close ( ) . unwrap ( ) ;
1501+ }
1502+
1503+ #[ test]
1504+ fn test_multi_table_with_where_filter ( ) {
1505+ let dir = tempdir ( ) . unwrap ( ) ;
1506+
1507+ // Create file for table "a"
1508+ let file_path_a = dir. path ( ) . join ( "a.jsonl" ) ;
1509+ let mut file_a = File :: create ( file_path_a. clone ( ) ) . unwrap ( ) ;
1510+ writeln ! ( file_a, r#"{{"x": 1}}"# ) . unwrap ( ) ;
1511+ writeln ! ( file_a, r#"{{"x": 2}}"# ) . unwrap ( ) ;
1512+ writeln ! ( file_a, r#"{{"x": 3}}"# ) . unwrap ( ) ;
1513+ file_a. sync_all ( ) . unwrap ( ) ;
1514+ drop ( file_a) ;
1515+
1516+ // Create file for table "b"
1517+ let file_path_b = dir. path ( ) . join ( "b.jsonl" ) ;
1518+ let mut file_b = File :: create ( file_path_b. clone ( ) ) . unwrap ( ) ;
1519+ writeln ! ( file_b, r#"{{"y": 2}}"# ) . unwrap ( ) ;
1520+ writeln ! ( file_b, r#"{{"y": 3}}"# ) . unwrap ( ) ;
1521+ writeln ! ( file_b, r#"{{"y": 4}}"# ) . unwrap ( ) ;
1522+ file_b. sync_all ( ) . unwrap ( ) ;
1523+ drop ( file_b) ;
1524+
1525+ let mut data_sources = common:: types:: DataSourceRegistry :: new ( ) ;
1526+ data_sources. insert ( "a" . to_string ( ) , common:: types:: DataSource :: File ( file_path_a, "jsonl" . to_string ( ) , "a" . to_string ( ) ) ) ;
1527+ data_sources. insert ( "b" . to_string ( ) , common:: types:: DataSource :: File ( file_path_b, "jsonl" . to_string ( ) , "b" . to_string ( ) ) ) ;
1528+
1529+ let result = run ( r#"SELECT a.x, b.y FROM a, b WHERE a.x = b.y"# , data_sources, OutputMode :: Csv ) ;
1530+ assert_eq ! ( result, Ok ( ( ) ) ) ;
1531+
1532+ dir. close ( ) . unwrap ( ) ;
1533+ }
1534+
1535+ #[ test]
1536+ fn test_multi_table_case_sensitive_name_error ( ) {
1537+ let dir = tempdir ( ) . unwrap ( ) ;
1538+
1539+ let file_path = dir. path ( ) . join ( "mytable.jsonl" ) ;
1540+ let mut file = File :: create ( file_path. clone ( ) ) . unwrap ( ) ;
1541+ writeln ! ( file, r#"{{"x": 1}}"# ) . unwrap ( ) ;
1542+ file. sync_all ( ) . unwrap ( ) ;
1543+ drop ( file) ;
1544+
1545+ let mut data_sources = common:: types:: DataSourceRegistry :: new ( ) ;
1546+ data_sources. insert ( "MyTable" . to_string ( ) , common:: types:: DataSource :: File ( file_path, "jsonl" . to_string ( ) , "MyTable" . to_string ( ) ) ) ;
1547+
1548+ // Query uses lowercase "mytable" but registry has "MyTable" — should fail
1549+ let result = run ( r#"SELECT * FROM mytable"# , data_sources, OutputMode :: Csv ) ;
1550+ assert ! ( result. is_err( ) , "Expected error for case-sensitive table name mismatch" ) ;
1551+
1552+ dir. close ( ) . unwrap ( ) ;
1553+ }
1554+
13501555 #[ test]
13511556 fn test_squid_count_by_status ( ) {
13521557 let lines = & [
0 commit comments