Skip to content

Commit 8c0aeaf

Browse files
authored
test: cover mixed-format schema evolution reorder reads (#366)
1 parent b7fe7d1 commit 8c0aeaf

2 files changed

Lines changed: 157 additions & 10 deletions

File tree

crates/integration_tests/tests/read_tables.rs

Lines changed: 107 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1519,16 +1519,10 @@ async fn test_read_schema_evolution_rename_column() {
15191519
let (plan, batches) =
15201520
scan_and_read_with_fs_catalog("schema_evolution_rename_column", None).await;
15211521

1522-
let formats: HashSet<&str> = plan
1523-
.splits()
1524-
.iter()
1525-
.flat_map(|split| split.data_files())
1526-
.filter_map(|file| file.file_name.rsplit_once('.').map(|(_, ext)| ext))
1527-
.collect();
1528-
assert_eq!(
1529-
formats,
1530-
HashSet::from(["avro", "orc", "parquet"]),
1531-
"schema_evolution_rename_column should scan all provisioned file formats"
1522+
assert_plan_file_formats(
1523+
&plan,
1524+
&["avro", "orc", "parquet"],
1525+
"schema_evolution_rename_column",
15321526
);
15331527

15341528
let mut rows: Vec<(i32, String)> = Vec::new();
@@ -1686,6 +1680,109 @@ async fn test_read_mixed_format_schema_evolution_drop_column() {
16861680
);
16871681
}
16881682

1683+
/// Test reading a mixed-format table after ALTER COLUMN ... FIRST/AFTER.
1684+
/// Old files keep the original physical column order; new files use moved columns.
1685+
#[tokio::test]
1686+
async 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;
1690+
1691+
assert_plan_file_formats(
1692+
&plan,
1693+
&["avro", "orc", "parquet"],
1694+
"mixed_format_schema_evolution_reorder_move_column",
1695+
);
1696+
1697+
for batch in &batches {
1698+
let schema = batch.schema();
1699+
let field_names: Vec<&str> = schema.fields().iter().map(|f| f.name().as_str()).collect();
1700+
assert_eq!(
1701+
field_names,
1702+
vec!["right_value", "left_value", "id"],
1703+
"Full read should expose the current table schema order"
1704+
);
1705+
}
1706+
1707+
let mut rows: Vec<(i32, String, String)> = Vec::new();
1708+
for batch in &batches {
1709+
let right_value = batch
1710+
.column_by_name("right_value")
1711+
.and_then(|c| c.as_any().downcast_ref::<StringArray>())
1712+
.expect("right_value");
1713+
let left_value = batch
1714+
.column_by_name("left_value")
1715+
.and_then(|c| c.as_any().downcast_ref::<StringArray>())
1716+
.expect("left_value");
1717+
let id = batch
1718+
.column_by_name("id")
1719+
.and_then(|c| c.as_any().downcast_ref::<Int32Array>())
1720+
.expect("id");
1721+
for i in 0..batch.num_rows() {
1722+
rows.push((
1723+
id.value(i),
1724+
left_value.value(i).to_string(),
1725+
right_value.value(i).to_string(),
1726+
));
1727+
}
1728+
}
1729+
rows.sort_by_key(|(id, _, _)| *id);
1730+
1731+
assert_eq!(
1732+
rows,
1733+
vec![
1734+
(1, "parquet-left-1".into(), "parquet-right-1".into()),
1735+
(2, "parquet-left-2".into(), "parquet-right-2".into()),
1736+
(3, "orc-left-3".into(), "orc-right-3".into()),
1737+
(4, "orc-left-4".into(), "orc-right-4".into()),
1738+
(5, "avro-left-5".into(), "avro-right-5".into()),
1739+
(6, "avro-left-6".into(), "avro-right-6".into()),
1740+
],
1741+
"Mixed-format REORDER/MOVE COLUMN should read values by field id, not physical position"
1742+
);
1743+
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;
1749+
let mut projected_rows: Vec<(i32, String)> = Vec::new();
1750+
for batch in &projected_batches {
1751+
let schema = batch.schema();
1752+
let field_names: Vec<&str> = schema.fields().iter().map(|f| f.name().as_str()).collect();
1753+
assert_eq!(
1754+
field_names,
1755+
vec!["id", "right_value"],
1756+
"Projection should follow caller-specified order"
1757+
);
1758+
1759+
let id = batch
1760+
.column_by_name("id")
1761+
.and_then(|c| c.as_any().downcast_ref::<Int32Array>())
1762+
.expect("projected id");
1763+
let right_value = batch
1764+
.column_by_name("right_value")
1765+
.and_then(|c| c.as_any().downcast_ref::<StringArray>())
1766+
.expect("projected right_value");
1767+
for i in 0..batch.num_rows() {
1768+
projected_rows.push((id.value(i), right_value.value(i).to_string()));
1769+
}
1770+
}
1771+
projected_rows.sort_by_key(|(id, _)| *id);
1772+
assert_eq!(
1773+
projected_rows,
1774+
vec![
1775+
(1, "parquet-right-1".into()),
1776+
(2, "parquet-right-2".into()),
1777+
(3, "orc-right-3".into()),
1778+
(4, "orc-right-4".into()),
1779+
(5, "avro-right-5".into()),
1780+
(6, "avro-right-6".into()),
1781+
],
1782+
"Projection should still map reordered old and new files by field id"
1783+
);
1784+
}
1785+
16891786
// ---------------------------------------------------------------------------
16901787
// Complex type integration tests
16911788
// ---------------------------------------------------------------------------

dev/spark/provision.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,56 @@ def main():
690690
"""
691691
)
692692

693+
# ===== Mixed-format Schema Evolution: Reorder/Move Column =====
694+
# Old Parquet files use the original order (id, left_value, right_value).
695+
# ORC and Avro files are written after moving columns; readers should expose
696+
# the current table schema order and map old/new files by field id.
697+
spark.sql(
698+
"""
699+
CREATE TABLE IF NOT EXISTS mixed_format_schema_evolution_reorder_move_column (
700+
id INT,
701+
left_value STRING,
702+
right_value STRING
703+
) USING paimon
704+
TBLPROPERTIES (
705+
'file.format' = 'parquet'
706+
)
707+
"""
708+
)
709+
spark.sql(
710+
"""
711+
INSERT INTO mixed_format_schema_evolution_reorder_move_column VALUES
712+
(1, 'parquet-left-1', 'parquet-right-1'),
713+
(2, 'parquet-left-2', 'parquet-right-2')
714+
"""
715+
)
716+
spark.sql(
717+
"ALTER TABLE mixed_format_schema_evolution_reorder_move_column ALTER COLUMN right_value FIRST"
718+
)
719+
spark.sql(
720+
"ALTER TABLE mixed_format_schema_evolution_reorder_move_column SET TBLPROPERTIES ('file.format' = 'orc')"
721+
)
722+
spark.sql(
723+
"""
724+
INSERT INTO mixed_format_schema_evolution_reorder_move_column VALUES
725+
('orc-right-3', 3, 'orc-left-3'),
726+
('orc-right-4', 4, 'orc-left-4')
727+
"""
728+
)
729+
spark.sql(
730+
"ALTER TABLE mixed_format_schema_evolution_reorder_move_column ALTER COLUMN left_value AFTER right_value"
731+
)
732+
spark.sql(
733+
"ALTER TABLE mixed_format_schema_evolution_reorder_move_column SET TBLPROPERTIES ('file.format' = 'avro')"
734+
)
735+
spark.sql(
736+
"""
737+
INSERT INTO mixed_format_schema_evolution_reorder_move_column VALUES
738+
('avro-right-5', 'avro-left-5', 5),
739+
('avro-right-6', 'avro-left-6', 6)
740+
"""
741+
)
742+
693743
# ===== Complex Types table: ARRAY, MAP, STRUCT =====
694744
spark.sql(
695745
"""

0 commit comments

Comments
 (0)