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

jshao pushed a commit to branch branch-1.1
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/branch-1.1 by this push:
     new 0b9f996612 [Cherry-pick to branch-1.1] [#9113] feat(lance-rest): 
Support drop and rename column for Lance table (#9127) (#9920)
0b9f996612 is described below

commit 0b9f9966127e105e5734a831220df320da2c8caf
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Mon Feb 9 19:34:23 2026 +0800

    [Cherry-pick to branch-1.1] [#9113] feat(lance-rest): Support drop and 
rename column for Lance table (#9127) (#9920)
    
    **Cherry-pick Information:**
    - Original commit: 982604fc44cceb9afb53f8d0f10aceff59c98ff5
    - Target branch: `branch-1.1`
    - Status: ✅ Clean cherry-pick (no conflicts)
    
    ---------
    
    Co-authored-by: Qi Yu <[email protected]>
---
 .../lakehouse/lance/LanceTableOperations.java      | 104 +++++++++------
 .../lakehouse/lance/TestLanceTableOperations.java  |  41 ++++++
 .../lance/common/ops/LanceTableOperations.java     |  10 ++
 .../gravitino/GravitinoLanceTableAlterHandler.java | 140 +++++++++++++++++++++
 .../gravitino/GravitinoLanceTableOperations.java   |  43 +++++++
 .../lance/common/utils/LanceConstants.java         |   1 +
 .../lance/service/rest/LanceTableOperations.java   |  74 +++++++++++
 .../lance/integration/test/LanceRESTServiceIT.java | 104 ++++++++++++++-
 .../rest/TestGravitinoLanceTableOperations.java    |  77 ++++++++++++
 .../service/rest/TestLanceNamespaceOperations.java | 138 ++++++++++++++++++++
 10 files changed, 693 insertions(+), 39 deletions(-)

diff --git 
a/catalogs/catalog-lakehouse-generic/src/main/java/org/apache/gravitino/catalog/lakehouse/lance/LanceTableOperations.java
 
b/catalogs/catalog-lakehouse-generic/src/main/java/org/apache/gravitino/catalog/lakehouse/lance/LanceTableOperations.java
index e5f0519a20..e35beccc47 100644
--- 
a/catalogs/catalog-lakehouse-generic/src/main/java/org/apache/gravitino/catalog/lakehouse/lance/LanceTableOperations.java
+++ 
b/catalogs/catalog-lakehouse-generic/src/main/java/org/apache/gravitino/catalog/lakehouse/lance/LanceTableOperations.java
@@ -28,7 +28,9 @@ import com.lancedb.lance.index.DistanceType;
 import com.lancedb.lance.index.IndexParams;
 import com.lancedb.lance.index.IndexType;
 import com.lancedb.lance.index.vector.VectorIndexParams;
+import com.lancedb.lance.schema.ColumnAlteration;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -40,6 +42,7 @@ import org.apache.gravitino.EntityStore;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.catalog.ManagedSchemaOperations;
 import org.apache.gravitino.catalog.ManagedTableOperations;
+import org.apache.gravitino.connector.GenericTable;
 import org.apache.gravitino.connector.SupportsSchemas;
 import org.apache.gravitino.exceptions.NoSuchSchemaException;
 import org.apache.gravitino.exceptions.NoSuchTableException;
@@ -54,7 +57,6 @@ import 
org.apache.gravitino.rel.expressions.distributions.Distribution;
 import org.apache.gravitino.rel.expressions.sorts.SortOrder;
 import org.apache.gravitino.rel.expressions.transforms.Transform;
 import org.apache.gravitino.rel.indexes.Index;
-import org.apache.gravitino.rel.indexes.Indexes;
 import org.apache.gravitino.storage.IdGenerator;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -160,31 +162,26 @@ public class LanceTableOperations extends 
ManagedTableOperations {
   @Override
   public Table alterTable(NameIdentifier ident, TableChange... changes)
       throws NoSuchSchemaException, TableAlreadyExistsException {
-    // Lance only supports adding indexes for now.
-    boolean onlyAddIndex =
-        Arrays.stream(changes).allMatch(change -> change instanceof 
TableChange.AddIndex);
-    Preconditions.checkArgument(onlyAddIndex, "Only adding indexes is 
supported for Lance tables");
-
-    List<Index> addedIndexes =
-        Arrays.stream(changes)
-            .filter(change -> change instanceof TableChange.AddIndex)
-            .map(
-                change -> {
-                  TableChange.AddIndex addIndexChange = (TableChange.AddIndex) 
change;
-                  return Indexes.IndexImpl.builder()
-                      .withIndexType(addIndexChange.getType())
-                      .withName(addIndexChange.getName())
-                      .withFieldNames(addIndexChange.getFieldNames())
-                      .build();
-                })
-            .collect(Collectors.toList());
 
     Table loadedTable = super.loadTable(ident);
-    addLanceIndex(loadedTable, addedIndexes);
-    // After adding the index to the Lance dataset, we need to update the 
table metadata in
+    long version = handleLanceTableChange(loadedTable, changes);
+    // After making changes to the Lance dataset, we need to update the table 
metadata in
     // Gravitino. If there's any failure during this process, the code will 
throw an exception
     // and the update won't be applied in Gravitino.
-    return super.alterTable(ident, changes);
+    GenericTable table = (GenericTable) super.alterTable(ident, changes);
+    Map<String, String> updatedProperties = new HashMap<>(table.properties());
+    updatedProperties.put(LanceConstants.LANCE_TABLE_VERSION, 
String.valueOf(version));
+    return GenericTable.builder()
+        .withName(table.name())
+        .withColumns(table.columns())
+        .withComment(table.comment())
+        .withProperties(updatedProperties)
+        .withAuditInfo(table.auditInfo())
+        .withPartitioning(table.partitioning())
+        .withSortOrders(table.sortOrder())
+        .withDistribution(table.distribution())
+        .withIndexes(table.index())
+        .build();
   }
 
   @Override
@@ -316,29 +313,60 @@ public class LanceTableOperations extends 
ManagedTableOperations {
     return new org.apache.arrow.vector.types.pojo.Schema(fields);
   }
 
-  private void addLanceIndex(Table table, List<Index> addedIndexes) {
+  /**
+   * Handle the table changes on the underlying Lance dataset.
+   *
+   * <p>Note: this method can't guarantee the atomicity of the operations on 
Lance dataset. For
+   * example, only a subset of changes may be applied if an exception occurs 
during the process.
+   *
+   * @param table the table to be altered
+   * @param changes the changes to be applied
+   * @return the new version id of the Lance dataset after applying the changes
+   */
+  long handleLanceTableChange(Table table, TableChange[] changes) {
     String location = table.properties().get(Table.PROPERTY_LOCATION);
-    try (Dataset dataset = Dataset.open(location, new RootAllocator())) {
-      // For Lance, we only support adding indexes, so in fact, we can't 
handle drop index here.
-      for (Index index : addedIndexes) {
-        IndexType indexType = IndexType.valueOf(index.type().name());
-        IndexParams indexParams = getIndexParamsByIndexType(indexType);
-
-        dataset.createIndex(
-            Arrays.stream(index.fieldNames())
-                .map(field -> String.join(".", field))
-                .collect(Collectors.toList()),
-            indexType,
-            Optional.of(index.name()),
-            indexParams,
-            true);
+    try (Dataset dataset = openDataset(location)) {
+      for (TableChange change : changes) {
+        if (change instanceof TableChange.DeleteColumn deleteColumn) {
+          dataset.dropColumns(List.of(String.join(".", 
deleteColumn.fieldName())));
+        } else if (change instanceof TableChange.AddIndex addIndex) {
+          IndexType indexType = IndexType.valueOf(addIndex.getType().name());
+          IndexParams indexParams = getIndexParamsByIndexType(indexType);
+          dataset.createIndex(
+              Arrays.stream(addIndex.getFieldNames())
+                  .map(field -> String.join(".", field))
+                  .collect(Collectors.toList()),
+              indexType,
+              Optional.of(addIndex.getName()),
+              indexParams,
+              true);
+        } else if (change instanceof TableChange.RenameColumn renameColumn) {
+          ColumnAlteration lanceColumnAlter =
+              new ColumnAlteration.Builder(String.join(".", 
renameColumn.fieldName()))
+                  .rename(renameColumn.getNewName())
+                  .build();
+          dataset.alterColumns(List.of(lanceColumnAlter));
+        } else {
+          // Currently, only column drop/rename and index addition are 
supported.
+          // TODO: Support change column type once we have a clear knowledge 
about the means of
+          // castTo in Lance.
+          throw new UnsupportedOperationException(
+              "Unsupported changes to lance table: " + 
change.getClass().getSimpleName());
+        }
       }
+      return dataset.getVersion().getId();
+    } catch (RuntimeException e) {
+      throw e;
     } catch (Exception e) {
       throw new RuntimeException(
-          "Failed to add indexes to Lance dataset at location " + location, e);
+          "Failed to handle alterations to Lance dataset at location " + 
location, e);
     }
   }
 
+  Dataset openDataset(String location) {
+    return Dataset.open(location, new RootAllocator());
+  }
+
   private IndexParams getIndexParamsByIndexType(IndexType indexType) {
     switch (indexType) {
       case SCALAR:
diff --git 
a/catalogs/catalog-lakehouse-generic/src/test/java/org/apache/gravitino/catalog/lakehouse/lance/TestLanceTableOperations.java
 
b/catalogs/catalog-lakehouse-generic/src/test/java/org/apache/gravitino/catalog/lakehouse/lance/TestLanceTableOperations.java
index e87b85fa6e..ef1d0e76e3 100644
--- 
a/catalogs/catalog-lakehouse-generic/src/test/java/org/apache/gravitino/catalog/lakehouse/lance/TestLanceTableOperations.java
+++ 
b/catalogs/catalog-lakehouse-generic/src/test/java/org/apache/gravitino/catalog/lakehouse/lance/TestLanceTableOperations.java
@@ -19,16 +19,25 @@
 package org.apache.gravitino.catalog.lakehouse.lance;
 
 import static 
org.apache.gravitino.lance.common.utils.LanceConstants.LANCE_CREATION_MODE;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
 
 import com.google.common.collect.Maps;
+import com.lancedb.lance.Dataset;
+import com.lancedb.lance.Version;
+import com.lancedb.lance.index.IndexParams;
+import com.lancedb.lance.index.IndexType;
 import java.util.Map;
 import org.apache.gravitino.EntityStore;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.catalog.ManagedSchemaOperations;
 import org.apache.gravitino.rel.Column;
 import org.apache.gravitino.rel.Table;
+import org.apache.gravitino.rel.TableChange;
 import org.apache.gravitino.rel.expressions.sorts.SortOrder;
 import org.apache.gravitino.rel.expressions.transforms.Transform;
 import org.apache.gravitino.rel.indexes.Index;
@@ -38,6 +47,8 @@ import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
 
 public class TestLanceTableOperations {
 
@@ -89,4 +100,34 @@ public class TestLanceTableOperations {
                 new SortOrder[0],
                 new Index[0]));
   }
+
+  @Test
+  public void testHandleLanceTableChangeRespectsOrder() {
+    Table table = mock(Table.class);
+    when(table.properties()).thenReturn(Map.of(Table.PROPERTY_LOCATION, 
"location"));
+
+    Dataset dataset = mock(Dataset.class);
+    Version version = mock(Version.class);
+    when(dataset.getVersion()).thenReturn(version);
+    when(version.getId()).thenReturn(7L);
+    Mockito.doReturn(dataset).when(lanceTableOps).openDataset("location");
+
+    TableChange[] changes =
+        new TableChange[] {
+          TableChange.renameColumn(new String[] {"old"}, "renamed"),
+          TableChange.addIndex(Index.IndexType.SCALAR, "idx_renamed", new 
String[][] {{"renamed"}}),
+          TableChange.deleteColumn(new String[] {"renamed"}, false)
+        };
+
+    long returnedVersion = lanceTableOps.handleLanceTableChange(table, 
changes);
+    Assertions.assertEquals(7L, returnedVersion);
+
+    InOrder inOrder = Mockito.inOrder(dataset);
+    inOrder.verify(dataset).alterColumns(anyList());
+    inOrder
+        .verify(dataset)
+        .createIndex(anyList(), any(IndexType.class), any(), 
any(IndexParams.class), eq(true));
+    inOrder.verify(dataset).dropColumns(anyList());
+    inOrder.verify(dataset).getVersion();
+  }
 }
diff --git 
a/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/LanceTableOperations.java
 
b/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/LanceTableOperations.java
index 97b65d9bf0..c8b21ef3b2 100644
--- 
a/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/LanceTableOperations.java
+++ 
b/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/LanceTableOperations.java
@@ -113,4 +113,14 @@ public interface LanceTableOperations {
    * @return the response of the drop table operation
    */
   DropTableResponse dropTable(String tableId, String delimiter);
+
+  /**
+   * Alter a table.
+   *
+   * @param tableId table ids are in the format of 
"{namespace}{delimiter}{table_name}"
+   * @param delimiter the delimiter used in the namespace
+   * @param request the request containing alter table details, it can be 
add/drop/alter columns
+   * @return the response of the alter table operation.
+   */
+  Object alterTable(String tableId, String delimiter, Object request);
 }
diff --git 
a/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/gravitino/GravitinoLanceTableAlterHandler.java
 
b/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/gravitino/GravitinoLanceTableAlterHandler.java
new file mode 100644
index 0000000000..5ab08d546e
--- /dev/null
+++ 
b/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/gravitino/GravitinoLanceTableAlterHandler.java
@@ -0,0 +1,140 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.gravitino.lance.common.ops.gravitino;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.lancedb.lance.namespace.model.AlterTableAlterColumnsRequest;
+import com.lancedb.lance.namespace.model.AlterTableAlterColumnsResponse;
+import com.lancedb.lance.namespace.model.AlterTableDropColumnsRequest;
+import com.lancedb.lance.namespace.model.AlterTableDropColumnsResponse;
+import com.lancedb.lance.namespace.model.ColumnAlteration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.lance.common.utils.LanceConstants;
+import org.apache.gravitino.rel.Table;
+import org.apache.gravitino.rel.TableChange;
+
+/**
+ * Handler for altering a Gravitino table. It builds the Gravitino table 
changes based on the
+ * request and applies the changes to the table.
+ *
+ * @param <REQUEST> the type of the request to alter the table, such as add 
column, drop column,
+ *     etc. the request will be converted to Gravitino TableChange and applied 
to the table
+ * @param <RESPONSE> the type of the response after handling the alter table 
request.
+ */
+public interface GravitinoLanceTableAlterHandler<REQUEST, RESPONSE> {
+
+  /**
+   * Build the Gravitino table changes based on the alter table request.
+   *
+   * @param request the request to alter the table
+   * @return the array of Gravitino TableChange to be applied to the table
+   */
+  TableChange[] buildGravitinoTableChange(REQUEST request);
+
+  /**
+   * Apply the Gravitino table changes to the table and return the response.
+   *
+   * @param gravitinoTable the Gravitino table to be altered
+   * @param request the request to alter the table, it can be used to generate 
the response after
+   *     applying the changes
+   * @return the response after handling the alter table request, it can 
contain the details of the
+   *     altered
+   */
+  RESPONSE handle(Table gravitinoTable, REQUEST request);
+
+  @VisibleForTesting
+  class DropColumns
+      implements GravitinoLanceTableAlterHandler<
+          AlterTableDropColumnsRequest, AlterTableDropColumnsResponse> {
+
+    @Override
+    public TableChange[] 
buildGravitinoTableChange(AlterTableDropColumnsRequest request) {
+      return request.getColumns().stream()
+          .map(colName -> TableChange.deleteColumn(new String[] {colName}, 
false))
+          .toArray(TableChange[]::new);
+    }
+
+    @Override
+    public AlterTableDropColumnsResponse handle(
+        Table gravitinoTable, AlterTableDropColumnsRequest request) {
+      AlterTableDropColumnsResponse response = new 
AlterTableDropColumnsResponse();
+      Long version = extractTableVersion(gravitinoTable);
+      if (version != null) {
+        response.setVersion(version);
+      }
+      return response;
+    }
+  }
+
+  private static Long extractTableVersion(Table gravitinoTable) {
+    return 
Optional.ofNullable(gravitinoTable.properties().get(LanceConstants.LANCE_TABLE_VERSION))
+        .map(Long::valueOf)
+        .orElse(null);
+  }
+
+  @VisibleForTesting
+  class AlterColumnsGravitinoLance
+      implements GravitinoLanceTableAlterHandler<
+          AlterTableAlterColumnsRequest, AlterTableAlterColumnsResponse> {
+
+    @Override
+    public TableChange[] 
buildGravitinoTableChange(AlterTableAlterColumnsRequest request) {
+      return buildAlterColumnChanges(request);
+    }
+
+    @Override
+    public AlterTableAlterColumnsResponse handle(
+        Table gravitinoTable, AlterTableAlterColumnsRequest request) {
+      AlterTableAlterColumnsResponse response = new 
AlterTableAlterColumnsResponse();
+      Long version = extractTableVersion(gravitinoTable);
+      if (version != null) {
+        response.setVersion(version);
+      }
+
+      return response;
+    }
+
+    private TableChange[] 
buildAlterColumnChanges(AlterTableAlterColumnsRequest request) {
+      List<ColumnAlteration> columns = request.getAlterations();
+
+      List<TableChange> changes = new ArrayList<>();
+      for (ColumnAlteration column : columns) {
+        // Column name will not be null according to LanceDB spec.
+        String columnName = column.getColumn();
+        String newName = column.getRename();
+        if (StringUtils.isNotBlank(newName)) {
+          changes.add(TableChange.renameColumn(new String[] {columnName}, 
newName));
+        }
+
+        // The format of ColumnAlteration#castTo is unclear, so we will skip 
it now
+        // for more, please refer to: https://shorturl.at/bYI0Z (short url for
+        // github.com/lance-format/lance-namespace)
+        if (StringUtils.isNotBlank(column.getCastTo())) {
+          throw new UnsupportedOperationException(
+              "Altering column data type is not supported yet.");
+        }
+      }
+      return changes.stream().toArray(TableChange[]::new);
+    }
+  }
+}
diff --git 
a/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/gravitino/GravitinoLanceTableOperations.java
 
b/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/gravitino/GravitinoLanceTableOperations.java
index f94ffdcdbd..d5c137cc2a 100644
--- 
a/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/gravitino/GravitinoLanceTableOperations.java
+++ 
b/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/gravitino/GravitinoLanceTableOperations.java
@@ -32,6 +32,8 @@ import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.lancedb.lance.namespace.LanceNamespaceException;
 import com.lancedb.lance.namespace.ObjectIdentifier;
+import com.lancedb.lance.namespace.model.AlterTableAlterColumnsRequest;
+import com.lancedb.lance.namespace.model.AlterTableDropColumnsRequest;
 import com.lancedb.lance.namespace.model.CreateEmptyTableResponse;
 import com.lancedb.lance.namespace.model.CreateTableRequest;
 import com.lancedb.lance.namespace.model.CreateTableRequest.ModeEnum;
@@ -56,15 +58,27 @@ import org.apache.gravitino.Catalog;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.exceptions.NoSuchTableException;
 import org.apache.gravitino.lance.common.ops.LanceTableOperations;
+import 
org.apache.gravitino.lance.common.ops.gravitino.GravitinoLanceTableAlterHandler.AlterColumnsGravitinoLance;
+import 
org.apache.gravitino.lance.common.ops.gravitino.GravitinoLanceTableAlterHandler.DropColumns;
 import org.apache.gravitino.lance.common.utils.ArrowUtils;
 import org.apache.gravitino.lance.common.utils.LancePropertiesUtils;
 import org.apache.gravitino.rel.Column;
 import org.apache.gravitino.rel.Table;
+import org.apache.gravitino.rel.TableChange;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class GravitinoLanceTableOperations implements LanceTableOperations {
 
+  public static final Logger LOG = 
LoggerFactory.getLogger(GravitinoLanceTableOperations.class);
+
   private final GravitinoLanceNamespaceWrapper namespaceWrapper;
 
+  private static final Map<Class<?>, GravitinoLanceTableAlterHandler<?, ?>> 
ALTER_HANDLERS =
+      Map.of(
+          AlterTableDropColumnsRequest.class, new DropColumns(),
+          AlterTableAlterColumnsRequest.class, new 
AlterColumnsGravitinoLance());
+
   public GravitinoLanceTableOperations(GravitinoLanceNamespaceWrapper 
namespaceWrapper) {
     this.namespaceWrapper = namespaceWrapper;
   }
@@ -294,6 +308,35 @@ public class GravitinoLanceTableOperations implements 
LanceTableOperations {
     return response;
   }
 
+  @Override
+  public Object alterTable(String tableId, String delimiter, Object request) {
+    ObjectIdentifier nsId = ObjectIdentifier.of(tableId, 
Pattern.quote(delimiter));
+    Preconditions.checkArgument(
+        nsId.levels() == 3, "Expected at 3-level namespace but got: %s", 
nsId.levels());
+
+    String catalogName = nsId.levelAtListPos(0);
+    Catalog catalog = 
namespaceWrapper.loadAndValidateLakehouseCatalog(catalogName);
+    NameIdentifier tableIdentifier =
+        NameIdentifier.of(nsId.levelAtListPos(1), nsId.levelAtListPos(2));
+
+    GravitinoLanceTableAlterHandler<Object, Object> handler = 
getHandler(request.getClass());
+    if (handler == null) {
+      throw new IllegalArgumentException(
+          "Unsupported alter table request type: " + 
request.getClass().getName());
+    }
+    TableChange[] changes = handler.buildGravitinoTableChange(request);
+
+    Table table = catalog.asTableCatalog().alterTable(tableIdentifier, 
changes);
+
+    return handler.handle(table, request);
+  }
+
+  @SuppressWarnings("unchecked")
+  private static <REQUEST, RESPONSE> GravitinoLanceTableAlterHandler<REQUEST, 
RESPONSE> getHandler(
+      Class<?> requestClass) {
+    return (GravitinoLanceTableAlterHandler<REQUEST, RESPONSE>) 
ALTER_HANDLERS.get(requestClass);
+  }
+
   private List<Column> 
extractColumns(org.apache.arrow.vector.types.pojo.Schema arrowSchema) {
     List<Column> columns = new ArrayList<>();
 
diff --git 
a/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/utils/LanceConstants.java
 
b/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/utils/LanceConstants.java
index 3dd5e6fd60..991c6b8f26 100644
--- 
a/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/utils/LanceConstants.java
+++ 
b/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/utils/LanceConstants.java
@@ -37,6 +37,7 @@ public class LanceConstants {
 
   public static final String LANCE_TABLE_REGISTER = "lance.register";
 
+  public static final String LANCE_TABLE_VERSION = "lance.version";
   // Mark whether it is to create an empty Lance table(no data files)
   public static final String LANCE_TABLE_CREATE_EMPTY = "lance.create-empty";
 
diff --git 
a/lance/lance-rest-server/src/main/java/org/apache/gravitino/lance/service/rest/LanceTableOperations.java
 
b/lance/lance-rest-server/src/main/java/org/apache/gravitino/lance/service/rest/LanceTableOperations.java
index 19d6ae57ec..c71d9969b3 100644
--- 
a/lance/lance-rest-server/src/main/java/org/apache/gravitino/lance/service/rest/LanceTableOperations.java
+++ 
b/lance/lance-rest-server/src/main/java/org/apache/gravitino/lance/service/rest/LanceTableOperations.java
@@ -25,7 +25,13 @@ import static 
org.apache.gravitino.lance.common.utils.LanceConstants.LANCE_TABLE
 
 import com.codahale.metrics.annotation.ResponseMetered;
 import com.codahale.metrics.annotation.Timed;
+import com.google.common.base.Preconditions;
 import com.google.common.collect.Maps;
+import com.lancedb.lance.namespace.model.AlterTableAlterColumnsRequest;
+import com.lancedb.lance.namespace.model.AlterTableAlterColumnsResponse;
+import com.lancedb.lance.namespace.model.AlterTableDropColumnsRequest;
+import com.lancedb.lance.namespace.model.AlterTableDropColumnsResponse;
+import com.lancedb.lance.namespace.model.ColumnAlteration;
 import com.lancedb.lance.namespace.model.CreateEmptyTableRequest;
 import com.lancedb.lance.namespace.model.CreateEmptyTableResponse;
 import com.lancedb.lance.namespace.model.CreateTableRequest;
@@ -55,6 +61,7 @@ import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.gravitino.lance.common.ops.NamespaceWrapper;
 import org.apache.gravitino.lance.common.utils.LanceConstants;
 import org.apache.gravitino.lance.common.utils.SerializationUtils;
@@ -241,6 +248,51 @@ public class LanceTableOperations {
     }
   }
 
+  @POST
+  @Path("/drop_columns")
+  @Timed(name = "drop-columns." + MetricNames.HTTP_PROCESS_DURATION, absolute 
= true)
+  @ResponseMetered(name = "drop-columns", absolute = true)
+  public Response dropColumns(
+      @PathParam("id") String tableId,
+      @QueryParam("delimiter") @DefaultValue("$") String delimiter,
+      @Context HttpHeaders headers,
+      AlterTableDropColumnsRequest alterTableDropColumnsRequest) {
+    try {
+      validateDropColumnsRequest(alterTableDropColumnsRequest);
+      AlterTableDropColumnsResponse response =
+          (AlterTableDropColumnsResponse)
+              lanceNamespace
+                  .asTableOps()
+                  .alterTable(tableId, delimiter, 
alterTableDropColumnsRequest);
+      return Response.ok(response).build();
+    } catch (Exception e) {
+      return LanceExceptionMapper.toRESTResponse(tableId, e);
+    }
+  }
+
+  // TODO: Currently, only column rename is supported in alter columns.
+  @POST
+  @Path("/alter_columns")
+  @Timed(name = "alter-columns." + MetricNames.HTTP_PROCESS_DURATION, absolute 
= true)
+  @ResponseMetered(name = "alter-columns", absolute = true)
+  public Response alterColumns(
+      @PathParam("id") String tableId,
+      @QueryParam("delimiter") @DefaultValue("$") String delimiter,
+      @Context HttpHeaders headers,
+      AlterTableAlterColumnsRequest alterTableAlterColumnsRequest) {
+    try {
+      validateAlterColumnsRequest(alterTableAlterColumnsRequest);
+      AlterTableAlterColumnsResponse response =
+          (AlterTableAlterColumnsResponse)
+              lanceNamespace
+                  .asTableOps()
+                  .alterTable(tableId, delimiter, 
alterTableAlterColumnsRequest);
+      return Response.ok(response).build();
+    } catch (Exception e) {
+      return LanceExceptionMapper.toRESTResponse(tableId, e);
+    }
+  }
+
   private void validateCreateEmptyTableRequest(
       @SuppressWarnings("unused") CreateEmptyTableRequest request) {
     // No specific fields to validate for now
@@ -272,4 +324,26 @@ public class LanceTableOperations {
     // We will ignore the id in the request body since it's already provided 
in the path param
     // No specific fields to validate for now
   }
+
+  private void validateDropColumnsRequest(AlterTableDropColumnsRequest 
request) {
+    Preconditions.checkArgument(
+        !request.getColumns().isEmpty(), "Columns to drop cannot be empty.");
+    Preconditions.checkArgument(
+        request.getColumns().stream().allMatch(StringUtils::isNotBlank),
+        "Columns to drop cannot be blank.");
+  }
+
+  private void validateAlterColumnsRequest(AlterTableAlterColumnsRequest 
request) {
+    Preconditions.checkArgument(
+        !request.getAlterations().isEmpty(), "Columns to alter cannot be 
empty.");
+
+    for (ColumnAlteration alteration : request.getAlterations()) {
+      Preconditions.checkArgument(
+          StringUtils.isBlank(alteration.getCastTo()),
+          "Only RENAME alteration is supported currently.");
+      Preconditions.checkArgument(
+          StringUtils.isNotBlank(alteration.getRename()),
+          "Rename field must be specified when castTo is not provided.");
+    }
+  }
 }
diff --git 
a/lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/integration/test/LanceRESTServiceIT.java
 
b/lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/integration/test/LanceRESTServiceIT.java
index 2060d93dfd..e88e3b07d7 100644
--- 
a/lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/integration/test/LanceRESTServiceIT.java
+++ 
b/lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/integration/test/LanceRESTServiceIT.java
@@ -26,6 +26,12 @@ import com.lancedb.lance.namespace.LanceNamespace;
 import com.lancedb.lance.namespace.LanceNamespaceException;
 import com.lancedb.lance.namespace.LanceNamespaces;
 import com.lancedb.lance.namespace.client.apache.ApiException;
+import com.lancedb.lance.namespace.client.apache.api.TableApi;
+import com.lancedb.lance.namespace.model.AlterTableAlterColumnsRequest;
+import com.lancedb.lance.namespace.model.AlterTableAlterColumnsResponse;
+import com.lancedb.lance.namespace.model.AlterTableDropColumnsRequest;
+import com.lancedb.lance.namespace.model.AlterTableDropColumnsResponse;
+import com.lancedb.lance.namespace.model.ColumnAlteration;
 import com.lancedb.lance.namespace.model.CreateEmptyTableRequest;
 import com.lancedb.lance.namespace.model.CreateEmptyTableResponse;
 import com.lancedb.lance.namespace.model.CreateNamespaceRequest;
@@ -51,6 +57,7 @@ import com.lancedb.lance.namespace.model.RegisterTableRequest;
 import com.lancedb.lance.namespace.model.RegisterTableRequest.ModeEnum;
 import com.lancedb.lance.namespace.model.RegisterTableResponse;
 import com.lancedb.lance.namespace.model.TableExistsRequest;
+import com.lancedb.lance.namespace.rest.RestNamespace;
 import com.lancedb.lance.namespace.rest.RestNamespaceConfig;
 import java.io.File;
 import java.io.IOException;
@@ -69,6 +76,7 @@ import org.apache.arrow.memory.RootAllocator;
 import org.apache.arrow.vector.types.pojo.ArrowType;
 import org.apache.arrow.vector.types.pojo.Field;
 import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.reflect.FieldUtils;
 import org.apache.gravitino.Catalog;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Schema;
@@ -472,7 +480,7 @@ public class LanceRESTServiceIT extends BaseIT {
   }
 
   @Test
-  void testCreateTable() throws IOException, ApiException {
+  void testCreateTable() throws IOException, ApiException, 
IllegalAccessException {
     catalog = createCatalog(CATALOG_NAME);
     createSchema();
 
@@ -597,6 +605,100 @@ public class LanceRESTServiceIT extends BaseIT {
     Set<String> stringSet = listResponse.getTables();
     Assertions.assertEquals(1, stringSet.size());
     Assertions.assertTrue(stringSet.contains(Joiner.on(".").join(ids)));
+
+    // Now try to drop columns in the table
+    AlterTableDropColumnsRequest dropColumnsRequest = new 
AlterTableDropColumnsRequest();
+    dropColumnsRequest.setId(List.of(CATALOG_NAME, SCHEMA_NAME, "table"));
+    dropColumnsRequest.setColumns(List.of("value"));
+
+    // No alterTableDropColumns in Namespace interface, so we need to get 
TableApi via reflection
+    RestNamespace restNamespace = (RestNamespace) ns;
+    TableApi tableApi = (TableApi) FieldUtils.readField(restNamespace, 
"tableApi", true);
+    String delimiter = RestNamespaceConfig.DELIMITER_DEFAULT;
+
+    AlterTableDropColumnsResponse alterTableDropColumnsResponse =
+        Assertions.assertDoesNotThrow(
+            () ->
+                tableApi.alterTableDropColumns(
+                    String.join(delimiter, ids), dropColumnsRequest, 
delimiter));
+    Assertions.assertNotNull(alterTableDropColumnsResponse);
+    Assertions.assertNotNull(alterTableDropColumnsResponse.getVersion());
+
+    describeTableRequest.setId(ids);
+    loadTable = ns.describeTable(describeTableRequest);
+    Assertions.assertNotNull(loadTable);
+    Assertions.assertEquals(newLocation, loadTable.getLocation());
+
+    jsonArrowFields = loadTable.getSchema().getFields();
+    Assertions.assertEquals(1, jsonArrowFields.size());
+    JsonArrowField jsonArrowField = jsonArrowFields.get(0);
+    Assertions.assertEquals("id", jsonArrowField.getName());
+    Assertions.assertEquals("int32", jsonArrowField.getType().getType());
+
+    // Drop a non-existing column should fail
+    AlterTableDropColumnsRequest dropNonExistingColumnsRequest = new 
AlterTableDropColumnsRequest();
+    dropNonExistingColumnsRequest.setId(ids);
+    dropNonExistingColumnsRequest.setColumns(List.of("non_existing_column"));
+    Exception dropColumnException =
+        Assertions.assertThrows(
+            Exception.class,
+            () ->
+                tableApi.alterTableDropColumns(
+                    String.join(delimiter, ids), 
dropNonExistingColumnsRequest, delimiter));
+    Assertions.assertTrue(
+        dropColumnException
+            .getMessage()
+            .contains("Column non_existing_column does not exist in the 
dataset"));
+  }
+
+  @Test
+  void testAlterColumns() throws Exception {
+    catalog = createCatalog(CATALOG_NAME);
+    createSchema();
+
+    String location = tempDir + "/" + "alter_columns/";
+    List<String> ids = List.of(CATALOG_NAME, SCHEMA_NAME, 
"alter_columns_table");
+    org.apache.arrow.vector.types.pojo.Schema schema =
+        new org.apache.arrow.vector.types.pojo.Schema(
+            Arrays.asList(
+                Field.nullable("id", new ArrowType.Int(32, true)),
+                Field.nullable("value", new ArrowType.Utf8())));
+    byte[] body = ArrowUtils.generateIpcStream(schema);
+
+    CreateTableRequest request = new CreateTableRequest();
+    request.setId(ids);
+    request.setLocation(location);
+    request.setProperties(ImmutableMap.of("key1", "v1"));
+    ns.createTable(request, body);
+
+    RestNamespace restNamespace = (RestNamespace) ns;
+    TableApi tableApi = (TableApi) FieldUtils.readField(restNamespace, 
"tableApi", true);
+    String delimiter = RestNamespaceConfig.DELIMITER_DEFAULT;
+
+    AlterTableAlterColumnsRequest alterRequest = new 
AlterTableAlterColumnsRequest();
+    alterRequest.setId(ids);
+    ColumnAlteration columnAlteration = new ColumnAlteration();
+    columnAlteration.setColumn("value");
+    columnAlteration.setRename("value_new");
+    alterRequest.setAlterations(List.of(columnAlteration));
+
+    AlterTableAlterColumnsResponse response =
+        Assertions.assertDoesNotThrow(
+            () ->
+                tableApi.alterTableAlterColumns(
+                    String.join(delimiter, ids), alterRequest, delimiter));
+    Assertions.assertNotNull(response);
+    Assertions.assertNotNull(response.getVersion());
+
+    DescribeTableRequest describeTableRequest = new DescribeTableRequest();
+    describeTableRequest.setId(ids);
+    DescribeTableResponse loadTable = ns.describeTable(describeTableRequest);
+    Assertions.assertNotNull(loadTable);
+
+    List<JsonArrowField> jsonArrowFields = loadTable.getSchema().getFields();
+    Assertions.assertEquals(2, jsonArrowFields.size());
+    Assertions.assertEquals("id", jsonArrowFields.get(0).getName());
+    Assertions.assertEquals("value_new", jsonArrowFields.get(1).getName());
   }
 
   @Test
diff --git 
a/lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/service/rest/TestGravitinoLanceTableOperations.java
 
b/lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/service/rest/TestGravitinoLanceTableOperations.java
new file mode 100644
index 0000000000..2e7047f98a
--- /dev/null
+++ 
b/lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/service/rest/TestGravitinoLanceTableOperations.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.gravitino.lance.service.rest;
+
+import static 
org.apache.gravitino.lance.common.utils.LanceConstants.LANCE_TABLE_VERSION;
+
+import com.lancedb.lance.namespace.model.AlterTableAlterColumnsRequest;
+import com.lancedb.lance.namespace.model.AlterTableAlterColumnsResponse;
+import com.lancedb.lance.namespace.model.AlterTableDropColumnsRequest;
+import com.lancedb.lance.namespace.model.AlterTableDropColumnsResponse;
+import com.lancedb.lance.namespace.model.ColumnAlteration;
+import java.util.List;
+import java.util.Map;
+import 
org.apache.gravitino.lance.common.ops.gravitino.GravitinoLanceTableAlterHandler.AlterColumnsGravitinoLance;
+import 
org.apache.gravitino.lance.common.ops.gravitino.GravitinoLanceTableAlterHandler.DropColumns;
+import org.apache.gravitino.rel.Table;
+import org.apache.gravitino.rel.TableChange;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+class TestGravitinoLanceTableOperations {
+
+  @Test
+  void testDropColumnsHandlerBuildsChangesAndSetsVersion() {
+    AlterTableDropColumnsRequest request = new AlterTableDropColumnsRequest();
+    request.setColumns(List.of("col1", "col2"));
+
+    DropColumns handler = new DropColumns();
+
+    TableChange[] changes = handler.buildGravitinoTableChange(request);
+    Assertions.assertEquals(2, changes.length);
+
+    Table table = Mockito.mock(Table.class);
+    Mockito.when(table.properties()).thenReturn(Map.of(LANCE_TABLE_VERSION, 
"7"));
+
+    AlterTableDropColumnsResponse response = handler.handle(table, request);
+    Assertions.assertEquals(7L, response.getVersion());
+  }
+
+  @Test
+  void testAlterColumnsHandlerBuildsChangesAndSetsVersion() {
+    AlterTableAlterColumnsRequest request = new 
AlterTableAlterColumnsRequest();
+    ColumnAlteration alteration = new ColumnAlteration();
+    alteration.setColumn("c1");
+    alteration.setRename("c1_new");
+    request.setAlterations(List.of(alteration));
+
+    AlterColumnsGravitinoLance handler = new AlterColumnsGravitinoLance();
+
+    TableChange[] changes = handler.buildGravitinoTableChange(request);
+    Assertions.assertEquals(1, changes.length);
+
+    Table table = Mockito.mock(Table.class);
+    Mockito.when(table.properties()).thenReturn(Map.of(LANCE_TABLE_VERSION, 
"3"));
+
+    AlterTableAlterColumnsResponse response = handler.handle(table, request);
+    Assertions.assertEquals(3L, response.getVersion());
+  }
+}
diff --git 
a/lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/service/rest/TestLanceNamespaceOperations.java
 
b/lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/service/rest/TestLanceNamespaceOperations.java
index 4f5f05c8d9..388977b0df 100644
--- 
a/lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/service/rest/TestLanceNamespaceOperations.java
+++ 
b/lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/service/rest/TestLanceNamespaceOperations.java
@@ -30,6 +30,11 @@ import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import com.lancedb.lance.namespace.LanceNamespaceException;
+import com.lancedb.lance.namespace.model.AlterTableAlterColumnsRequest;
+import com.lancedb.lance.namespace.model.AlterTableAlterColumnsResponse;
+import com.lancedb.lance.namespace.model.AlterTableDropColumnsRequest;
+import com.lancedb.lance.namespace.model.AlterTableDropColumnsResponse;
+import com.lancedb.lance.namespace.model.ColumnAlteration;
 import com.lancedb.lance.namespace.model.CreateEmptyTableRequest;
 import com.lancedb.lance.namespace.model.CreateEmptyTableResponse;
 import com.lancedb.lance.namespace.model.CreateNamespaceRequest;
@@ -48,6 +53,7 @@ import 
com.lancedb.lance.namespace.model.ListNamespacesResponse;
 import com.lancedb.lance.namespace.model.RegisterTableRequest;
 import com.lancedb.lance.namespace.model.RegisterTableResponse;
 import java.io.IOException;
+import java.util.List;
 import java.util.regex.Pattern;
 import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.client.Entity;
@@ -774,4 +780,136 @@ public class TestLanceNamespaceOperations extends 
JerseyTest {
         Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), 
resp.getStatus());
     Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE, 
resp.getMediaType());
   }
+
+  @Test
+  void testDropColumns() {
+    String tableIds = "catalog.scheme.alter_table_drop_columns";
+    String delimiter = ".";
+
+    // first try to create a table and drop columns from it
+    AlterTableDropColumnsResponse dropColumnsResponse = new 
AlterTableDropColumnsResponse();
+    dropColumnsResponse.setVersion(2L);
+
+    AlterTableDropColumnsRequest dropColumnsRequest = new 
AlterTableDropColumnsRequest();
+    dropColumnsRequest.setColumns(List.of("id"));
+    when(tableOps.alterTable(any(), any(), 
any(AlterTableDropColumnsRequest.class)))
+        .thenReturn(dropColumnsResponse);
+    Response resp =
+        target(String.format("/v1/table/%s/drop_columns", tableIds))
+            .queryParam("delimiter", delimiter)
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .post(Entity.entity(dropColumnsRequest, 
MediaType.APPLICATION_JSON_TYPE));
+
+    Assertions.assertEquals(Response.Status.OK.getStatusCode(), 
resp.getStatus());
+    Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE, 
resp.getMediaType());
+    AlterTableDropColumnsResponse response = 
resp.readEntity(AlterTableDropColumnsResponse.class);
+    Assertions.assertEquals(dropColumnsResponse.getVersion(), 
response.getVersion());
+
+    // Test empty columns validation
+    AlterTableDropColumnsRequest emptyColumnsRequest = new 
AlterTableDropColumnsRequest();
+    emptyColumnsRequest.setColumns(List.of());
+    resp =
+        target(String.format("/v1/table/%s/drop_columns", tableIds))
+            .queryParam("delimiter", delimiter)
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .post(Entity.entity(emptyColumnsRequest, 
MediaType.APPLICATION_JSON_TYPE));
+    Assertions.assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), 
resp.getStatus());
+    Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE, 
resp.getMediaType());
+    ErrorResponse errorResp = resp.readEntity(ErrorResponse.class);
+    Assertions.assertEquals("Columns to drop cannot be empty.", 
errorResp.getError());
+    Assertions.assertEquals(IllegalArgumentException.class.getSimpleName(), 
errorResp.getType());
+
+    // Test blank column names validation
+    AlterTableDropColumnsRequest blankColumnRequest = new 
AlterTableDropColumnsRequest();
+    blankColumnRequest.setColumns(List.of(" "));
+    resp =
+        target(String.format("/v1/table/%s/drop_columns", tableIds))
+            .queryParam("delimiter", delimiter)
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .post(Entity.entity(blankColumnRequest, 
MediaType.APPLICATION_JSON_TYPE));
+    Assertions.assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), 
resp.getStatus());
+    Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE, 
resp.getMediaType());
+    errorResp = resp.readEntity(ErrorResponse.class);
+    Assertions.assertEquals("Columns to drop cannot be blank.", 
errorResp.getError());
+    Assertions.assertEquals(IllegalArgumentException.class.getSimpleName(), 
errorResp.getType());
+
+    // Test runtime exception
+    Mockito.reset(tableOps);
+    when(tableOps.alterTable(any(), any(), 
any(AlterTableDropColumnsRequest.class)))
+        .thenThrow(new RuntimeException("Runtime exception"));
+    resp =
+        target(String.format("/v1/table/%s/drop_columns", tableIds))
+            .queryParam("delimiter", delimiter)
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .post(Entity.entity(dropColumnsRequest, 
MediaType.APPLICATION_JSON_TYPE));
+    Assertions.assertEquals(
+        Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), 
resp.getStatus());
+    Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE, 
resp.getMediaType());
+
+    // Test No such table exception
+    Mockito.reset(tableOps);
+    when(tableOps.alterTable(any(), any(), 
any(AlterTableDropColumnsRequest.class)))
+        .thenThrow(
+            LanceNamespaceException.notFound(
+                "Table not found", "NoSuchTableException", tableIds, ""));
+    resp =
+        target(String.format("/v1/table/%s/drop_columns", tableIds))
+            .queryParam("delimiter", delimiter)
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .post(Entity.entity(dropColumnsRequest, 
MediaType.APPLICATION_JSON_TYPE));
+    Assertions.assertEquals(Response.Status.NOT_FOUND.getStatusCode(), 
resp.getStatus());
+  }
+
+  @Test
+  void testAlterColumns() {
+    String tableIds = "catalog.scheme.alter_table_alter_columns";
+    String delimiter = ".";
+
+    AlterTableAlterColumnsResponse alterColumnsResponse = new 
AlterTableAlterColumnsResponse();
+    alterColumnsResponse.setVersion(3L);
+
+    AlterTableAlterColumnsRequest alterColumnsRequest = new 
AlterTableAlterColumnsRequest();
+    alterColumnsRequest.setId(List.of("catalog", "scheme", 
"alter_table_alter_columns"));
+    ColumnAlteration columnAlteration = new ColumnAlteration();
+    columnAlteration.setColumn("col1");
+    columnAlteration.setRename("col1_new");
+    alterColumnsRequest.setAlterations(List.of(columnAlteration));
+
+    when(tableOps.alterTable(any(), any(), 
any(AlterTableAlterColumnsRequest.class)))
+        .thenReturn(alterColumnsResponse);
+    Response resp =
+        target(String.format("/v1/table/%s/alter_columns", tableIds))
+            .queryParam("delimiter", delimiter)
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .post(Entity.entity(alterColumnsRequest, 
MediaType.APPLICATION_JSON_TYPE));
+
+    Assertions.assertEquals(Response.Status.OK.getStatusCode(), 
resp.getStatus());
+    Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE, 
resp.getMediaType());
+    AlterTableAlterColumnsResponse response = 
resp.readEntity(AlterTableAlterColumnsResponse.class);
+    Assertions.assertEquals(alterColumnsResponse.getVersion(), 
response.getVersion());
+
+    Mockito.reset(tableOps);
+    when(tableOps.alterTable(any(), any(), 
any(AlterTableAlterColumnsRequest.class)))
+        .thenThrow(new RuntimeException("Runtime exception"));
+    resp =
+        target(String.format("/v1/table/%s/alter_columns", tableIds))
+            .queryParam("delimiter", delimiter)
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .post(Entity.entity(alterColumnsRequest, 
MediaType.APPLICATION_JSON_TYPE));
+    Assertions.assertEquals(
+        Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), 
resp.getStatus());
+    Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE, 
resp.getMediaType());
+
+    Mockito.reset(tableOps);
+    when(tableOps.alterTable(any(), any(), 
any(AlterTableAlterColumnsRequest.class)))
+        .thenThrow(
+            LanceNamespaceException.notFound(
+                "Table not found", "NoSuchTableException", tableIds, ""));
+    resp =
+        target(String.format("/v1/table/%s/alter_columns", tableIds))
+            .queryParam("delimiter", delimiter)
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .post(Entity.entity(alterColumnsRequest, 
MediaType.APPLICATION_JSON_TYPE));
+    Assertions.assertEquals(Response.Status.NOT_FOUND.getStatusCode(), 
resp.getStatus());
+  }
 }

Reply via email to