@@ -144,6 +144,21 @@ fn extract_id_name(batches: &[RecordBatch]) -> Vec<(i32, String)> {
144144 rows
145145}
146146
147+ fn extract_ids ( batches : & [ RecordBatch ] ) -> Vec < i32 > {
148+ let mut ids = Vec :: new ( ) ;
149+ for batch in batches {
150+ let id = batch
151+ . column_by_name ( "id" )
152+ . and_then ( |c| c. as_any ( ) . downcast_ref :: < Int32Array > ( ) )
153+ . expect ( "id" ) ;
154+ for i in 0 ..batch. num_rows ( ) {
155+ ids. push ( id. value ( i) ) ;
156+ }
157+ }
158+ ids. sort ( ) ;
159+ ids
160+ }
161+
147162fn extract_id_name_dt ( batches : & [ RecordBatch ] ) -> Vec < ( i32 , String , String ) > {
148163 let mut rows = Vec :: new ( ) ;
149164 for batch in batches {
@@ -1194,8 +1209,12 @@ fn assert_plan_has_multiple_schema_ids(plan: &Plan, table_name: &str) {
11941209/// Old Parquet files lack the new column; newer ORC/Avro files contain it.
11951210#[ tokio:: test]
11961211async fn test_read_format_schema_evolution_add_column ( ) {
1212+ use paimon:: spec:: { Datum , PredicateBuilder } ;
1213+
11971214 let table_name = "format_schema_evolution_add_column" ;
1198- let ( plan, batches) = scan_and_read_with_fs_catalog ( table_name, None ) . await ;
1215+ let catalog = create_file_system_catalog ( ) ;
1216+ let table = get_table_from_catalog ( & catalog, table_name) . await ;
1217+ let ( plan, batches) = scan_and_read ( & catalog, table_name, None ) . await ;
11991218 assert_plan_file_formats ( & plan, & [ "avro" , "orc" , "parquet" ] , table_name) ;
12001219 assert_plan_has_multiple_schema_ids ( & plan, table_name) ;
12011220
@@ -1235,14 +1254,39 @@ async fn test_read_format_schema_evolution_add_column() {
12351254 ] ,
12361255 "Old Parquet rows should have null age and new ORC/Avro rows should keep age values"
12371256 ) ;
1257+
1258+ let pb = PredicateBuilder :: new ( table. schema ( ) . fields ( ) ) ;
1259+ let filter = pb
1260+ . equal ( "age" , Datum :: Int ( 30 ) )
1261+ . expect ( "Failed to build predicate" ) ;
1262+ let ( _, filtered_batches) =
1263+ scan_and_read_with_projection_and_filter ( & table, Some ( & [ "id" , "name" ] ) , filter) . await ;
1264+ assert_eq ! (
1265+ extract_id_name( & filtered_batches) ,
1266+ vec![ ( 3 , "carol" . to_string( ) ) ] ,
1267+ "Projection plus age filter should return only the matching row"
1268+ ) ;
1269+
1270+ let filter = pb. is_null ( "age" ) . expect ( "Failed to build predicate" ) ;
1271+ let ( _, filtered_batches) =
1272+ scan_and_read_with_projection_and_filter ( & table, Some ( & [ "id" ] ) , filter) . await ;
1273+ assert_eq ! (
1274+ extract_ids( & filtered_batches) ,
1275+ vec![ 1 , 2 ] ,
1276+ "Projection plus age IS NULL should return rows with null added-column values"
1277+ ) ;
12381278}
12391279
12401280/// Test reading mixed-format files after ALTER TABLE ALTER COLUMN TYPE (INT -> BIGINT).
12411281/// Old Parquet files have INT; newer ORC/Avro files have BIGINT.
12421282#[ tokio:: test]
12431283async fn test_read_format_schema_evolution_type_promotion ( ) {
1284+ use paimon:: spec:: { Datum , PredicateBuilder } ;
1285+
12441286 let table_name = "format_schema_evolution_type_promotion" ;
1245- let ( plan, batches) = scan_and_read_with_fs_catalog ( table_name, None ) . await ;
1287+ let catalog = create_file_system_catalog ( ) ;
1288+ let table = get_table_from_catalog ( & catalog, table_name) . await ;
1289+ let ( plan, batches) = scan_and_read ( & catalog, table_name, None ) . await ;
12461290 assert_plan_file_formats ( & plan, & [ "avro" , "orc" , "parquet" ] , table_name) ;
12471291 assert_plan_has_multiple_schema_ids ( & plan, table_name) ;
12481292
@@ -1283,6 +1327,18 @@ async fn test_read_format_schema_evolution_type_promotion() {
12831327 ] ,
12841328 "Old Parquet INT rows should be cast to BIGINT and new ORC/Avro BIGINT rows should match"
12851329 ) ;
1330+
1331+ let pb = PredicateBuilder :: new ( table. schema ( ) . fields ( ) ) ;
1332+ let filter = pb
1333+ . greater_than ( "value" , Datum :: Long ( 250 ) )
1334+ . expect ( "Failed to build predicate" ) ;
1335+ let ( _, filtered_batches) =
1336+ scan_and_read_with_projection_and_filter ( & table, Some ( & [ "id" ] ) , filter) . await ;
1337+ assert_eq ! (
1338+ extract_ids( & filtered_batches) ,
1339+ vec![ 3 , 4 , 5 , 6 ] ,
1340+ "Projection plus promoted BIGINT filter should return matching promoted values"
1341+ ) ;
12861342}
12871343
12881344/// Stats pruning should treat a newly added column as all-NULL for old files.
@@ -1516,14 +1572,14 @@ async fn test_read_schema_evolution_drop_column() {
15161572/// Old files have the old physical field name; reader should map by field id.
15171573#[ tokio:: test]
15181574async fn test_read_schema_evolution_rename_column ( ) {
1519- let ( plan, batches) =
1520- scan_and_read_with_fs_catalog ( "schema_evolution_rename_column" , None ) . await ;
1575+ use paimon:: spec:: { Datum , PredicateBuilder } ;
15211576
1522- assert_plan_file_formats (
1523- & plan,
1524- & [ "avro" , "orc" , "parquet" ] ,
1525- "schema_evolution_rename_column" ,
1526- ) ;
1577+ let catalog = create_file_system_catalog ( ) ;
1578+ let table_name = "schema_evolution_rename_column" ;
1579+ let table = get_table_from_catalog ( & catalog, table_name) . await ;
1580+ let ( plan, batches) = scan_and_read ( & catalog, table_name, None ) . await ;
1581+
1582+ assert_plan_file_formats ( & plan, & [ "avro" , "orc" , "parquet" ] , table_name) ;
15271583
15281584 let mut rows: Vec < ( i32 , String ) > = Vec :: new ( ) ;
15291585 for batch in & batches {
@@ -1558,8 +1614,7 @@ async fn test_read_schema_evolution_rename_column() {
15581614 ) ;
15591615
15601616 let ( _, projected_batches) =
1561- scan_and_read_with_fs_catalog ( "schema_evolution_rename_column" , Some ( & [ "renamed_payload" ] ) )
1562- . await ;
1617+ scan_and_read ( & catalog, table_name, Some ( & [ "renamed_payload" ] ) ) . await ;
15631618 let mut projected_values = Vec :: new ( ) ;
15641619 for batch in & projected_batches {
15651620 assert_eq ! (
@@ -1586,14 +1641,30 @@ async fn test_read_schema_evolution_rename_column() {
15861641 ] ,
15871642 "Projection on renamed column should still use field-id mapping"
15881643 ) ;
1644+
1645+ let pb = PredicateBuilder :: new ( table. schema ( ) . fields ( ) ) ;
1646+ let filter = pb
1647+ . equal ( "renamed_payload" , Datum :: String ( "parquet-old" . into ( ) ) )
1648+ . expect ( "Failed to build predicate" ) ;
1649+ let ( _, filtered_batches) =
1650+ scan_and_read_with_projection_and_filter ( & table, Some ( & [ "id" ] ) , filter) . await ;
1651+ assert_eq ! (
1652+ extract_ids( & filtered_batches) ,
1653+ vec![ 1 ] ,
1654+ "Projection plus filter on renamed column should map old Parquet field ids"
1655+ ) ;
15891656}
15901657
15911658/// Test reading a mixed-format table after ALTER TABLE DROP COLUMN.
15921659/// Old Parquet/ORC data files have the dropped column; new Avro files do not.
15931660#[ tokio:: test]
15941661async fn test_read_mixed_format_schema_evolution_drop_column ( ) {
1662+ use paimon:: spec:: { Datum , PredicateBuilder } ;
1663+
15951664 let table_name = "mixed_format_schema_evolution_drop_column" ;
1596- let ( plan, batches) = scan_and_read_with_fs_catalog ( table_name, None ) . await ;
1665+ let catalog = create_file_system_catalog ( ) ;
1666+ let table = get_table_from_catalog ( & catalog, table_name) . await ;
1667+ let ( plan, batches) = scan_and_read ( & catalog, table_name, None ) . await ;
15971668 assert_plan_file_formats ( & plan, & [ "avro" , "orc" , "parquet" ] , table_name) ;
15981669
15991670 for batch in & batches {
@@ -1632,11 +1703,7 @@ async fn test_read_mixed_format_schema_evolution_drop_column() {
16321703 "Mixed-format DROP COLUMN should expose only remaining columns from all file formats"
16331704 ) ;
16341705
1635- let ( _, projected_batches) = scan_and_read_with_fs_catalog (
1636- "mixed_format_schema_evolution_drop_column" ,
1637- Some ( & [ "name" , "id" ] ) ,
1638- )
1639- . await ;
1706+ let ( _, projected_batches) = scan_and_read ( & catalog, table_name, Some ( & [ "name" , "id" ] ) ) . await ;
16401707
16411708 let mut projected_rows: Vec < ( i32 , String ) > = Vec :: new ( ) ;
16421709 for batch in & projected_batches {
@@ -1678,21 +1745,32 @@ async fn test_read_mixed_format_schema_evolution_drop_column() {
16781745 ] ,
16791746 "Projection should read remaining columns across old and new file schemas"
16801747 ) ;
1748+
1749+ let pb = PredicateBuilder :: new ( table. schema ( ) . fields ( ) ) ;
1750+ let filter = pb
1751+ . equal ( "name" , Datum :: String ( "orc-carol" . into ( ) ) )
1752+ . expect ( "Failed to build predicate" ) ;
1753+ let ( _, filtered_batches) =
1754+ scan_and_read_with_projection_and_filter ( & table, Some ( & [ "id" ] ) , filter) . await ;
1755+ assert_eq ! (
1756+ extract_ids( & filtered_batches) ,
1757+ vec![ 3 ] ,
1758+ "Projection plus filter should read remaining columns after DROP COLUMN"
1759+ ) ;
16811760}
16821761
16831762/// Test reading a mixed-format table after ALTER COLUMN ... FIRST/AFTER.
16841763/// Old files keep the original physical column order; new files use moved columns.
16851764#[ tokio:: test]
16861765async fn test_read_mixed_format_schema_evolution_reorder_move_column ( ) {
1687- let ( plan, batches) =
1688- scan_and_read_with_fs_catalog ( "mixed_format_schema_evolution_reorder_move_column" , None )
1689- . await ;
1766+ use paimon:: spec:: { Datum , PredicateBuilder } ;
16901767
1691- assert_plan_file_formats (
1692- & plan,
1693- & [ "avro" , "orc" , "parquet" ] ,
1694- "mixed_format_schema_evolution_reorder_move_column" ,
1695- ) ;
1768+ let table_name = "mixed_format_schema_evolution_reorder_move_column" ;
1769+ let catalog = create_file_system_catalog ( ) ;
1770+ let table = get_table_from_catalog ( & catalog, table_name) . await ;
1771+ let ( plan, batches) = scan_and_read ( & catalog, table_name, None ) . await ;
1772+
1773+ assert_plan_file_formats ( & plan, & [ "avro" , "orc" , "parquet" ] , table_name) ;
16961774
16971775 for batch in & batches {
16981776 let schema = batch. schema ( ) ;
@@ -1741,11 +1819,8 @@ async fn test_read_mixed_format_schema_evolution_reorder_move_column() {
17411819 "Mixed-format REORDER/MOVE COLUMN should read values by field id, not physical position"
17421820 ) ;
17431821
1744- let ( _, projected_batches) = scan_and_read_with_fs_catalog (
1745- "mixed_format_schema_evolution_reorder_move_column" ,
1746- Some ( & [ "id" , "right_value" ] ) ,
1747- )
1748- . await ;
1822+ let ( _, projected_batches) =
1823+ scan_and_read ( & catalog, table_name, Some ( & [ "id" , "right_value" ] ) ) . await ;
17491824 let mut projected_rows: Vec < ( i32 , String ) > = Vec :: new ( ) ;
17501825 for batch in & projected_batches {
17511826 let schema = batch. schema ( ) ;
@@ -1781,6 +1856,43 @@ async fn test_read_mixed_format_schema_evolution_reorder_move_column() {
17811856 ] ,
17821857 "Projection should still map reordered old and new files by field id"
17831858 ) ;
1859+
1860+ let pb = PredicateBuilder :: new ( table. schema ( ) . fields ( ) ) ;
1861+ let filter = pb
1862+ . equal ( "left_value" , Datum :: String ( "orc-left-3" . into ( ) ) )
1863+ . expect ( "Failed to build predicate" ) ;
1864+ let ( _, filtered_batches) =
1865+ scan_and_read_with_projection_and_filter ( & table, Some ( & [ "right_value" , "id" ] ) , filter)
1866+ . await ;
1867+
1868+ let mut filtered_rows: Vec < ( i32 , String ) > = Vec :: new ( ) ;
1869+ for batch in & filtered_batches {
1870+ let schema = batch. schema ( ) ;
1871+ let field_names: Vec < & str > = schema. fields ( ) . iter ( ) . map ( |f| f. name ( ) . as_str ( ) ) . collect ( ) ;
1872+ assert_eq ! (
1873+ field_names,
1874+ vec![ "right_value" , "id" ] ,
1875+ "Projection plus filter should preserve caller-specified order"
1876+ ) ;
1877+
1878+ let right_value = batch
1879+ . column_by_name ( "right_value" )
1880+ . and_then ( |c| c. as_any ( ) . downcast_ref :: < StringArray > ( ) )
1881+ . expect ( "filtered right_value" ) ;
1882+ let id = batch
1883+ . column_by_name ( "id" )
1884+ . and_then ( |c| c. as_any ( ) . downcast_ref :: < Int32Array > ( ) )
1885+ . expect ( "filtered id" ) ;
1886+ for i in 0 ..batch. num_rows ( ) {
1887+ filtered_rows. push ( ( id. value ( i) , right_value. value ( i) . to_string ( ) ) ) ;
1888+ }
1889+ }
1890+ filtered_rows. sort_by_key ( |( id, _) | * id) ;
1891+ assert_eq ! (
1892+ filtered_rows,
1893+ vec![ ( 3 , "orc-right-3" . into( ) ) ] ,
1894+ "Projection plus filter should map reordered fields by id"
1895+ ) ;
17841896}
17851897
17861898// ---------------------------------------------------------------------------
0 commit comments