From 23c0fcfba34d3ca46b91db7d72607ab59c0a8f1b Mon Sep 17 00:00:00 2001 From: Xiangyi Zhu <82511136+zhuxiangyi@users.noreply.github.com> Date: Sun, 9 Aug 2026 00:21:32 +0800 Subject: [PATCH 1/3] [core] Support sub-field-level data evolution for nested columns Today the smallest evolvable unit is a top-level column, so changing one sub-field of a struct rewrites the whole column. This records a partial struct write as dotted paths in writeCols (e.g. "nest.a") and reassembles the struct across files on read, so updating one sub-field only writes that leaf. - RowType.projectByPaths / leafPaths convert between a partial nested type and its dotted paths, preserving field ids. Fields are emitted in the order the paths are given, exactly like project(List): that order is the physical column layout a data file records in its writeCols, so it must not be normalised to schema order. - DataEvolutionReadPlanner: pure, no-IO planning of the read layout, doing leaf-level matching and nested assembly. Extracting it keeps DataEvolutionSplitRead's reader building thin and makes the layout logic directly unit-testable. - DataEvolutionRow composes a struct whose sub-fields live in several source files; DataEvolutionFileReader carries the plan. - Row-id conflict detection and writeCols resolution work at leaf field id granularity, so a whole-struct write and a sub-field write of the same struct still conflict. Only one level of partial nesting is supported; deeper splits are rejected at write time so a file that later breaks full-table reads can never be committed. Gated by data-evolution.nested-field.enabled (default false). --- docs/generated/core_configuration.html | 6 + .../java/org/apache/paimon/CoreOptions.java | 16 + .../org/apache/paimon/schema/TableSchema.java | 4 +- .../java/org/apache/paimon/types/RowType.java | 161 ++++++++ .../reader/DataEvolutionFileReader.java | 11 + .../paimon/reader/DataEvolutionRow.java | 89 ++++- .../apache/paimon/types/DataTypesTest.java | 184 +++++++++ .../operation/BaseAppendFileStoreWrite.java | 9 +- .../operation/DataEvolutionFileStoreScan.java | 6 + .../operation/DataEvolutionReadPlanner.java | 329 +++++++++++++++ .../operation/DataEvolutionSplitRead.java | 127 +++--- .../commit/RowIdColumnConflictChecker.java | 71 ++-- .../paimon/utils/DataEvolutionUtils.java | 27 +- .../DataEvolutionReadPlannerTest.java | 173 ++++++++ .../RowIdColumnConflictCheckerTest.java | 54 +++ .../table/NestedDataEvolutionTableTest.java | 375 ++++++++++++++++++ .../NestedSubfieldDataEvolutionTableTest.java | 301 ++++++++++++++ 17 files changed, 1836 insertions(+), 107 deletions(-) create mode 100644 paimon-core/src/main/java/org/apache/paimon/operation/DataEvolutionReadPlanner.java create mode 100644 paimon-core/src/test/java/org/apache/paimon/operation/DataEvolutionReadPlannerTest.java create mode 100644 paimon-core/src/test/java/org/apache/paimon/table/NestedDataEvolutionTableTest.java create mode 100644 paimon-core/src/test/java/org/apache/paimon/table/NestedSubfieldDataEvolutionTableTest.java diff --git a/docs/generated/core_configuration.html b/docs/generated/core_configuration.html index 70989a7bc76b..de0596ace90b 100644 --- a/docs/generated/core_configuration.html +++ b/docs/generated/core_configuration.html @@ -512,6 +512,12 @@
Separating this from {@link DataEvolutionSplitRead} keeps the reader-building (IO) thin and
+ * lets the layout logic be unit-tested directly. Only one level of nested composition is supported;
+ * deeper or cross-file splits of a sub-struct throw {@link UnsupportedOperationException}.
+ */
+class DataEvolutionReadPlanner {
+
+ private final RowType readRowType;
+ // for each bunch, the (row-tracked) row type it physically provides
+ private final List