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());
+ }
}