JingsongLi commented on code in PR #8334:
URL: https://github.com/apache/paimon/pull/8334#discussion_r3464539214


##########
paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/action/DataEvolutionMergeIntoAction.java:
##########
@@ -526,7 +671,17 @@ private void checkSchema(Table source) {
                                         .getTypeRoot()
                                         .getFamilies()
                                         
.contains(DataTypeFamily.BINARY_STRING);
+                // For sub-field-level data evolution, a struct column's 
source value is a partial
+                // (subset) ROW carrying only the updated sub-fields, which is 
not directly
+                // cast-compatible with the full target struct. Accept it when 
every source
+                // sub-field exists in the target struct with a compatible 
cast.
+                boolean partialStructCompatible =

Review Comment:
   This relaxation should be scoped to sub-field writes. Today it also accepts 
whole-column assignments, e.g. `--matched_update_set T.nest=S.nest` where the 
source `S.nest` is `ROW<a>` and the target is `ROW<a,b>`. 
`partialStructCompatible` returns true here, but `writePaths` is still just 
`nest`, so `sourceType` is built as the full target struct and the partial 
RowData is sent to a whole-struct write. That can fail at runtime or create an 
incomplete whole-struct file. Please keep whole-struct assignments on the 
normal full-type compatibility check, and only allow this subset check when the 
column is actually being written through dotted paths such as `nest.a`.



##########
paimon-api/src/main/java/org/apache/paimon/types/RowType.java:
##########
@@ -312,6 +312,144 @@ public RowType project(String... names) {
         return project(Arrays.asList(names));
     }
 
+    /**
+     * Project this row type by a list of (possibly nested) dotted paths, e.g. 
{@code ["f0",
+     * "nest.a"]}. A path without a dot selects the whole top-level field 
(same as {@link
+     * #project(List)}); a dotted path selects only the addressed sub-field of 
a nested {@link
+     * RowType}, preserving field ids and nullability of every level. Schema 
field order is
+     * preserved. This is used by data evolution to reconstruct the partial 
nested schema of a
+     * column-group file from its {@code writeCols}.
+     */
+    public RowType projectByPaths(List<String> paths) {
+        return projectTypeByPaths(this, paths);
+    }
+
+    private static RowType projectTypeByPaths(RowType type, List<String> 
paths) {
+        // group paths by their immediate child name; a child appearing 
without a tail (or also with
+        // a tail) is selected as a whole field
+        Map<String, List<String>> childToSubPaths = new HashMap<>();
+        Set<String> wholeChildren = new HashSet<>();
+        Set<String> fieldNames = new HashSet<>();
+        for (DataField field : type.getFields()) {
+            fieldNames.add(field.name());
+        }
+        for (String path : paths) {
+            int dot = path.indexOf('.');
+            // Prefer an exact field-name match so a column whose name itself 
contains a dot (and
+            // any
+            // plain top-level name) is selected whole; only split into 
head.tail for genuine nested
+            // sub-field paths that do not name a field directly. This keeps 
backward compatibility
+            // with the legacy exact-name project(List).
+            if (dot < 0 || fieldNames.contains(path)) {
+                childToSubPaths.computeIfAbsent(path, k -> new ArrayList<>());
+                wholeChildren.add(path);
+            } else {
+                String head = path.substring(0, dot);
+                String tail = path.substring(dot + 1);
+                childToSubPaths.computeIfAbsent(head, k -> new 
ArrayList<>()).add(tail);
+            }
+        }
+
+        Set<String> matched = new HashSet<>();
+        List<DataField> result = new ArrayList<>();
+        for (DataField field : type.getFields()) {
+            List<String> subPaths = childToSubPaths.get(field.name());
+            if (subPaths == null) {
+                continue;
+            }
+            matched.add(field.name());
+            if (wholeChildren.contains(field.name())
+                    || subPaths.isEmpty()
+                    || !(field.type() instanceof RowType)) {

Review Comment:
   Could we reject dotted paths when the selected head is not a ROW? With the 
current branch, `projectByPaths(Collections.singletonList("id.a"))` falls into 
this arm and returns the whole `id` field. That makes invalid dotted 
`writeCols` look valid to callers such as the conflict checker, and in the 
Flink action an invalid SET target under a scalar can pass path resolution 
before failing later with a less helpful error. Since dotted paths now encode 
physical sub-fields, this should throw unless the head field is a ROW, or the 
whole path matched an exact top-level field name.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to