This is an automated email from the ASF dual-hosted git repository.
mchades pushed a commit to branch branch-lance-namepspace-dev
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/branch-lance-namepspace-dev by
this push:
new 662097ad51 [#8892] feat(Lance-REST-Server): implement namespace
operation APIs for LRS (#8902)
662097ad51 is described below
commit 662097ad51cd44bc031fe02ecff2d16c613c89ba
Author: mchades <[email protected]>
AuthorDate: Sat Oct 25 00:30:04 2025 +0800
[#8892] feat(Lance-REST-Server): implement namespace operation APIs for LRS
(#8902)
### What changes were proposed in this pull request?
implement namespace operation APIs for LRS
### Why are the changes needed?
Fix: #8892
### Does this PR introduce _any_ user-facing change?
yes, new REST APIs added
### How was this patch tested?
not now
---
conf/gravitino-lance-rest-server.conf.template | 9 +-
conf/gravitino.conf.template | 17 +-
.../gravitino/lance/common/config/LanceConfig.java | 17 +-
.../lance/common/ops/LanceNamespaceOperations.java | 8 +-
.../gravitino/GravitinoLanceNamespaceWrapper.java | 332 +++++++++++++++++++--
.../lance/common/config/TestLanceConfig.java | 13 +-
.../apache/gravitino/lance/LanceRESTService.java | 2 +-
.../service/rest/LanceNamespaceOperations.java | 94 +++++-
.../apache/gravitino/server/TestServerConfig.java | 3 +-
9 files changed, 447 insertions(+), 48 deletions(-)
diff --git a/conf/gravitino-lance-rest-server.conf.template
b/conf/gravitino-lance-rest-server.conf.template
index 32609bffca..137daf145d 100644
--- a/conf/gravitino-lance-rest-server.conf.template
+++ b/conf/gravitino-lance-rest-server.conf.template
@@ -40,6 +40,9 @@ gravitino.lance-rest.requestHeaderSize = 131072
# The response header size of the built-in web server
gravitino.lance-rest.responseHeaderSize = 131072
-# THE CONFIGURATION FOR Lance CATALOG
-# The logical Lance catalog served by this REST endpoint
-gravitino.lance-rest.catalog-name = default
+# THE CONFIGURATION FOR Lance namespace backend
+# The backend Lance namespace for Lance REST service, it's recommended to use
Gravitino
+gravitino.lance-rest.namespace-backend = gravitino
+gravitino.lance-rest.uri = http://localhost:8090
+# replace metalake with your metalake name in Gravitino
+# gravitino.lance-rest.metalake-name = metalake
diff --git a/conf/gravitino.conf.template b/conf/gravitino.conf.template
index 418d14f14c..a1fdb005ca 100644
--- a/conf/gravitino.conf.template
+++ b/conf/gravitino.conf.template
@@ -81,8 +81,9 @@ gravitino.authorization.enable = false
gravitino.authorization.serviceAdmins = anonymous
# THE CONFIGURATION FOR AUXILIARY SERVICE
-# Auxiliary service names, separate by ','
+# Auxiliary service names, separate by ',' such as iceberg-rest,lance-rest
gravitino.auxService.names = iceberg-rest
+
# Iceberg REST service classpath
gravitino.iceberg-rest.classpath = iceberg-rest-server/libs,
iceberg-rest-server/conf
# Iceberg REST service host
@@ -93,3 +94,17 @@ gravitino.iceberg-rest.httpPort = 9001
gravitino.iceberg-rest.catalog-backend = memory
# The warehouse directory of Iceberg catalog for Iceberg REST service
gravitino.iceberg-rest.warehouse = /tmp/
+
+# Lance REST service classpath
+gravitino.lance-rest.classpath = lance-rest-server/libs
+# Lance REST service host
+gravitino.lance-rest.host = 0.0.0.0
+# Lance REST service http port
+gravitino.lance-rest.httpPort = 9101
+
+# THE CONFIGURATION FOR Lance namespace backend
+# The backend Lance namespace for Lance REST service, it's recommended to use
Gravitino
+gravitino.lance-rest.namespace-backend = gravitino
+gravitino.lance-rest.uri = http://localhost:8090
+# replace metalake with your metalake name in Gravitino
+# gravitino.lance-rest.metalake-name = metalake
diff --git
a/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/config/LanceConfig.java
b/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/config/LanceConfig.java
index b6614c87ee..3703189ba8 100644
---
a/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/config/LanceConfig.java
+++
b/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/config/LanceConfig.java
@@ -39,29 +39,22 @@ public class LanceConfig extends Config implements
OverwriteDefaultConfig {
public static final String DEFAULT_NAMESPACE_BACKEND = "gravitino";
public static final String DEFAULT_URI = "http://localhost:8090";
- public static final ConfigEntry<String> CATALOG_NAME =
- new ConfigBuilder(LANCE_CONFIG_PREFIX + "catalog-name")
- .doc("Logical Lance catalog served by the REST endpoint")
- .version(ConfigConstants.VERSION_0_1_0)
- .stringConf()
- .createWithDefault("default");
-
public static final ConfigEntry<String> NAMESPACE_BACKEND =
- new ConfigBuilder(LANCE_CONFIG_PREFIX + CONFIG_NAMESPACE_BACKEND)
+ new ConfigBuilder(CONFIG_NAMESPACE_BACKEND)
.doc("The backend implementation for namespace operations")
.version(ConfigConstants.VERSION_0_1_0)
.stringConf()
.createWithDefault(DEFAULT_NAMESPACE_BACKEND);
public static final ConfigEntry<String> METALAKE_NAME =
- new ConfigBuilder(LANCE_CONFIG_PREFIX + CONFIG_METALAKE)
+ new ConfigBuilder(CONFIG_METALAKE)
.doc("The Metalake name for Gravitino namespace backend")
.version(ConfigConstants.VERSION_0_1_0)
.stringConf()
.create();
public static final ConfigEntry<String> NAMESPACE_URI =
- new ConfigBuilder(LANCE_CONFIG_PREFIX + CONFIG_URI)
+ new ConfigBuilder(CONFIG_URI)
.doc("The URI for the namespace backend, e.g., Gravitino server URI")
.version(ConfigConstants.VERSION_0_1_0)
.stringConf()
@@ -76,8 +69,8 @@ public class LanceConfig extends Config implements
OverwriteDefaultConfig {
super(false);
}
- public String getCatalogName() {
- return get(CATALOG_NAME);
+ public String getNamespaceBackend() {
+ return get(NAMESPACE_BACKEND);
}
public String getNamespaceUri() {
diff --git
a/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/LanceNamespaceOperations.java
b/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/LanceNamespaceOperations.java
index 1b5da98ec0..226de4dbd7 100644
---
a/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/LanceNamespaceOperations.java
+++
b/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/LanceNamespaceOperations.java
@@ -32,19 +32,19 @@ public interface LanceNamespaceOperations {
ListNamespacesResponse listNamespaces(
String namespaceId, String delimiter, String pageToken, Integer limit);
- DescribeNamespaceResponse describeNamespace(String id, String delimiter);
+ DescribeNamespaceResponse describeNamespace(String namespaceId, String
delimiter);
CreateNamespaceResponse createNamespace(
- String id,
+ String namespaceId,
String delimiter,
CreateNamespaceRequest.ModeEnum mode,
Map<String, String> properties);
DropNamespaceResponse dropNamespace(
- String id,
+ String namespaceId,
String delimiter,
DropNamespaceRequest.ModeEnum mode,
DropNamespaceRequest.BehaviorEnum behavior);
- void namespaceExists(String id, String delimiter) throws
LanceNamespaceException;
+ void namespaceExists(String namespaceId, String delimiter) throws
LanceNamespaceException;
}
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 59f637b5a1..cb1b85752a 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
@@ -23,6 +23,7 @@ import static
org.apache.gravitino.lance.common.config.LanceConfig.NAMESPACE_URI
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.lancedb.lance.namespace.LanceNamespaceException;
import com.lancedb.lance.namespace.ObjectIdentifier;
@@ -33,16 +34,30 @@ import
com.lancedb.lance.namespace.model.DropNamespaceRequest;
import com.lancedb.lance.namespace.model.DropNamespaceResponse;
import com.lancedb.lance.namespace.model.ListNamespacesResponse;
import com.lancedb.lance.namespace.model.ListTablesResponse;
+import com.lancedb.lance.namespace.util.CommonUtil;
import com.lancedb.lance.namespace.util.PageUtil;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.IntFunction;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import org.apache.gravitino.Catalog;
+import org.apache.gravitino.CatalogChange;
+import org.apache.gravitino.Schema;
+import org.apache.gravitino.SchemaChange;
import org.apache.gravitino.client.GravitinoClient;
+import org.apache.gravitino.exceptions.CatalogAlreadyExistsException;
import org.apache.gravitino.exceptions.NoSuchCatalogException;
+import org.apache.gravitino.exceptions.NoSuchSchemaException;
+import org.apache.gravitino.exceptions.NonEmptyCatalogException;
+import org.apache.gravitino.exceptions.NonEmptySchemaException;
+import org.apache.gravitino.exceptions.SchemaAlreadyExistsException;
import org.apache.gravitino.lance.common.config.LanceConfig;
import org.apache.gravitino.lance.common.ops.LanceNamespaceOperations;
import org.apache.gravitino.lance.common.ops.LanceTableOperations;
@@ -102,7 +117,6 @@ public class GravitinoLanceNamespaceWrapper extends
NamespaceWrapper
List<String> namespaces;
switch (nsId.levels()) {
case 0:
- // List catalogs of type relational and provider generic-lakehouse
namespaces =
Arrays.stream(client.listCatalogsInfo())
.filter(this::isLakehouseCatalog)
@@ -111,16 +125,14 @@ public class GravitinoLanceNamespaceWrapper extends
NamespaceWrapper
break;
case 1:
- // List schemas under the catalog
- String catalogName = nsId.levelAtListPos(0);
- Catalog catalog = client.loadCatalog(catalogName);
- if (!isLakehouseCatalog(catalog)) {
- throw new NoSuchCatalogException("Catalog not found: %s",
catalogName);
- }
-
+ Catalog catalog =
loadAndValidateLakehouseCatalog(nsId.levelAtListPos(0));
namespaces = Lists.newArrayList(catalog.asSchemas().listSchemas());
break;
+ case 2:
+ namespaces = Lists.newArrayList();
+ break;
+
default:
throw new IllegalArgumentException(
"Expected at most 2-level namespace but got: " + namespaceId);
@@ -136,34 +148,101 @@ public class GravitinoLanceNamespaceWrapper extends
NamespaceWrapper
}
@Override
- public DescribeNamespaceResponse describeNamespace(String id, String
delimiter) {
- throw new UnsupportedOperationException("Not implemented yet");
+ public DescribeNamespaceResponse describeNamespace(String namespaceId,
String delimiter) {
+ ObjectIdentifier nsId = ObjectIdentifier.of(namespaceId, delimiter);
+ Preconditions.checkArgument(
+ nsId.levels() <= 2 && nsId.levels() > 0,
+ "Expected at most 2-level and at least 1-level namespace but got: %s",
+ namespaceId);
+
+ Catalog catalog = loadAndValidateLakehouseCatalog(nsId.levelAtListPos(0));
+ Map<String, String> properties = Maps.newHashMap();
+
+ switch (nsId.levels()) {
+ case 1:
+
Optional.ofNullable(catalog.properties()).ifPresent(properties::putAll);
+ break;
+ case 2:
+ String schemaName = nsId.levelAtListPos(1);
+ Schema schema = catalog.asSchemas().loadSchema(schemaName);
+ Optional.ofNullable(schema.properties()).ifPresent(properties::putAll);
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Expected at most 2-level and at least 1-level namespace but got:
" + namespaceId);
+ }
+
+ DescribeNamespaceResponse response = new DescribeNamespaceResponse();
+ response.setProperties(properties);
+ return response;
}
@Override
public CreateNamespaceResponse createNamespace(
- String id,
+ String namespaceId,
String delimiter,
CreateNamespaceRequest.ModeEnum mode,
Map<String, String> properties) {
- throw new UnsupportedOperationException("Not implemented yet");
+ ObjectIdentifier nsId = ObjectIdentifier.of(namespaceId, delimiter);
+ Preconditions.checkArgument(
+ nsId.levels() <= 2 && nsId.levels() > 0,
+ "Expected at most 2-level and at least 1-level namespace but got: %s",
+ namespaceId);
+
+ switch (nsId.levels()) {
+ case 1:
+ return createOrUpdateCatalog(nsId.levelAtListPos(0), mode, properties);
+ case 2:
+ return createOrUpdateSchema(
+ nsId.levelAtListPos(0), nsId.levelAtListPos(1), mode, properties);
+ default:
+ throw new IllegalArgumentException(
+ "Expected at most 2-level and at least 1-level namespace but got:
" + namespaceId);
+ }
}
@Override
public DropNamespaceResponse dropNamespace(
- String id,
+ String namespaceId,
String delimiter,
DropNamespaceRequest.ModeEnum mode,
DropNamespaceRequest.BehaviorEnum behavior) {
- throw new UnsupportedOperationException("Not implemented yet");
+ ObjectIdentifier nsId = ObjectIdentifier.of(namespaceId, delimiter);
+ Preconditions.checkArgument(
+ nsId.levels() <= 2 && nsId.levels() > 0,
+ "Expected at most 2-level and at least 1-level namespace but got: %s",
+ namespaceId);
+
+ switch (nsId.levels()) {
+ case 1:
+ return dropCatalog(nsId.levelAtListPos(0), mode, behavior);
+ case 2:
+ return dropSchema(nsId.levelAtListPos(0), nsId.levelAtListPos(1),
mode, behavior);
+ default:
+ throw new IllegalArgumentException(
+ "Expected at most 2-level and at least 1-level namespace but got:
" + namespaceId);
+ }
}
@Override
- public void namespaceExists(String id, String delimiter) throws
LanceNamespaceException {}
+ public void namespaceExists(String namespaceId, String delimiter) throws
LanceNamespaceException {
+ ObjectIdentifier nsId = ObjectIdentifier.of(namespaceId, delimiter);
+ Preconditions.checkArgument(
+ nsId.levels() <= 2 && nsId.levels() > 0,
+ "Expected at most 2-level and at least 1-level namespace but got: %s",
+ namespaceId);
- private boolean isLakehouseCatalog(Catalog catalog) {
- return catalog.type().equals(Catalog.Type.RELATIONAL)
- && "generic-lakehouse".equals(catalog.provider());
+ Catalog catalog = loadAndValidateLakehouseCatalog(nsId.levelAtListPos(0));
+ if (nsId.levels() == 2) {
+ String schemaName = nsId.levelAtListPos(1);
+ if (!catalog.asSchemas().schemaExists(schemaName)) {
+ throw LanceNamespaceException.notFound(
+ "Schema not found: " + schemaName,
+ NoSuchSchemaException.class.getSimpleName(),
+ schemaName,
+ CommonUtil.formatCurrentStackTrace());
+ }
+ }
}
@Override
@@ -171,4 +250,221 @@ public class GravitinoLanceNamespaceWrapper extends
NamespaceWrapper
String id, String delimiter, String pageToken, Integer limit) {
throw new UnsupportedOperationException("Not implemented yet");
}
+
+ private boolean isLakehouseCatalog(Catalog catalog) {
+ return catalog.type().equals(Catalog.Type.RELATIONAL)
+ && "generic-lakehouse".equals(catalog.provider());
+ }
+
+ private Catalog loadAndValidateLakehouseCatalog(String catalogName) {
+ Catalog catalog;
+ try {
+ catalog = client.loadCatalog(catalogName);
+ } catch (NoSuchCatalogException e) {
+ throw LanceNamespaceException.notFound(
+ "Catalog not found: " + catalogName,
+ NoSuchCatalogException.class.getSimpleName(),
+ catalogName,
+ CommonUtil.formatCurrentStackTrace());
+ }
+ if (!isLakehouseCatalog(catalog)) {
+ throw LanceNamespaceException.notFound(
+ "Catalog is not a lakehouse catalog: " + catalogName,
+ NoSuchCatalogException.class.getSimpleName(),
+ catalogName,
+ CommonUtil.formatCurrentStackTrace());
+ }
+ return catalog;
+ }
+
+ private CreateNamespaceResponse createOrUpdateCatalog(
+ String catalogName, CreateNamespaceRequest.ModeEnum mode, Map<String,
String> properties) {
+ CreateNamespaceResponse response = new CreateNamespaceResponse();
+
+ Catalog catalog;
+ try {
+ catalog = client.loadCatalog(catalogName);
+ } catch (NoSuchCatalogException e) {
+ // Catalog does not exist, create it
+ Catalog createdCatalog =
+ client.createCatalog(
+ catalogName,
+ Catalog.Type.RELATIONAL,
+ "generic-lakehouse",
+ "created by Lance REST server",
+ properties);
+ response.setProperties(
+ createdCatalog.properties() == null ? Maps.newHashMap() :
createdCatalog.properties());
+ return response;
+ }
+
+ // Catalog exists, validate type
+ if (!isLakehouseCatalog(catalog)) {
+ throw LanceNamespaceException.conflict(
+ "Catalog already exists but is not a lakehouse catalog: " +
catalogName,
+ CatalogAlreadyExistsException.class.getSimpleName(),
+ catalogName,
+ CommonUtil.formatCurrentStackTrace());
+ }
+
+ // Catalog exists, handle based on mode
+ switch (mode) {
+ case EXIST_OK:
+ response.setProperties(Maps.newHashMap());
+ return response;
+ case CREATE:
+ throw LanceNamespaceException.conflict(
+ "Catalog already exists: " + catalogName,
+ CatalogAlreadyExistsException.class.getSimpleName(),
+ catalogName,
+ CommonUtil.formatCurrentStackTrace());
+ case OVERWRITE:
+ CatalogChange[] changes =
+ buildChanges(
+ properties,
+ catalog.properties(),
+ CatalogChange::setProperty,
+ CatalogChange::removeProperty,
+ CatalogChange[]::new);
+ Catalog alteredCatalog = client.alterCatalog(catalogName, changes);
+
Optional.ofNullable(alteredCatalog.properties()).ifPresent(response::setProperties);
+ return response;
+ default:
+ throw new IllegalArgumentException("Unknown mode: " + mode);
+ }
+ }
+
+ private CreateNamespaceResponse createOrUpdateSchema(
+ String catalogName,
+ String schemaName,
+ CreateNamespaceRequest.ModeEnum mode,
+ Map<String, String> properties) {
+ CreateNamespaceResponse response = new CreateNamespaceResponse();
+ Catalog loadedCatalog = loadAndValidateLakehouseCatalog(catalogName);
+
+ Schema schema;
+ try {
+ schema = loadedCatalog.asSchemas().loadSchema(schemaName);
+ } catch (NoSuchSchemaException e) {
+ // Schema does not exist, create it
+ Schema createdSchema =
loadedCatalog.asSchemas().createSchema(schemaName, null, properties);
+ response.setProperties(
+ createdSchema.properties() == null ? Maps.newHashMap() :
createdSchema.properties());
+ return response;
+ }
+
+ // Schema exists, handle based on mode
+ switch (mode) {
+ case EXIST_OK:
+ response.setProperties(Maps.newHashMap());
+ return response;
+ case CREATE:
+ throw LanceNamespaceException.conflict(
+ "Schema already exists: " + schemaName,
+ SchemaAlreadyExistsException.class.getSimpleName(),
+ schemaName,
+ CommonUtil.formatCurrentStackTrace());
+ case OVERWRITE:
+ SchemaChange[] changes =
+ buildChanges(
+ properties,
+ schema.properties(),
+ SchemaChange::setProperty,
+ SchemaChange::removeProperty,
+ SchemaChange[]::new);
+ Schema alteredSchema =
loadedCatalog.asSchemas().alterSchema(schemaName, changes);
+
Optional.ofNullable(alteredSchema.properties()).ifPresent(response::setProperties);
+ return response;
+ default:
+ throw new IllegalArgumentException("Unknown mode: " + mode);
+ }
+ }
+
+ private DropNamespaceResponse dropCatalog(
+ String catalogName,
+ DropNamespaceRequest.ModeEnum mode,
+ DropNamespaceRequest.BehaviorEnum behavior) {
+ try {
+ boolean dropped =
+ client.dropCatalog(catalogName, behavior ==
DropNamespaceRequest.BehaviorEnum.CASCADE);
+ if (dropped) {
+ return new DropNamespaceResponse();
+ } else {
+ // Catalog did not exist
+ if (mode == DropNamespaceRequest.ModeEnum.FAIL) {
+ throw LanceNamespaceException.notFound(
+ "Catalog not found: " + catalogName,
+ NoSuchCatalogException.class.getSimpleName(),
+ catalogName,
+ CommonUtil.formatCurrentStackTrace());
+ }
+ return new DropNamespaceResponse(); // SKIP mode
+ }
+ } catch (NonEmptyCatalogException e) {
+ throw LanceNamespaceException.badRequest(
+ String.format("Catalog %s is not empty.", catalogName),
+ NonEmptyCatalogException.class.getSimpleName(),
+ catalogName,
+ CommonUtil.formatCurrentStackTrace());
+ }
+ }
+
+ private DropNamespaceResponse dropSchema(
+ String catalogName,
+ String schemaName,
+ DropNamespaceRequest.ModeEnum mode,
+ DropNamespaceRequest.BehaviorEnum behavior) {
+ try {
+ boolean dropped =
+ client
+ .loadCatalog(catalogName)
+ .asSchemas()
+ .dropSchema(schemaName, behavior ==
DropNamespaceRequest.BehaviorEnum.CASCADE);
+ if (dropped) {
+ return new DropNamespaceResponse();
+ } else {
+ // Schema did not exist
+ if (mode == DropNamespaceRequest.ModeEnum.FAIL) {
+ throw LanceNamespaceException.notFound(
+ "Schema not found: " + schemaName,
+ NoSuchSchemaException.class.getSimpleName(),
+ schemaName,
+ CommonUtil.formatCurrentStackTrace());
+ }
+ return new DropNamespaceResponse(); // SKIP mode
+ }
+ } catch (NoSuchCatalogException e) {
+ throw LanceNamespaceException.notFound(
+ "Catalog not found: " + catalogName,
+ NoSuchCatalogException.class.getSimpleName(),
+ catalogName,
+ CommonUtil.formatCurrentStackTrace());
+ } catch (NonEmptySchemaException e) {
+ throw LanceNamespaceException.badRequest(
+ String.format("Schema %s is not empty.", schemaName),
+ NonEmptySchemaException.class.getSimpleName(),
+ schemaName,
+ CommonUtil.formatCurrentStackTrace());
+ }
+ }
+
+ private <T> T[] buildChanges(
+ Map<String, String> newProps,
+ Map<String, String> oldProps,
+ BiFunction<String, String, T> setPropertyFunc,
+ Function<String, T> removePropertyFunc,
+ IntFunction<T[]> arrayCreator) {
+ Stream<T> setPropertiesStream =
+ newProps.entrySet().stream()
+ .map(entry -> setPropertyFunc.apply(entry.getKey(),
entry.getValue()));
+
+ Stream<T> removePropertiesStream =
+ oldProps == null
+ ? Stream.empty()
+ : oldProps.keySet().stream()
+ .filter(key -> !newProps.containsKey(key))
+ .map(removePropertyFunc);
+
+ return Stream.concat(setPropertiesStream,
removePropertiesStream).toArray(arrayCreator);
+ }
}
diff --git
a/lance/lance-common/src/test/java/org/apache/gravitino/lance/common/config/TestLanceConfig.java
b/lance/lance-common/src/test/java/org/apache/gravitino/lance/common/config/TestLanceConfig.java
index 176634f309..44577a2dfa 100644
---
a/lance/lance-common/src/test/java/org/apache/gravitino/lance/common/config/TestLanceConfig.java
+++
b/lance/lance-common/src/test/java/org/apache/gravitino/lance/common/config/TestLanceConfig.java
@@ -29,22 +29,22 @@ public class TestLanceConfig {
@Test
public void testLoadLanceConfig() {
Map<String, String> properties =
- ImmutableMap.of("gravitino.lance-rest.catalog-name", "test_catalog");
+ ImmutableMap.of("gravitino.lance-rest.namespace-backend",
"test_catalog");
LanceConfig lanceConfig = new LanceConfig();
lanceConfig.loadFromMap(properties, k ->
k.startsWith("gravitino.lance-rest."));
- Assertions.assertEquals("test_catalog", lanceConfig.getCatalogName());
+ Assertions.assertEquals("gravitino", lanceConfig.getNamespaceBackend());
LanceConfig lanceConfig2 = new LanceConfig(properties);
- Assertions.assertEquals("test_catalog", lanceConfig2.getCatalogName());
+ Assertions.assertEquals("gravitino", lanceConfig2.getNamespaceBackend());
}
@Test
public void testDefaultCatalogName() {
- // Test default catalog name when not specified
+ // Test default namespace backend name when not specified
Map<String, String> properties = ImmutableMap.of();
LanceConfig lanceConfig = new LanceConfig(properties);
- Assertions.assertEquals("default", lanceConfig.getCatalogName());
+ Assertions.assertEquals("gravitino", lanceConfig.getNamespaceBackend());
}
@Test
@@ -94,7 +94,6 @@ public class TestLanceConfig {
// Test all configurations together for auxiliary mode
Map<String, String> properties =
ImmutableMap.<String, String>builder()
- .put(LanceConfig.CATALOG_NAME.getKey(), "lance_catalog")
.put(LanceConfig.NAMESPACE_URI.getKey(),
"http://gravitino-prod:8090")
.put(LanceConfig.METALAKE_NAME.getKey(), "production")
.put(LanceConfig.NAMESPACE_BACKEND.getKey(), "gravitino")
@@ -104,7 +103,7 @@ public class TestLanceConfig {
LanceConfig lanceConfig = new LanceConfig(properties);
// Verify all config values
- Assertions.assertEquals("lance_catalog", lanceConfig.getCatalogName());
+ Assertions.assertEquals("gravitino", lanceConfig.getNamespaceBackend());
Assertions.assertEquals("http://gravitino-prod:8090",
lanceConfig.getNamespaceUri());
Assertions.assertEquals("production", lanceConfig.getGravitinoMetalake());
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 d1409c8e12..2d9f3e8823 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
@@ -90,7 +90,7 @@ public class LanceRESTService implements
GravitinoAuxiliaryService {
server.addCustomFilters(LANCE_SPEC);
server.addSystemFilters(LANCE_SPEC);
- LOG.info("Initialized Lance REST service for catalog {}",
lanceConfig.getCatalogName());
+ LOG.info("Initialized Lance REST service for backend {}",
lanceConfig.getNamespaceBackend());
}
@Override
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 2d07357f30..dd548541ad 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
@@ -22,12 +22,19 @@ import static
org.apache.gravitino.lance.common.ops.NamespaceWrapper.NAMESPACE_D
import com.codahale.metrics.annotation.ResponseMetered;
import com.codahale.metrics.annotation.Timed;
+import com.lancedb.lance.namespace.model.CreateNamespaceRequest;
+import com.lancedb.lance.namespace.model.CreateNamespaceResponse;
+import com.lancedb.lance.namespace.model.DescribeNamespaceResponse;
+import com.lancedb.lance.namespace.model.DropNamespaceRequest;
+import com.lancedb.lance.namespace.model.DropNamespaceResponse;
import com.lancedb.lance.namespace.model.ListNamespacesResponse;
+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;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
@@ -61,10 +68,95 @@ public class LanceNamespaceOperations {
@QueryParam("limit") Integer limit) {
try {
ListNamespacesResponse response =
- lanceNamespace.asNamespaceOps().listNamespaces(namespaceId,
delimiter, pageToken, limit);
+ lanceNamespace
+ .asNamespaceOps()
+ .listNamespaces(namespaceId, Pattern.quote(delimiter),
pageToken, limit);
return Response.ok(response).build();
} catch (Exception e) {
return LanceExceptionMapper.toRESTResponse(namespaceId, e);
}
}
+
+ @POST
+ @Path("/{id}/describe")
+ @Timed(name = "describe-namespaces." + MetricNames.HTTP_PROCESS_DURATION,
absolute = true)
+ @ResponseMetered(name = "describe-namespaces", absolute = true)
+ public Response describeNamespace(
+ @Encoded @PathParam("id") String namespaceId,
+ @DefaultValue(NAMESPACE_DELIMITER_DEFAULT) @QueryParam("delimiter")
String delimiter) {
+ try {
+ DescribeNamespaceResponse response =
+ lanceNamespace.asNamespaceOps().describeNamespace(namespaceId,
Pattern.quote(delimiter));
+ return Response.ok(response).build();
+ } catch (Exception e) {
+ return LanceExceptionMapper.toRESTResponse(namespaceId, e);
+ }
+ }
+
+ @POST
+ @Path("/{id}/create")
+ @Timed(name = "create-namespaces." + MetricNames.HTTP_PROCESS_DURATION,
absolute = true)
+ @ResponseMetered(name = "create-namespaces", absolute = true)
+ public Response createNamespace(
+ @Encoded @PathParam("id") String namespaceId,
+ @DefaultValue(NAMESPACE_DELIMITER_DEFAULT) @QueryParam("delimiter")
String delimiter,
+ CreateNamespaceRequest request) {
+ try {
+ CreateNamespaceResponse response =
+ lanceNamespace
+ .asNamespaceOps()
+ .createNamespace(
+ namespaceId,
+ Pattern.quote(delimiter),
+ request.getMode() == null
+ ? CreateNamespaceRequest.ModeEnum.CREATE
+ : request.getMode(),
+ request.getProperties());
+ return Response.ok(response).build();
+ } catch (Exception e) {
+ return LanceExceptionMapper.toRESTResponse(namespaceId, e);
+ }
+ }
+
+ @POST
+ @Path("/{id}/drop")
+ @Timed(name = "drop-namespaces." + MetricNames.HTTP_PROCESS_DURATION,
absolute = true)
+ @ResponseMetered(name = "drop-namespaces", absolute = true)
+ public Response dropNamespace(
+ @Encoded @PathParam("id") String namespaceId,
+ @DefaultValue(NAMESPACE_DELIMITER_DEFAULT) @QueryParam("delimiter")
String delimiter,
+ DropNamespaceRequest request) {
+ try {
+ DropNamespaceResponse response =
+ lanceNamespace
+ .asNamespaceOps()
+ .dropNamespace(
+ namespaceId,
+ Pattern.quote(delimiter),
+ request.getMode() == null
+ ? DropNamespaceRequest.ModeEnum.FAIL
+ : request.getMode(),
+ request.getBehavior() == null
+ ? DropNamespaceRequest.BehaviorEnum.RESTRICT
+ : request.getBehavior());
+ return Response.ok(response).build();
+ } catch (Exception e) {
+ return LanceExceptionMapper.toRESTResponse(namespaceId, e);
+ }
+ }
+
+ @POST
+ @Path("/{id}/exists")
+ @Timed(name = "namespace-exists." + MetricNames.HTTP_PROCESS_DURATION,
absolute = true)
+ @ResponseMetered(name = "namespace-exists", absolute = true)
+ public Response namespaceExists(
+ @Encoded @PathParam("id") String namespaceId,
+ @DefaultValue(NAMESPACE_DELIMITER_DEFAULT) @QueryParam("delimiter")
String delimiter) {
+ try {
+ lanceNamespace.asNamespaceOps().namespaceExists(namespaceId,
Pattern.quote(delimiter));
+ return Response.ok().build();
+ } catch (Exception e) {
+ return LanceExceptionMapper.toRESTResponse(namespaceId, e);
+ }
+ }
}
diff --git
a/server-common/src/test/java/org/apache/gravitino/server/TestServerConfig.java
b/server-common/src/test/java/org/apache/gravitino/server/TestServerConfig.java
index fc9193e012..e46e27b807 100644
---
a/server-common/src/test/java/org/apache/gravitino/server/TestServerConfig.java
+++
b/server-common/src/test/java/org/apache/gravitino/server/TestServerConfig.java
@@ -67,7 +67,8 @@ public class TestServerConfig {
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
String propKey = (String) entry.getKey();
if
(propKey.startsWith(AuxiliaryServiceManager.GRAVITINO_AUX_SERVICE_PREFIX)
- || propKey.startsWith("gravitino.iceberg-rest.")) {
+ || propKey.startsWith("gravitino.iceberg-rest.")
+ || propKey.startsWith("gravitino.lance-rest.")) {
continue;
}
Assertions.assertTrue(