This is an automated email from the ASF dual-hosted git repository.
yuqi4733 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push:
new e850494a74 [#8955] feat(lance-rest): Support register and deregister
table operations in Lance REST server (#9197)
e850494a74 is described below
commit e850494a7463f2dbca0909309ea4aaac1bdf67f8
Author: Junda Yang <[email protected]>
AuthorDate: Thu Nov 20 23:09:48 2025 -0800
[#8955] feat(lance-rest): Support register and deregister table operations
in Lance REST server (#9197)
### What changes were proposed in this pull request?
This PR adds support for registerTable and deregisterTable operations to
the Lance REST server:
1. New REST endpoints:
- POST /lance/v1/table/{id}/register - Register an existing Lance table
(metadata-only operation)
- POST /lance/v1/table/{id}/deregister - Deregister a Lance table
without deleting physical data
2. Backend implementation:
- Implemented registerTable() and deregisterTable() in
GravitinoLanceNamespaceWrapper to handle Gravitino catalog integration
- Added purgeTable() support in GenericLakehouseCatalogOperations to
delete metadata without removing physical data
- Added register property handling in table creation to skip physical
table creation during registration
3. REST endpoint improvements:
- Refactored createTable endpoints to extract headers using @Context
HttpHeaders instead of individual @HeaderParam annotations
- Added ServiceConstants class for consistent header key definitions
- Removed rootCatalog parameter (simplified to 3-level namespace
requirement)
4. Bug fixes:
- Removed @Encoded annotation from all @PathParam in
LanceNamespaceOperations to allow proper automatic URL decoding by HTTP
clients
- Fixed error handling in loadTable() to properly throw
NoSuchTableException
- Improved error messages in alterTable() and dropTable()
- Strengthened namespace level validation (changed from <= 2 to == 2 for
namespaces, <= 3 to == 3 for tables)
### Why are the changes needed?
1. Primary need: Users need the ability to register existing Lance
tables (that were created outside Gravitino) into the Gravitino catalog
for unified metadata management, and to deregister tables without
deleting the underlying data.
2. Secondary improvements:
- The @Encoded annotation was causing issues with URL encoding/decoding
- most HTTP clients automatically encode special characters, so
disabling automatic decoding was incorrect and would break requests with
encoded namespace delimiters
- Stricter namespace validation prevents ambiguous table identifiers
- Better error handling improves debugging experience
Fix: #8955
### Does this PR introduce _any_ user-facing change?
Yes, new user-facing changes:
New APIs:
- Users can now register existing Lance tables: POST
/lance/v1/table/{catalog}${schema}${table}/register?mode=create&delimiter=$
- Users can now deregister tables without data deletion: POST
/lance/v1/table/{catalog}${schema}${table}/deregister?delimiter=$
### How was this patch tested?
Test locally.
---------
Co-authored-by: Mini Yu <[email protected]>
Co-authored-by: mchades <[email protected]>
---
.../GenericLakehouseCatalogOperations.java | 49 ++++++++++++--
.../GenericLakehouseTablePropertiesMetadata.java | 12 +++-
.../catalog/TableOperationDispatcher.java | 4 ++
.../lance/common/ops/LanceTableOperations.java | 8 ++-
.../gravitino/GravitinoLanceNamespaceWrapper.java | 79 +++++++++++++++++++---
.../apache/gravitino/lance/LanceRESTService.java | 1 +
.../gravitino/lance/service/ServiceConstants.java} | 22 ++----
.../service/rest/LanceNamespaceOperations.java | 11 ++-
.../lance/service/rest/LanceTableOperations.java | 79 +++++++++++++++++++---
9 files changed, 217 insertions(+), 48 deletions(-)
diff --git
a/catalogs/catalog-generic-lakehouse/src/main/java/org/apache/gravitino/catalog/lakehouse/GenericLakehouseCatalogOperations.java
b/catalogs/catalog-generic-lakehouse/src/main/java/org/apache/gravitino/catalog/lakehouse/GenericLakehouseCatalogOperations.java
index 29385ff472..812f74c051 100644
---
a/catalogs/catalog-generic-lakehouse/src/main/java/org/apache/gravitino/catalog/lakehouse/GenericLakehouseCatalogOperations.java
+++
b/catalogs/catalog-generic-lakehouse/src/main/java/org/apache/gravitino/catalog/lakehouse/GenericLakehouseCatalogOperations.java
@@ -221,6 +221,8 @@ public class GenericLakehouseCatalogOperations
.withName(tableEntity.name())
.withComment(tableEntity.getComment())
.build();
+ } catch (NoSuchEntityException e) {
+ throw new NoSuchTableException(e, "Table %s does not exist", ident);
} catch (IOException e) {
throw new RuntimeException("Failed to list tables under schema " +
ident.namespace(), e);
}
@@ -281,6 +283,34 @@ public class GenericLakehouseCatalogOperations
.withAuditInfo(auditInfo)
.build();
store.put(entityToStore);
+
+ // Get the value of register in table properties
+ boolean register =
+ Boolean.parseBoolean(
+ properties.getOrDefault(
+ GenericLakehouseTablePropertiesMetadata.LAKEHOUSE_REGISTER,
"false"));
+ if (register) {
+ // Do not need to create the physical table if this is a registration
operation.
+ // Whether we need to check the existence of the physical table?
+ GenericLakehouseTable.Builder builder =
GenericLakehouseTable.builder();
+ return builder
+ .withName(ident.name())
+ .withColumns(columns)
+ .withComment(comment)
+ .withProperties(properties)
+ .withDistribution(distribution)
+ .withIndexes(indexes)
+ .withAuditInfo(
+ AuditInfo.builder()
+ .withCreator(PrincipalUtils.getCurrentUserName())
+ .withCreateTime(Instant.now())
+ .build())
+ .withPartitioning(partitions)
+ .withSortOrders(sortOrders)
+ .withFormat(LakehouseTableFormat.LANCE.lowerName())
+ .build();
+ }
+
LakehouseCatalogOperations lanceCatalogOperations =
getLakehouseCatalogOperations(newProperties);
return lanceCatalogOperations.createTable(
@@ -335,7 +365,6 @@ public class GenericLakehouseCatalogOperations
@Override
public Table alterTable(NameIdentifier ident, TableChange... changes)
throws NoSuchTableException, IllegalArgumentException {
- Namespace namespace = ident.namespace();
try {
TableEntity tableEntity = store.get(ident, Entity.EntityType.TABLE,
TableEntity.class);
Map<String, String> tableProperties = tableEntity.getProperties();
@@ -343,13 +372,12 @@ public class GenericLakehouseCatalogOperations
getLakehouseCatalogOperations(tableProperties);
return lakehouseCatalogOperations.alterTable(ident, changes);
} catch (IOException e) {
- throw new RuntimeException("Failed to list tables under schema " +
namespace, e);
+ throw new RuntimeException("Failed to alter table " + ident, e);
}
}
@Override
public boolean dropTable(NameIdentifier ident) {
- Namespace namespace = ident.namespace();
try {
TableEntity tableEntity = store.get(ident, Entity.EntityType.TABLE,
TableEntity.class);
LakehouseCatalogOperations lakehouseCatalogOperations =
@@ -359,7 +387,20 @@ public class GenericLakehouseCatalogOperations
LOG.warn("Table {} does not exist, skip dropping it.", ident);
return false;
} catch (IOException e) {
- throw new RuntimeException("Failed to list tables under schema " +
namespace, e);
+ throw new RuntimeException("Failed to drop table: " + ident, e);
+ }
+ }
+
+ @Override
+ public boolean purgeTable(NameIdentifier ident) throws
UnsupportedOperationException {
+ try {
+ // Only delete the metadata entry here. The physical data will not be
deleted.
+ if (!tableExists(ident)) {
+ return false;
+ }
+ return store.delete(ident, Entity.EntityType.TABLE);
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to purge table " + ident, e);
}
}
diff --git
a/catalogs/catalog-generic-lakehouse/src/main/java/org/apache/gravitino/catalog/lakehouse/GenericLakehouseTablePropertiesMetadata.java
b/catalogs/catalog-generic-lakehouse/src/main/java/org/apache/gravitino/catalog/lakehouse/GenericLakehouseTablePropertiesMetadata.java
index f8ca11b0a0..72c1e5bc57 100644
---
a/catalogs/catalog-generic-lakehouse/src/main/java/org/apache/gravitino/catalog/lakehouse/GenericLakehouseTablePropertiesMetadata.java
+++
b/catalogs/catalog-generic-lakehouse/src/main/java/org/apache/gravitino/catalog/lakehouse/GenericLakehouseTablePropertiesMetadata.java
@@ -18,6 +18,7 @@
*/
package org.apache.gravitino.catalog.lakehouse;
+import static
org.apache.gravitino.connector.PropertyEntry.booleanPropertyEntry;
import static org.apache.gravitino.connector.PropertyEntry.enumPropertyEntry;
import static
org.apache.gravitino.connector.PropertyEntry.stringOptionalPropertyEntry;
@@ -32,6 +33,7 @@ public class GenericLakehouseTablePropertiesMetadata extends
BasePropertiesMetad
public static final String LAKEHOUSE_LOCATION = "location";
public static final String LAKEHOUSE_FORMAT = "format";
public static final String LANCE_TABLE_STORAGE_OPTION_PREFIX =
"lance.storage.";
+ public static final String LAKEHOUSE_REGISTER = "register";
private static final Map<String, PropertyEntry<?>> PROPERTIES_METADATA;
@@ -59,7 +61,15 @@ public class GenericLakehouseTablePropertiesMetadata extends
BasePropertiesMetad
false /* immutable */,
null /* default value*/,
false /* hidden */,
- false /* reserved */));
+ false /* reserved */),
+ booleanPropertyEntry(
+ LAKEHOUSE_REGISTER,
+ "Whether this is a table registration operation.",
+ false,
+ true /* immutable */,
+ false /* defaultValue */,
+ false /* hidden */,
+ false));
PROPERTIES_METADATA = Maps.uniqueIndex(propertyEntries,
PropertyEntry::getName);
}
diff --git
a/core/src/main/java/org/apache/gravitino/catalog/TableOperationDispatcher.java
b/core/src/main/java/org/apache/gravitino/catalog/TableOperationDispatcher.java
index e549b806d8..6289bd3404 100644
---
a/core/src/main/java/org/apache/gravitino/catalog/TableOperationDispatcher.java
+++
b/core/src/main/java/org/apache/gravitino/catalog/TableOperationDispatcher.java
@@ -408,6 +408,10 @@ public class TableOperationDispatcher extends
OperationDispatcher implements Tab
RuntimeException.class,
UnsupportedOperationException.class);
+ if (isManagedTable(catalogIdent)) {
+ return droppedFromCatalog;
+ }
+
// For unmanaged table, it could happen that the table:
// 1. Is not found in the catalog (dropped directly from underlying
sources)
// 2. Is found in the catalog but not in the store (not managed by
Gravitino)
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 b8a967cd30..8a356fb135 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
@@ -19,7 +19,9 @@
package org.apache.gravitino.lance.common.ops;
import com.lancedb.lance.namespace.model.CreateTableResponse;
+import com.lancedb.lance.namespace.model.DeregisterTableResponse;
import com.lancedb.lance.namespace.model.DescribeTableResponse;
+import com.lancedb.lance.namespace.model.RegisterTableResponse;
import java.util.Map;
public interface LanceTableOperations {
@@ -32,6 +34,10 @@ public interface LanceTableOperations {
String delimiter,
String tableLocation,
Map<String, String> tableProperties,
- String rootCatalog,
byte[] arrowStreamBody);
+
+ RegisterTableResponse registerTable(
+ String tableId, String mode, String delimiter, Map<String, String>
tableProperties);
+
+ DeregisterTableResponse deregisterTable(String tableId, String delimiter);
}
diff --git
a/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/gravitino/GravitinoLanceNamespaceWrapper.java
b/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/gravitino/GravitinoLanceNamespaceWrapper.java
index d3ddbb0ede..865313b31c 100644
---
a/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/gravitino/GravitinoLanceNamespaceWrapper.java
+++
b/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/gravitino/GravitinoLanceNamespaceWrapper.java
@@ -35,6 +35,7 @@ import
com.lancedb.lance.namespace.model.CreateNamespaceRequest;
import com.lancedb.lance.namespace.model.CreateNamespaceRequest.ModeEnum;
import com.lancedb.lance.namespace.model.CreateNamespaceResponse;
import com.lancedb.lance.namespace.model.CreateTableResponse;
+import com.lancedb.lance.namespace.model.DeregisterTableResponse;
import com.lancedb.lance.namespace.model.DescribeNamespaceResponse;
import com.lancedb.lance.namespace.model.DescribeTableResponse;
import com.lancedb.lance.namespace.model.DropNamespaceRequest;
@@ -42,6 +43,8 @@ import
com.lancedb.lance.namespace.model.DropNamespaceResponse;
import com.lancedb.lance.namespace.model.JsonArrowSchema;
import com.lancedb.lance.namespace.model.ListNamespacesResponse;
import com.lancedb.lance.namespace.model.ListTablesResponse;
+import com.lancedb.lance.namespace.model.RegisterTableRequest;
+import com.lancedb.lance.namespace.model.RegisterTableResponse;
import com.lancedb.lance.namespace.util.CommonUtil;
import com.lancedb.lance.namespace.util.JsonArrowSchemaConverter;
import com.lancedb.lance.namespace.util.PageUtil;
@@ -492,7 +495,7 @@ public class GravitinoLanceNamespaceWrapper extends
NamespaceWrapper
String namespaceId, String delimiter, String pageToken, Integer limit) {
ObjectIdentifier nsId = ObjectIdentifier.of(namespaceId,
Pattern.quote(delimiter));
Preconditions.checkArgument(
- nsId.levels() <= 2, "Expected at most 2-level namespace but got: %s",
nsId.levels());
+ nsId.levels() == 2, "Expected 2-level namespace but got: %s",
nsId.levels());
String catalogName = nsId.levelAtListPos(0);
Catalog catalog = loadAndValidateLakehouseCatalog(catalogName);
String schemaName = nsId.levelAtListPos(1);
@@ -516,7 +519,7 @@ public class GravitinoLanceNamespaceWrapper extends
NamespaceWrapper
public DescribeTableResponse describeTable(String tableId, String delimiter)
{
ObjectIdentifier nsId = ObjectIdentifier.of(tableId,
Pattern.quote(delimiter));
Preconditions.checkArgument(
- nsId.levels() <= 3, "Expected at most 3-level namespace but got: %s",
nsId.levels());
+ nsId.levels() == 3, "Expected at 3-level namespace but got: %s",
nsId.levels());
String catalogName = nsId.levelAtListPos(0);
Catalog catalog = loadAndValidateLakehouseCatalog(catalogName);
@@ -538,17 +541,10 @@ public class GravitinoLanceNamespaceWrapper extends
NamespaceWrapper
String delimiter,
String tableLocation,
Map<String, String> tableProperties,
- String rootCatalog,
byte[] arrowStreamBody) {
ObjectIdentifier nsId = ObjectIdentifier.of(tableId,
Pattern.quote(delimiter));
Preconditions.checkArgument(
- nsId.levels() <= 3, "Expected at most 3-level namespace but got: %s",
nsId.levels());
- if (rootCatalog != null) {
- List<String> levels = nsId.listStyleId();
- List<String> newLevels = Lists.newArrayList(rootCatalog);
- newLevels.addAll(levels);
- nsId = ObjectIdentifier.of(newLevels);
- }
+ nsId.levels() == 3, "Expected at 3-level namespace but got: %s",
nsId.levels());
// Parser column information.
List<Column> columns = Lists.newArrayList();
@@ -614,6 +610,68 @@ public class GravitinoLanceNamespaceWrapper extends
NamespaceWrapper
return response;
}
+ @Override
+ public RegisterTableResponse registerTable(
+ String tableId, String mode, String delimiter, Map<String, String>
tableProperties) {
+ 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 = loadAndValidateLakehouseCatalog(catalogName);
+ NameIdentifier tableIdentifier =
+ NameIdentifier.of(nsId.levelAtListPos(1), nsId.levelAtListPos(2));
+
+ // TODO Support real register API
+ RegisterTableRequest.ModeEnum createMode =
+ RegisterTableRequest.ModeEnum.fromValue(mode.toUpperCase());
+ if (createMode == RegisterTableRequest.ModeEnum.CREATE
+ && catalog.asTableCatalog().tableExists(tableIdentifier)) {
+ throw LanceNamespaceException.conflict(
+ "Table already exists: " + tableId,
+ SchemaAlreadyExistsException.class.getSimpleName(),
+ tableId,
+ CommonUtil.formatCurrentStackTrace());
+ }
+
+ if (createMode == RegisterTableRequest.ModeEnum.OVERWRITE
+ && catalog.asTableCatalog().tableExists(tableIdentifier)) {
+ LOG.info("Overwriting existing table: {}", tableId);
+ catalog.asTableCatalog().dropTable(tableIdentifier);
+ }
+
+ Table t =
+ catalog.asTableCatalog().createTable(tableIdentifier, new Column[] {},
"", tableProperties);
+
+ RegisterTableResponse response = new RegisterTableResponse();
+ response.setProperties(t.properties());
+ response.setLocation(t.properties().get("location"));
+ return response;
+ }
+
+ @Override
+ public DeregisterTableResponse deregisterTable(String tableId, String
delimiter) {
+
+ 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 = loadAndValidateLakehouseCatalog(catalogName);
+
+ NameIdentifier tableIdentifier =
+ NameIdentifier.of(nsId.levelAtListPos(1), nsId.levelAtListPos(2));
+ Table t = catalog.asTableCatalog().loadTable(tableIdentifier);
+ Map<String, String> properties = t.properties();
+ // TODO Support real deregister API.
+ catalog.asTableCatalog().purgeTable(tableIdentifier);
+
+ DeregisterTableResponse response = new DeregisterTableResponse();
+ response.setProperties(properties);
+ response.setLocation(properties.get("location"));
+ return response;
+ }
+
private JsonArrowSchema toJsonArrowSchema(Column[] columns) {
List<Field> fields =
Arrays.stream(columns)
@@ -627,7 +685,6 @@ public class GravitinoLanceNamespaceWrapper extends
NamespaceWrapper
@VisibleForTesting
org.apache.arrow.vector.types.pojo.Schema parseArrowIpcStream(byte[] stream)
{
org.apache.arrow.vector.types.pojo.Schema schema;
-
try (BufferAllocator allocator = new RootAllocator();
ByteArrayInputStream bais = new ByteArrayInputStream(stream);
ArrowStreamReader reader = new ArrowStreamReader(bais, allocator)) {
diff --git
a/lance/lance-rest-server/src/main/java/org/apache/gravitino/lance/LanceRESTService.java
b/lance/lance-rest-server/src/main/java/org/apache/gravitino/lance/LanceRESTService.java
index 8c800e49d6..afab3ab2a7 100644
---
a/lance/lance-rest-server/src/main/java/org/apache/gravitino/lance/LanceRESTService.java
+++
b/lance/lance-rest-server/src/main/java/org/apache/gravitino/lance/LanceRESTService.java
@@ -127,6 +127,7 @@ public class LanceRESTService implements
GravitinoAuxiliaryService {
try {
Constructor<? extends NamespaceWrapper> constructor =
lanceNamespaceBackend.getWrapperClass().getConstructor(LanceConfig.class);
+
return constructor.newInstance(lanceConfig);
} catch (Exception e) {
LOG.error("Error loading namespace implementation for backend type: {}",
backendType, e);
diff --git
a/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/LanceTableOperations.java
b/lance/lance-rest-server/src/main/java/org/apache/gravitino/lance/service/ServiceConstants.java
similarity index 60%
copy from
lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/LanceTableOperations.java
copy to
lance/lance-rest-server/src/main/java/org/apache/gravitino/lance/service/ServiceConstants.java
index b8a967cd30..f39ea2e684 100644
---
a/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/LanceTableOperations.java
+++
b/lance/lance-rest-server/src/main/java/org/apache/gravitino/lance/service/ServiceConstants.java
@@ -16,22 +16,14 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.gravitino.lance.common.ops;
-import com.lancedb.lance.namespace.model.CreateTableResponse;
-import com.lancedb.lance.namespace.model.DescribeTableResponse;
-import java.util.Map;
+package org.apache.gravitino.lance.service;
-public interface LanceTableOperations {
+public class ServiceConstants {
+ public static final String LANCE_HTTP_HEADER_PREFIX = "x-lance-";
- DescribeTableResponse describeTable(String tableId, String delimiter);
-
- CreateTableResponse createTable(
- String tableId,
- String mode,
- String delimiter,
- String tableLocation,
- Map<String, String> tableProperties,
- String rootCatalog,
- byte[] arrowStreamBody);
+ public static final String LANCE_TABLE_LOCATION_HEADER =
+ LANCE_HTTP_HEADER_PREFIX + "table-location";
+ public static final String LANCE_TABLE_PROPERTIES_PREFIX_HEADER =
+ LANCE_HTTP_HEADER_PREFIX + "table-properties";
}
diff --git
a/lance/lance-rest-server/src/main/java/org/apache/gravitino/lance/service/rest/LanceNamespaceOperations.java
b/lance/lance-rest-server/src/main/java/org/apache/gravitino/lance/service/rest/LanceNamespaceOperations.java
index 1ae9637b20..464201b851 100644
---
a/lance/lance-rest-server/src/main/java/org/apache/gravitino/lance/service/rest/LanceNamespaceOperations.java
+++
b/lance/lance-rest-server/src/main/java/org/apache/gravitino/lance/service/rest/LanceNamespaceOperations.java
@@ -33,7 +33,6 @@ import java.util.regex.Pattern;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
-import javax.ws.rs.Encoded;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
@@ -63,7 +62,7 @@ public class LanceNamespaceOperations {
@Timed(name = "list-namespaces." + MetricNames.HTTP_PROCESS_DURATION,
absolute = true)
@ResponseMetered(name = "list-namespaces", absolute = true)
public Response listNamespaces(
- @Encoded @PathParam("id") String namespaceId,
+ @PathParam("id") String namespaceId,
@DefaultValue(NAMESPACE_DELIMITER_DEFAULT) @QueryParam("delimiter")
String delimiter,
@QueryParam("page_token") String pageToken,
@QueryParam("limit") Integer limit) {
@@ -83,7 +82,7 @@ public class LanceNamespaceOperations {
@Timed(name = "describe-namespaces." + MetricNames.HTTP_PROCESS_DURATION,
absolute = true)
@ResponseMetered(name = "describe-namespaces", absolute = true)
public Response describeNamespace(
- @Encoded @PathParam("id") String namespaceId,
+ @PathParam("id") String namespaceId,
@DefaultValue(NAMESPACE_DELIMITER_DEFAULT) @QueryParam("delimiter")
String delimiter) {
try {
DescribeNamespaceResponse response =
@@ -99,7 +98,7 @@ public class LanceNamespaceOperations {
@Timed(name = "create-namespaces." + MetricNames.HTTP_PROCESS_DURATION,
absolute = true)
@ResponseMetered(name = "create-namespaces", absolute = true)
public Response createNamespace(
- @Encoded @PathParam("id") String namespaceId,
+ @PathParam("id") String namespaceId,
@DefaultValue(NAMESPACE_DELIMITER_DEFAULT) @QueryParam("delimiter")
String delimiter,
CreateNamespaceRequest request) {
try {
@@ -124,7 +123,7 @@ public class LanceNamespaceOperations {
@Timed(name = "drop-namespaces." + MetricNames.HTTP_PROCESS_DURATION,
absolute = true)
@ResponseMetered(name = "drop-namespaces", absolute = true)
public Response dropNamespace(
- @Encoded @PathParam("id") String namespaceId,
+ @PathParam("id") String namespaceId,
@DefaultValue(NAMESPACE_DELIMITER_DEFAULT) @QueryParam("delimiter")
String delimiter,
DropNamespaceRequest request) {
try {
@@ -151,7 +150,7 @@ public class LanceNamespaceOperations {
@Timed(name = "namespace-exists." + MetricNames.HTTP_PROCESS_DURATION,
absolute = true)
@ResponseMetered(name = "namespace-exists", absolute = true)
public Response namespaceExists(
- @Encoded @PathParam("id") String namespaceId,
+ @PathParam("id") String namespaceId,
@DefaultValue(NAMESPACE_DELIMITER_DEFAULT) @QueryParam("delimiter")
String delimiter) {
try {
lanceNamespace.asNamespaceOps().namespaceExists(namespaceId,
Pattern.quote(delimiter));
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 690cb8759e..5590eef9bd 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
@@ -19,24 +19,33 @@
package org.apache.gravitino.lance.service.rest;
import static
org.apache.gravitino.lance.common.ops.NamespaceWrapper.NAMESPACE_DELIMITER_DEFAULT;
+import static
org.apache.gravitino.lance.service.ServiceConstants.LANCE_TABLE_LOCATION_HEADER;
+import static
org.apache.gravitino.lance.service.ServiceConstants.LANCE_TABLE_PROPERTIES_PREFIX_HEADER;
import com.codahale.metrics.annotation.ResponseMetered;
import com.codahale.metrics.annotation.Timed;
import com.fasterxml.jackson.core.type.TypeReference;
+import com.google.common.collect.Maps;
import com.lancedb.lance.namespace.model.CreateTableResponse;
+import com.lancedb.lance.namespace.model.DeregisterTableRequest;
+import com.lancedb.lance.namespace.model.DeregisterTableResponse;
import com.lancedb.lance.namespace.model.DescribeTableResponse;
+import com.lancedb.lance.namespace.model.RegisterTableRequest;
+import com.lancedb.lance.namespace.model.RegisterTableResponse;
import com.lancedb.lance.namespace.util.JsonUtil;
import java.util.Map;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
-import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+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;
@@ -81,18 +90,20 @@ public class LanceTableOperations {
@PathParam("id") String tableId,
@QueryParam("mode") @DefaultValue("create") String mode, // create,
exist_ok, overwrite
@QueryParam("delimiter") @DefaultValue(NAMESPACE_DELIMITER_DEFAULT)
String delimiter,
- @HeaderParam("x-lance-table-location") String tableLocation,
- @HeaderParam("x-lance-table-properties") String tableProperties,
- @HeaderParam("x-lance-root-catalog") String rootCatalog,
+ @Context HttpHeaders headers,
byte[] arrowStreamBody) {
try {
+ // Extract table properties from header
+ MultivaluedMap<String, String> headersMap = headers.getRequestHeaders();
+ String tableLocation = headersMap.getFirst(LANCE_TABLE_LOCATION_HEADER);
+ String tableProperties =
headersMap.getFirst(LANCE_TABLE_PROPERTIES_PREFIX_HEADER);
+
Map<String, String> props =
JsonUtil.mapper().readValue(tableProperties, new TypeReference<>()
{});
CreateTableResponse response =
lanceNamespace
.asTableOps()
- .createTable(
- tableId, mode, delimiter, tableLocation, props, rootCatalog,
arrowStreamBody);
+ .createTable(tableId, mode, delimiter, tableLocation, props,
arrowStreamBody);
return Response.ok(response).build();
} catch (Exception e) {
return LanceExceptionMapper.toRESTResponse(tableId, e);
@@ -107,10 +118,13 @@ public class LanceTableOperations {
@PathParam("id") String tableId,
@QueryParam("mode") @DefaultValue("create") String mode, // create,
exist_ok, overwrite
@QueryParam("delimiter") @DefaultValue(NAMESPACE_DELIMITER_DEFAULT)
String delimiter,
- @HeaderParam("x-lance-table-location") String tableLocation,
- @HeaderParam("x-lance-root-catalog") String rootCatalog,
- @HeaderParam("x-lance-table-properties") String tableProperties) {
+ @Context HttpHeaders headers) {
try {
+ // Extract table properties from header
+ MultivaluedMap<String, String> headersMap = headers.getRequestHeaders();
+ String tableLocation = headersMap.getFirst(LANCE_TABLE_LOCATION_HEADER);
+ String tableProperties =
headersMap.getFirst(LANCE_TABLE_PROPERTIES_PREFIX_HEADER);
+
Map<String, String> props =
StringUtils.isBlank(tableProperties)
? Map.of()
@@ -118,7 +132,52 @@ public class LanceTableOperations {
CreateTableResponse response =
lanceNamespace
.asTableOps()
- .createTable(tableId, mode, delimiter, tableLocation, props,
rootCatalog, null);
+ .createTable(tableId, mode, delimiter, tableLocation, props,
null);
+ return Response.ok(response).build();
+ } catch (Exception e) {
+ return LanceExceptionMapper.toRESTResponse(tableId, e);
+ }
+ }
+
+ @POST
+ @Path("/register")
+ @Timed(name = "register-table." + MetricNames.HTTP_PROCESS_DURATION,
absolute = true)
+ @ResponseMetered(name = "register-table", absolute = true)
+ public Response registerTable(
+ @PathParam("id") String tableId,
+ @QueryParam("mode") @DefaultValue("create") String mode, // overwrite or
+ @QueryParam("delimiter") @DefaultValue("$") String delimiter,
+ @Context HttpHeaders headers,
+ RegisterTableRequest registerTableRequest) {
+ try {
+ Map<String, String> props =
+ registerTableRequest.getProperties() == null
+ ? Maps.newHashMap()
+ : Maps.newHashMap(registerTableRequest.getProperties());
+ props.put("register", "true");
+ props.put("location", registerTableRequest.getLocation());
+ props.put("format", "lance");
+
+ RegisterTableResponse response =
+ lanceNamespace.asTableOps().registerTable(tableId, mode, delimiter,
props);
+ return Response.ok(response).build();
+ } catch (Exception e) {
+ return LanceExceptionMapper.toRESTResponse(tableId, e);
+ }
+ }
+
+ @POST
+ @Path("/deregister")
+ @Timed(name = "deregister-table." + MetricNames.HTTP_PROCESS_DURATION,
absolute = true)
+ @ResponseMetered(name = "deregister-table", absolute = true)
+ public Response deregisterTable(
+ @PathParam("id") String tableId,
+ @QueryParam("delimiter") @DefaultValue("$") String delimiter,
+ @Context HttpHeaders headers,
+ DeregisterTableRequest deregisterTableRequest) {
+ try {
+ DeregisterTableResponse response =
+ lanceNamespace.asTableOps().deregisterTable(tableId, delimiter);
return Response.ok(response).build();
} catch (Exception e) {
return LanceExceptionMapper.toRESTResponse(tableId, e);