github-actions[bot] commented on code in PR #66627:
URL: https://github.com/apache/doris/pull/66627#discussion_r3762381692


##########
fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergNestedColumnEvolution.java:
##########
@@ -87,10 +99,234 @@ public static void addColumn(Table table, 
ConnectorColumnPath path, IcebergColum
 
     /** Drops the nested field at {@code path}; its parent must resolve to a 
struct that contains the leaf. */
     public static void dropColumn(Table table, ConnectorColumnPath path) {
-        ResolvedColumnPath resolvedPath = 
validateNestedStructFieldPath(table.schema(), path, "drop");
-        UpdateSchema updateSchema = table.updateSchema();
-        updateSchema.deleteColumn(resolvedPath.getFullPath());
-        updateSchema.commit();
+        dropColumnSafely(table, path, true);
+    }
+
+    static void dropTopLevelColumn(Table table, String columnName) {
+        dropColumnSafely(table, ConnectorColumnPath.of(columnName), false);
+    }
+
+    private static void dropColumnSafely(
+            Table table, ConnectorColumnPath path, boolean nested) {
+        TableOperations operations = ((HasTableOperations) table).operations();
+        TableMetadata loaded = operations.current();
+        if (loaded == null) {
+            throw new DorisConnectorException("Cannot drop column from an 
unloaded Iceberg table: " + table.name());
+        }
+        ResolvedColumnPath loadedPath = resolveDropPath(loaded.schema(), path, 
nested);
+        Set<Integer> loadedSubtreeIds = fieldSubtreeIds(loadedPath);
+
+        TableMetadata base = operations.refresh();
+        ResolvedColumnPath resolvedPath = validatePinnedDropIdentity(
+                table.name(), path, nested, loaded, loadedSubtreeIds, base);
+        validateNotUsedByRetainedPartitionSpec(base, resolvedPath);
+
+        if (isRestTableOperations(operations)) {
+            commitRestDropWithSpecFence(operations, table.name(), base, 
resolvedPath);
+            return;
+        }
+
+        // Use Iceberg's standard commit so column-scoped writer properties 
and name mappings are transformed.
+        // Direct catalogs atomically reject any metadata change after the 
refresh through their metadata CAS.
+        new BaseTable(operations, table.name()).updateSchema()
+                .deleteColumn(resolvedPath.getFullPath()).commit();
+    }
+
+    private static void commitRestDropWithSpecFence(
+            TableOperations operations, String tableName, TableMetadata base, 
ResolvedColumnPath resolvedPath) {
+        if (base.formatVersion() < 2) {
+            throw new DorisConnectorException(
+                    "Cannot safely drop a column from a format-v1 Iceberg REST 
table: " + tableName);
+        }
+
+        Set<Integer> droppedFieldIds = fieldSubtreeIds(resolvedPath);
+        String fenceSource = findRestFenceSource(base, droppedFieldIds);
+        if (fenceSource == null) {
+            throw new DorisConnectorException(
+                    "Cannot safely fence a column drop in Iceberg REST table: 
" + tableName);
+        }
+        String fenceName = newRestFenceName(base);
+
+        // Validation proved that no retained spec references the drop target, 
so any concurrent new reference
+        // must allocate a partition field ID. Adding a distinct void field 
makes REST assert that ID counter.
+        CapturingTableOperations schemaCapture = new 
CapturingTableOperations(operations, base);
+        new BaseTable(schemaCapture, tableName).updateSchema()
+                .deleteColumn(resolvedPath.getFullPath()).commit();
+
+        CapturingTableOperations fenceCapture = new 
CapturingTableOperations(operations, base);
+        new BaseTable(fenceCapture, tableName).updateSpec()
+                .addField(fenceName, Expressions.transform(fenceSource, 
Transforms.alwaysNull()))

Review Comment:
   [P2] Avoid pinning the fence source against later DROP
   
   On an unpartitioned REST v2 table with columns `(a, b, c, d)`, dropping `d` 
selects `a` here and persists a non-default `void(a)` spec. A later `DROP a` is 
then rejected by `validateNotUsedByRetainedPartitionSpec` solely because of 
this synthetic fence, even though `a` was not user-partitioned. Dropping other 
columns first also keeps selecting `a`; the unique names cause each fence to 
retain another spec/partition-field ID. Thus one successful DROP changes which 
unrelated columns can subsequently be dropped and repeated DROPs grow metadata. 
Please use a fence that does not durably reference a user field (or fail this 
path closed), and add a sequential REST DROP regression.
   



##########
fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergNestedColumnEvolution.java:
##########
@@ -87,10 +99,234 @@ public static void addColumn(Table table, 
ConnectorColumnPath path, IcebergColum
 
     /** Drops the nested field at {@code path}; its parent must resolve to a 
struct that contains the leaf. */
     public static void dropColumn(Table table, ConnectorColumnPath path) {
-        ResolvedColumnPath resolvedPath = 
validateNestedStructFieldPath(table.schema(), path, "drop");
-        UpdateSchema updateSchema = table.updateSchema();
-        updateSchema.deleteColumn(resolvedPath.getFullPath());
-        updateSchema.commit();
+        dropColumnSafely(table, path, true);
+    }
+
+    static void dropTopLevelColumn(Table table, String columnName) {
+        dropColumnSafely(table, ConnectorColumnPath.of(columnName), false);
+    }
+
+    private static void dropColumnSafely(
+            Table table, ConnectorColumnPath path, boolean nested) {
+        TableOperations operations = ((HasTableOperations) table).operations();
+        TableMetadata loaded = operations.current();
+        if (loaded == null) {
+            throw new DorisConnectorException("Cannot drop column from an 
unloaded Iceberg table: " + table.name());
+        }
+        ResolvedColumnPath loadedPath = resolveDropPath(loaded.schema(), path, 
nested);
+        Set<Integer> loadedSubtreeIds = fieldSubtreeIds(loadedPath);
+
+        TableMetadata base = operations.refresh();
+        ResolvedColumnPath resolvedPath = validatePinnedDropIdentity(
+                table.name(), path, nested, loaded, loadedSubtreeIds, base);
+        validateNotUsedByRetainedPartitionSpec(base, resolvedPath);
+
+        if (isRestTableOperations(operations)) {
+            commitRestDropWithSpecFence(operations, table.name(), base, 
resolvedPath);
+            return;
+        }
+
+        // Use Iceberg's standard commit so column-scoped writer properties 
and name mappings are transformed.
+        // Direct catalogs atomically reject any metadata change after the 
refresh through their metadata CAS.
+        new BaseTable(operations, table.name()).updateSchema()
+                .deleteColumn(resolvedPath.getFullPath()).commit();
+    }
+
+    private static void commitRestDropWithSpecFence(
+            TableOperations operations, String tableName, TableMetadata base, 
ResolvedColumnPath resolvedPath) {
+        if (base.formatVersion() < 2) {
+            throw new DorisConnectorException(
+                    "Cannot safely drop a column from a format-v1 Iceberg REST 
table: " + tableName);
+        }
+
+        Set<Integer> droppedFieldIds = fieldSubtreeIds(resolvedPath);
+        String fenceSource = findRestFenceSource(base, droppedFieldIds);
+        if (fenceSource == null) {
+            throw new DorisConnectorException(
+                    "Cannot safely fence a column drop in Iceberg REST table: 
" + tableName);
+        }
+        String fenceName = newRestFenceName(base);
+
+        // Validation proved that no retained spec references the drop target, 
so any concurrent new reference
+        // must allocate a partition field ID. Adding a distinct void field 
makes REST assert that ID counter.
+        CapturingTableOperations schemaCapture = new 
CapturingTableOperations(operations, base);
+        new BaseTable(schemaCapture, tableName).updateSchema()
+                .deleteColumn(resolvedPath.getFullPath()).commit();
+
+        CapturingTableOperations fenceCapture = new 
CapturingTableOperations(operations, base);
+        new BaseTable(fenceCapture, tableName).updateSpec()

Review Comment:
   [P2] Preserve forward-compatible specs during REST DROP
   
   This `updateSpec()` call constructs Iceberg 1.10.1's 
`BaseUpdatePartitionSpec`, whose constructor throws whenever the current spec 
contains an `UnknownTransform`. Consequently, a REST v2 table written by a 
newer engine cannot drop an unrelated column through Doris if its current spec 
has a transform this client does not recognize; the schema-only/direct path 
preserves that spec without invoking this mutation validator. Please generate 
the REST requirement/fence without routing through the current-spec mutation 
validator, and add a regression with an unrelated unknown transform.
   



-- 
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]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to