This is an automated email from the ASF dual-hosted git repository.

yuqi1129 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/main by this push:
     new c17fc03b01 [#13447] fix(core): re-insert updated columns at their 
current index in bundled alters (#13448)
c17fc03b01 is described below

commit c17fc03b01730cbdd5727d9964a1b7caa7b32e04
Author: YangJie <[email protected]>
AuthorDate: Sat Oct 10 07:53:22 2026 -0400

    [#13447] fix(core): re-insert updated columns at their current index in 
bundled alters (#13448)
    
    ### What changes were proposed in this pull request?
    
    When an alter updates a column without an explicit
    `UpdateColumnPosition`, `ManagedTableOperations` now puts the column
    back at the list index it was removed from instead of at its stored
    position. The explicit-position path is unchanged.
    
    ### Why are the changes needed?
    
    The stored position goes stale as soon as an earlier change in the same
    alter adds, removes or moves a column. Lance tables in the
    lakehouse-generic catalog accept `deleteColumn` and `renameColumn`
    together, and web-v2's Edit Table sends them in one request. Deleting a
    lower column and renaming a later one then either threw
    `IndexOutOfBoundsException` (renaming the last column) or stored the
    columns in a different order from the Lance dataset (renaming one in the
    middle). In both cases the dataset had already been changed.
    
    Fix: #13447
    
    ### Does this PR introduce _any_ user-facing change?
    
    No.
    
    ### How was this patch tested?
    
    Added
    `TestManagedTableOperations.testAlterTableDeleteAndRenameInOneRequest`,
    which deletes `col1` and renames the last column, then deletes `col1`
    and renames a middle column, and checks the resulting column order. Both
    cases fail against the code before this change.
---
 .../gravitino/catalog/ManagedTableOperations.java  | 15 +++++--
 .../catalog/TestManagedTableOperations.java        | 48 ++++++++++++++++++++++
 2 files changed, 59 insertions(+), 4 deletions(-)

diff --git 
a/core/src/main/java/org/apache/gravitino/catalog/ManagedTableOperations.java 
b/core/src/main/java/org/apache/gravitino/catalog/ManagedTableOperations.java
index 0e226abcbb..64c9af8b9c 100644
--- 
a/core/src/main/java/org/apache/gravitino/catalog/ManagedTableOperations.java
+++ 
b/core/src/main/java/org/apache/gravitino/catalog/ManagedTableOperations.java
@@ -335,10 +335,10 @@ public abstract class ManagedTableOperations implements 
TableCatalog {
           || change instanceof TableChange.UpdateColumnPosition
           || change instanceof TableChange.UpdateColumnNullability
           || change instanceof TableChange.UpdateColumnAutoIncrement) {
-        int i;
+        int removedIndex;
         ColumnEntity oldColumn = null;
-        for (i = 0; i < newColumns.size(); i++) {
-          ColumnEntity col = newColumns.get(i);
+        for (removedIndex = 0; removedIndex < newColumns.size(); 
removedIndex++) {
+          ColumnEntity col = newColumns.get(removedIndex);
           if (col.name().equals(DOT.join(change.fieldName()))) {
             oldColumn = col;
             break;
@@ -407,7 +407,14 @@ public abstract class ManagedTableOperations implements 
TableCatalog {
                 newPosition,
                 newNullable,
                 newAutoIncrement);
-        newColumns.add(newColumn.position(), newColumn);
+        if (newPosition.isPresent()) {
+          newColumns.add(newColumn.position(), newColumn);
+        } else {
+          // Stored positions go stale as sibling changes in the same alter 
add,
+          // remove or move columns, so without an explicit position change the
+          // column goes back to the list index it was removed from.
+          newColumns.add(removedIndex, newColumn);
+        }
 
       } else if (change instanceof TableChange.DeleteColumn deleteColumn) {
         boolean removed =
diff --git 
a/core/src/test/java/org/apache/gravitino/catalog/TestManagedTableOperations.java
 
b/core/src/test/java/org/apache/gravitino/catalog/TestManagedTableOperations.java
index c660a40b67..cd0ce98ff8 100644
--- 
a/core/src/test/java/org/apache/gravitino/catalog/TestManagedTableOperations.java
+++ 
b/core/src/test/java/org/apache/gravitino/catalog/TestManagedTableOperations.java
@@ -336,6 +336,54 @@ public class TestManagedTableOperations {
         .build();
   }
 
+  @Test
+  public void testAlterTableDeleteAndRenameInOneRequest() {
+    // Deleting a lower-position column shifts the columns after it, so 
renaming the last one in
+    // the same request must not re-insert it past the end of the list.
+    NameIdentifier lastIdent = createThreeColumnTable("rename_last");
+    Table renamedLast =
+        tableOperations.alterTable(
+            lastIdent,
+            TableChange.deleteColumn(new String[] {"col1"}, false),
+            TableChange.renameColumn(new String[] {"col3"}, "col3_renamed"));
+    Assertions.assertArrayEquals(
+        new String[] {"col2", "col3_renamed"},
+        
Arrays.stream(renamedLast.columns()).map(Column::name).toArray(String[]::new));
+
+    // After deleting col1 in the same request, renaming col2 must not move it 
behind col3.
+    NameIdentifier middleIdent = createThreeColumnTable("rename_middle");
+    Table renamedMiddle =
+        tableOperations.alterTable(
+            middleIdent,
+            TableChange.deleteColumn(new String[] {"col1"}, false),
+            TableChange.renameColumn(new String[] {"col2"}, "col2_renamed"));
+    Assertions.assertArrayEquals(
+        new String[] {"col2_renamed", "col3"},
+        
Arrays.stream(renamedMiddle.columns()).map(Column::name).toArray(String[]::new));
+  }
+
+  private NameIdentifier createThreeColumnTable(String tableName) {
+    NameIdentifier tableIdent =
+        NameIdentifierUtil.ofTable(METALAKE_NAME, CATALOG_NAME, SCHEMA_NAME, 
tableName);
+    Column[] columns =
+        new Column[] {
+          createColumn("col1", Types.StringType.get(), null),
+          createColumn("col2", Types.IntegerType.get(), 
Literals.integerLiteral(1)),
+          createColumn("col3", Types.StringType.get(), null)
+        };
+    tableOperations.createTable(
+        tableIdent,
+        columns,
+        "Test bundled alters",
+        StringIdentifier.newPropertiesWithId(
+            StringIdentifier.fromId(idGenerator.nextId()), 
Collections.emptyMap()),
+        new Transform[0],
+        Distributions.NONE,
+        new SortOrder[0],
+        Indexes.EMPTY_INDEXES);
+    return tableIdent;
+  }
+
   @Test
   public void testAlterTable() {
     NameIdentifier table1Ident =

Reply via email to