This is an automated email from the ASF dual-hosted git repository.
jshao 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 9aa61b2f8b [#8890] feat(lance-rest-server): add lance-rest-server
framework (#8895)
9aa61b2f8b is described below
commit 9aa61b2f8ba57f74082ecabbf61d8670a60665f1
Author: mchades <[email protected]>
AuthorDate: Fri Oct 24 10:47:38 2025 +0800
[#8890] feat(lance-rest-server): add lance-rest-server framework (#8895)
### What changes were proposed in this pull request?
- add lance-rest-server framework
- support list namespaces
### Why are the changes needed?
Fix: #8890
### Does this PR introduce _any_ user-facing change?
yes
### How was this patch tested?
no tests now
---
gradle/libs.versions.toml | 2 +
lance/lance-common/build.gradle.kts | 4 +
.../gravitino/lance/common/config/LanceConfig.java | 26 ++
.../lance/common/ops/LanceCatalogService.java | 352 ---------------------
.../lance/common/ops/LanceNamespaceBackend.java | 54 ++++
.../lance/common/ops/LanceNamespaceOperations.java | 50 +++
.../lance/common/ops/LanceTableOperations.java} | 25 +-
.../lance/common/ops/NamespaceWrapper.java | 76 +++++
.../gravitino/GravitinoLanceNamespaceWrapper.java | 174 ++++++++++
lance/lance-rest-server/build.gradle.kts | 2 +
.../apache/gravitino/lance/LanceRESTService.java | 39 ++-
.../lance/service/LanceExceptionMapper.java | 88 ++++++
.../service/rest/LanceListNamespacesResponse.java | 63 ----
.../service/rest/LanceListTablesResponse.java | 63 ----
.../service/rest/LanceNamespaceOperations.java | 63 ++--
.../lance/service/rest/LanceTableOperations.java | 63 ++++
16 files changed, 594 insertions(+), 550 deletions(-)
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index d0779e0ee6..db1ea15782 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -29,6 +29,7 @@ lombok = "1.18.20"
slf4j = "2.0.16"
log4j = "2.24.3"
lance = "0.34.0"
+lance-namespace = "0.0.19"
jetty = "9.4.51.v20230217"
jersey = "2.41"
mockito = "4.11.0"
@@ -161,6 +162,7 @@ log4j-core = { group = "org.apache.logging.log4j", name =
"log4j-core", version.
log4j-12-api = { group = "org.apache.logging.log4j", name = "log4j-1.2-api",
version.ref = "log4j" }
log4j-layout-template-json = { group = "org.apache.logging.log4j", name =
"log4j-layout-template-json", version.ref = "log4j" }
lance = { group = "com.lancedb", name = "lance-core", version.ref = "lance" }
+lance-namespace-core = { group = "com.lancedb", name = "lance-namespace-core",
version.ref = "lance-namespace" }
jakarta-validation-api = { group = "jakarta.validation", name =
"jakarta.validation-api", version.ref = "jakarta-validation" }
jetty-server = { group = "org.eclipse.jetty", name = "jetty-server",
version.ref = "jetty" }
jetty-servlet = { group = "org.eclipse.jetty", name = "jetty-servlet",
version.ref = "jetty" }
diff --git a/lance/lance-common/build.gradle.kts
b/lance/lance-common/build.gradle.kts
index 5048d274f6..4e91dd6c5c 100644
--- a/lance/lance-common/build.gradle.kts
+++ b/lance/lance-common/build.gradle.kts
@@ -27,6 +27,9 @@ plugins {
dependencies {
implementation(project(":api"))
implementation(project(":catalogs:catalog-common"))
+ implementation(project(":clients:client-java")) {
+ exclude("*")
+ }
implementation(project(":common")) {
exclude("*")
}
@@ -36,6 +39,7 @@ dependencies {
implementation(libs.guava)
implementation(libs.commons.lang3)
+ implementation(libs.lance.namespace.core)
implementation(libs.slf4j.api)
testImplementation(libs.junit.jupiter.api)
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 f2d7e748cf..dfe863953e 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
@@ -30,9 +30,14 @@ import org.apache.gravitino.config.ConfigEntry;
public class LanceConfig extends Config implements OverwriteDefaultConfig {
public static final String LANCE_CONFIG_PREFIX = "gravitino.lance-rest.";
+ public static final String CONFIG_NAMESPACE_BACKEND = "namespace-backend";
+ public static final String CONFIG_METALAKE = "metalake-name";
+ public static final String CONFIG_URI = "uri";
public static final int DEFAULT_LANCE_REST_SERVICE_HTTP_PORT = 9101;
public static final int DEFAULT_LANCE_REST_SERVICE_HTTPS_PORT = 9533;
+ 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")
@@ -41,6 +46,27 @@ public class LanceConfig extends Config implements
OverwriteDefaultConfig {
.stringConf()
.createWithDefault("default");
+ public static final ConfigEntry<String> NAMESPACE_BACKEND =
+ new ConfigBuilder(LANCE_CONFIG_PREFIX + 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)
+ .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)
+ .doc("The URI for the namespace backend, e.g., Gravitino server URI")
+ .version(ConfigConstants.VERSION_0_1_0)
+ .stringConf()
+ .createWithDefault(DEFAULT_URI);
+
public LanceConfig(Map<String, String> properties) {
super(false);
loadFromMap(properties, key -> true);
diff --git
a/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/LanceCatalogService.java
b/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/LanceCatalogService.java
deleted file mode 100644
index 67dd4c2d22..0000000000
---
a/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/LanceCatalogService.java
+++ /dev/null
@@ -1,352 +0,0 @@
-/*
- * 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;
-
-import com.google.common.collect.ImmutableMap;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.stream.Collectors;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.gravitino.lance.common.config.LanceConfig;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Thin placeholder that will later bridge Lance catalog metadata into
Gravitino.
- *
- * <p>The current implementation keeps an in-memory catalog view so the REST
surface mirrors the
- * Iceberg catalog experience while the Lance integration is built out for
real.
- */
-public class LanceCatalogService implements AutoCloseable {
-
- private static final Logger LOG =
LoggerFactory.getLogger(LanceCatalogService.class);
-
- private final LanceConfig config;
- private final ConcurrentMap<String, NamespaceState> namespaces;
-
- public LanceCatalogService(LanceConfig config) {
- this.config = config;
- this.namespaces = new ConcurrentHashMap<>();
- seedSampleMetadata();
- }
-
- public String catalogName() {
- return config.getCatalogName();
- }
-
- public boolean namespaceExists(String namespace) {
- return namespaces.containsKey(namespace);
- }
-
- public Map<String, Map<String, String>> listNamespaces() {
- Map<String, Map<String, String>> result = new ConcurrentHashMap<>();
- namespaces.forEach(
- (name, state) ->
- result.put(
- name, Collections.unmodifiableMap(new
ConcurrentHashMap<>(state.properties))));
- return Map.copyOf(result);
- }
-
- public List<String> listNamespaceNames() {
- return namespaces.keySet().stream()
- .sorted(Comparator.naturalOrder())
- .collect(Collectors.toUnmodifiableList());
- }
-
- public NamespaceListingResult listChildNamespaces(
- String parentId, String delimiter, String pageToken, Integer limit) {
- String normalizedParent = StringUtils.trimToEmpty(parentId);
- String effectiveDelimiter = StringUtils.isBlank(delimiter) ? "$" :
delimiter;
-
- List<String> sortedNamespaces = listNamespaceNames();
- List<String> filtered = filterChildren(sortedNamespaces, normalizedParent,
effectiveDelimiter);
-
- int startingOffset = parsePageToken(pageToken, filtered.size());
- int pageLimit = limit == null ? filtered.size() :
validatePositiveLimit(limit, filtered.size());
- int endIndex = Math.min(filtered.size(), startingOffset + pageLimit);
-
- List<String> page = filtered.subList(startingOffset, endIndex);
- String nextToken = endIndex < filtered.size() ? String.valueOf(endIndex) :
null;
- return new NamespaceListingResult(normalizedParent, effectiveDelimiter,
page, nextToken);
- }
-
- public boolean createNamespace(String namespace) {
- if (StringUtils.isBlank(namespace)) {
- throw new IllegalArgumentException("Namespace must be non-empty");
- }
- NamespaceState state = new NamespaceState(Collections.emptyMap());
- NamespaceState existing = namespaces.putIfAbsent(namespace, state);
- if (existing == null) {
- LOG.info("Created Lance namespace {}", namespace);
- return true;
- }
- return false;
- }
-
- public boolean dropNamespace(String namespace) {
- NamespaceState state = namespaces.get(namespace);
- if (state == null) {
- return false;
- }
- if (!state.tables.isEmpty()) {
- LOG.info("Refusing to drop Lance namespace {} because it still owns
tables", namespace);
- return false;
- }
- boolean removed = namespaces.remove(namespace, state);
- if (removed) {
- LOG.info("Dropped Lance namespace {}", namespace);
- }
- return removed;
- }
-
- public List<String> listTables(String namespace) {
- NamespaceState state = namespaces.get(namespace);
- if (state == null) {
- throw new IllegalArgumentException("Unknown namespace: " + namespace);
- }
- return state.tables.keySet().stream()
- .sorted(Comparator.naturalOrder())
- .collect(Collectors.toUnmodifiableList());
- }
-
- public Optional<Map<String, Object>> loadTable(String namespace, String
table) {
- NamespaceState state = namespaces.get(namespace);
- if (state == null) {
- return Optional.empty();
- }
- LanceTableEntry tableEntry = state.tables.get(table);
- if (tableEntry == null) {
- return Optional.empty();
- }
- return Optional.of(tableEntry.describe());
- }
-
- public TableListingResult listTables(
- String namespaceId, String delimiter, String pageToken, Integer limit) {
- String normalizedNamespace = StringUtils.trimToEmpty(namespaceId);
- if (StringUtils.isBlank(normalizedNamespace)) {
- throw new IllegalArgumentException("Namespace id must be provided");
- }
-
- String effectiveDelimiter = StringUtils.isBlank(delimiter) ? "$" :
delimiter;
-
- NamespaceState state = namespaces.get(normalizedNamespace);
- if (state == null) {
- throw new NoSuchElementException("Unknown namespace: " +
normalizedNamespace);
- }
-
- List<String> sortedTables =
- state.tables.keySet().stream()
- .sorted(Comparator.naturalOrder())
- .collect(Collectors.toList());
-
- int startingOffset = parsePageToken(pageToken, sortedTables.size());
- int pageLimit =
- limit == null ? sortedTables.size() : validatePositiveLimit(limit,
sortedTables.size());
- int endIndex = Math.min(sortedTables.size(), startingOffset + pageLimit);
-
- List<String> page = sortedTables.subList(startingOffset, endIndex);
- String nextToken = endIndex < sortedTables.size() ?
String.valueOf(endIndex) : null;
-
- return new TableListingResult(normalizedNamespace, effectiveDelimiter,
page, nextToken);
- }
-
- @Override
- public void close() {
- namespaces.clear();
- }
-
- private void seedSampleMetadata() {
- NamespaceState defaultNamespace =
- namespaces.computeIfAbsent("default", key -> new
NamespaceState(Collections.emptyMap()));
- defaultNamespace.tables.put(
- "sample_table",
- new LanceTableEntry(
- "sample_table",
- "default",
- ImmutableMap.of(
- "format", "lance",
- "uri", "file:///tmp/sample_table.lance",
- "summary", "Placeholder Lance table metadata")));
- }
-
- private static final class NamespaceState {
- private final Map<String, String> properties;
- private final ConcurrentMap<String, LanceTableEntry> tables;
-
- NamespaceState(Map<String, String> properties) {
- this.properties = new ConcurrentHashMap<>(properties);
- this.tables = new ConcurrentHashMap<>();
- }
- }
-
- private static final class LanceTableEntry {
- private final String name;
- private final String namespace;
- private final Map<String, Object> metadata;
-
- LanceTableEntry(String name, String namespace, Map<String, Object>
metadata) {
- this.name = name;
- this.namespace = namespace;
- this.metadata = new ConcurrentHashMap<>(metadata);
- }
-
- Map<String, Object> describe() {
- Map<String, Object> result = new ConcurrentHashMap<>(metadata);
- result.put("name", name);
- result.put("namespace", namespace);
- return Collections.unmodifiableMap(result);
- }
- }
-
- private List<String> filterChildren(List<String> namespaces, String
parentId, String delimiter) {
- boolean rootRequest = StringUtils.isBlank(parentId) ||
"root".equalsIgnoreCase(parentId);
- if (rootRequest) {
- return namespaces;
- }
-
- String parentPrefix = parentId + delimiter;
- return namespaces.stream()
- .filter(ns -> ns.startsWith(parentPrefix))
- .map(
- ns -> {
- String remainder = ns.substring(parentPrefix.length());
- int nextDelimiter = remainder.indexOf(delimiter);
- if (nextDelimiter >= 0) {
- return remainder.substring(0, nextDelimiter);
- }
- return remainder;
- })
- .filter(child -> !child.isEmpty())
- .distinct()
- .sorted(Comparator.naturalOrder())
- .collect(Collectors.toUnmodifiableList());
- }
-
- private int parsePageToken(String pageToken, int size) {
- if (StringUtils.isBlank(pageToken)) {
- return 0;
- }
- try {
- int parsed = Integer.parseInt(pageToken);
- if (parsed < 0 || parsed > size) {
- throw new IllegalArgumentException("Invalid page_token value");
- }
- return parsed;
- } catch (NumberFormatException nfe) {
- throw new IllegalArgumentException("Invalid page_token value", nfe);
- }
- }
-
- private int validatePositiveLimit(int limit, int size) {
- if (limit <= 0) {
- throw new IllegalArgumentException("limit must be greater than 0");
- }
- return Math.min(limit, Math.max(size, 0));
- }
-
- public static final class NamespaceListingResult {
- private final String parentId;
- private final String delimiter;
- private final List<String> namespaces;
- private final String nextPageToken;
-
- NamespaceListingResult(
- String parentId, String delimiter, List<String> namespaces, String
nextPageToken) {
- this.parentId = parentId;
- this.delimiter = delimiter;
- this.namespaces = List.copyOf(namespaces);
- this.nextPageToken = nextPageToken;
- }
-
- public String getParentId() {
- return parentId;
- }
-
- public String getDelimiter() {
- return delimiter;
- }
-
- public List<String> getNamespaces() {
- return namespaces;
- }
-
- public Optional<String> getNextPageToken() {
- return Optional.ofNullable(nextPageToken);
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (!(o instanceof NamespaceListingResult)) {
- return false;
- }
- NamespaceListingResult that = (NamespaceListingResult) o;
- return Objects.equals(parentId, that.parentId)
- && Objects.equals(delimiter, that.delimiter)
- && Objects.equals(namespaces, that.namespaces)
- && Objects.equals(nextPageToken, that.nextPageToken);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(parentId, delimiter, namespaces, nextPageToken);
- }
- }
-
- public static final class TableListingResult {
- private final String namespaceId;
- private final String delimiter;
- private final List<String> tables;
- private final String nextPageToken;
-
- TableListingResult(
- String namespaceId, String delimiter, List<String> tables, String
nextPageToken) {
- this.namespaceId = namespaceId;
- this.delimiter = delimiter;
- this.tables = List.copyOf(tables);
- this.nextPageToken = nextPageToken;
- }
-
- public String getNamespaceId() {
- return namespaceId;
- }
-
- public String getDelimiter() {
- return delimiter;
- }
-
- public List<String> getTables() {
- return tables;
- }
-
- public Optional<String> getNextPageToken() {
- return Optional.ofNullable(nextPageToken);
- }
- }
-}
diff --git
a/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/LanceNamespaceBackend.java
b/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/LanceNamespaceBackend.java
new file mode 100644
index 0000000000..57d393f5ad
--- /dev/null
+++
b/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/LanceNamespaceBackend.java
@@ -0,0 +1,54 @@
+/*
+ * 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;
+
+import java.util.Arrays;
+import
org.apache.gravitino.lance.common.ops.gravitino.GravitinoLanceNamespaceWrapper;
+
+public enum LanceNamespaceBackend {
+ GRAVITINO("gravitino", GravitinoLanceNamespaceWrapper.class);
+
+ private final String type;
+ private final Class<? extends NamespaceWrapper> wrapperClass;
+
+ public static LanceNamespaceBackend fromType(String type) {
+ for (LanceNamespaceBackend backend : values()) {
+ if (backend.type.equalsIgnoreCase(type)) {
+ return backend;
+ }
+ }
+ throw new IllegalArgumentException(
+ String.format(
+ "Unknown backend type %s, available types: %s",
+ type, Arrays.toString(LanceNamespaceBackend.values())));
+ }
+
+ LanceNamespaceBackend(String type, Class<? extends NamespaceWrapper>
wrapperClass) {
+ this.type = type;
+ this.wrapperClass = wrapperClass;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public Class<? extends NamespaceWrapper> getWrapperClass() {
+ return wrapperClass;
+ }
+}
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
new file mode 100644
index 0000000000..1b5da98ec0
--- /dev/null
+++
b/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/LanceNamespaceOperations.java
@@ -0,0 +1,50 @@
+/*
+ * 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;
+
+import com.lancedb.lance.namespace.LanceNamespaceException;
+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.Map;
+
+public interface LanceNamespaceOperations {
+
+ ListNamespacesResponse listNamespaces(
+ String namespaceId, String delimiter, String pageToken, Integer limit);
+
+ DescribeNamespaceResponse describeNamespace(String id, String delimiter);
+
+ CreateNamespaceResponse createNamespace(
+ String id,
+ String delimiter,
+ CreateNamespaceRequest.ModeEnum mode,
+ Map<String, String> properties);
+
+ DropNamespaceResponse dropNamespace(
+ String id,
+ String delimiter,
+ DropNamespaceRequest.ModeEnum mode,
+ DropNamespaceRequest.BehaviorEnum behavior);
+
+ void namespaceExists(String id, String delimiter) throws
LanceNamespaceException;
+}
diff --git a/lance/lance-common/build.gradle.kts
b/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/LanceTableOperations.java
similarity index 62%
copy from lance/lance-common/build.gradle.kts
copy to
lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/LanceTableOperations.java
index 5048d274f6..057dce8fb3 100644
--- a/lance/lance-common/build.gradle.kts
+++
b/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/LanceTableOperations.java
@@ -16,28 +16,13 @@
* specific language governing permissions and limitations
* under the License.
*/
-description = "lance-common"
+package org.apache.gravitino.lance.common.ops;
-plugins {
- `maven-publish`
- id("java")
- id("idea")
-}
+import com.lancedb.lance.namespace.model.ListTablesResponse;
-dependencies {
- implementation(project(":api"))
- implementation(project(":catalogs:catalog-common"))
- implementation(project(":common")) {
- exclude("*")
- }
- implementation(project(":core")) {
- exclude("*")
- }
+public interface LanceTableOperations {
- implementation(libs.guava)
- implementation(libs.commons.lang3)
- implementation(libs.slf4j.api)
+ ListTablesResponse listTables(String id, String delimiter, String pageToken,
Integer limit);
- testImplementation(libs.junit.jupiter.api)
- testRuntimeOnly(libs.junit.jupiter.engine)
+ // todo: add more table operation methods
}
diff --git
a/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/NamespaceWrapper.java
b/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/NamespaceWrapper.java
new file mode 100644
index 0000000000..936a5a7069
--- /dev/null
+++
b/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/NamespaceWrapper.java
@@ -0,0 +1,76 @@
+/*
+ * 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;
+
+import org.apache.gravitino.lance.common.config.LanceConfig;
+
+public abstract class NamespaceWrapper {
+
+ public static final String NAMESPACE_DELIMITER_DEFAULT = "$";
+ private final LanceConfig config;
+
+ private volatile boolean initialized = false;
+ private LanceNamespaceOperations namespaceOps;
+ private LanceTableOperations tableOps;
+
+ public NamespaceWrapper(LanceConfig config) {
+ this.config = config;
+ }
+
+ protected abstract void initialize();
+
+ protected abstract LanceNamespaceOperations newNamespaceOps();
+
+ protected abstract LanceTableOperations newTableOps();
+
+ public abstract void close() throws Exception;
+
+ public LanceNamespaceOperations asNamespaceOps() {
+ // lazy initialize the operations because it may block the startup
+ initIfNeeded();
+ return namespaceOps;
+ }
+
+ public LanceTableOperations asTableOps() {
+ // lazy initialize the operations because it may block the startup
+ initIfNeeded();
+ return tableOps;
+ }
+
+ public LanceConfig config() {
+ return config;
+ }
+
+ private void initAll() {
+ initialize();
+ namespaceOps = newNamespaceOps();
+ tableOps = newTableOps();
+ initialized = true;
+ }
+
+ private void initIfNeeded() {
+ if (!initialized) {
+ synchronized (this) {
+ if (!initialized) {
+ initAll();
+ }
+ }
+ }
+ }
+}
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
new file mode 100644
index 0000000000..59f637b5a1
--- /dev/null
+++
b/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/gravitino/GravitinoLanceNamespaceWrapper.java
@@ -0,0 +1,174 @@
+/*
+ * 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 static
org.apache.gravitino.lance.common.config.LanceConfig.METALAKE_NAME;
+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.Sets;
+import com.lancedb.lance.namespace.LanceNamespaceException;
+import com.lancedb.lance.namespace.ObjectIdentifier;
+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 com.lancedb.lance.namespace.model.ListTablesResponse;
+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.stream.Collectors;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.Catalog;
+import org.apache.gravitino.client.GravitinoClient;
+import org.apache.gravitino.exceptions.NoSuchCatalogException;
+import org.apache.gravitino.lance.common.config.LanceConfig;
+import org.apache.gravitino.lance.common.ops.LanceNamespaceOperations;
+import org.apache.gravitino.lance.common.ops.LanceTableOperations;
+import org.apache.gravitino.lance.common.ops.NamespaceWrapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class GravitinoLanceNamespaceWrapper extends NamespaceWrapper
+ implements LanceNamespaceOperations, LanceTableOperations {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(GravitinoLanceNamespaceWrapper.class);
+ private GravitinoClient client;
+
+ public GravitinoLanceNamespaceWrapper(LanceConfig config) {
+ super(config);
+ }
+
+ @Override
+ protected void initialize() {
+ String uri = config().get(NAMESPACE_URI);
+ String metalakeName = config().get(METALAKE_NAME);
+ Preconditions.checkArgument(
+ StringUtils.isNotBlank(metalakeName),
+ "Metalake name must be provided for Gravitino namespace backend");
+
+ this.client =
GravitinoClient.builder(uri).withMetalake(metalakeName).build();
+ }
+
+ @Override
+ public LanceNamespaceOperations newNamespaceOps() {
+ return this;
+ }
+
+ @Override
+ protected LanceTableOperations newTableOps() {
+ return this;
+ }
+
+ @Override
+ public void close() {
+ if (client != null) {
+ try {
+ client.close();
+ } catch (Exception e) {
+ LOG.warn("Error closing Gravitino client", e);
+ }
+ }
+ }
+
+ @Override
+ public ListNamespacesResponse listNamespaces(
+ String namespaceId, String delimiter, String pageToken, Integer limit) {
+ ObjectIdentifier nsId = ObjectIdentifier.of(namespaceId, delimiter);
+ Preconditions.checkArgument(
+ nsId.levels() <= 2, "Expected at most 2-level namespace but got: %s",
namespaceId);
+
+ 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)
+ .map(Catalog::name)
+ .collect(Collectors.toList());
+ 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);
+ }
+
+ namespaces = Lists.newArrayList(catalog.asSchemas().listSchemas());
+ break;
+
+ default:
+ throw new IllegalArgumentException(
+ "Expected at most 2-level namespace but got: " + namespaceId);
+ }
+
+ Collections.sort(namespaces);
+ PageUtil.Page page =
+ PageUtil.splitPage(namespaces, pageToken,
PageUtil.normalizePageSize(limit));
+ ListNamespacesResponse response = new ListNamespacesResponse();
+ response.setNamespaces(Sets.newHashSet(page.items()));
+ response.setPageToken(page.nextPageToken());
+ return response;
+ }
+
+ @Override
+ public DescribeNamespaceResponse describeNamespace(String id, String
delimiter) {
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ @Override
+ public CreateNamespaceResponse createNamespace(
+ String id,
+ String delimiter,
+ CreateNamespaceRequest.ModeEnum mode,
+ Map<String, String> properties) {
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ @Override
+ public DropNamespaceResponse dropNamespace(
+ String id,
+ String delimiter,
+ DropNamespaceRequest.ModeEnum mode,
+ DropNamespaceRequest.BehaviorEnum behavior) {
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ @Override
+ public void namespaceExists(String id, String delimiter) throws
LanceNamespaceException {}
+
+ private boolean isLakehouseCatalog(Catalog catalog) {
+ return catalog.type().equals(Catalog.Type.RELATIONAL)
+ && "generic-lakehouse".equals(catalog.provider());
+ }
+
+ @Override
+ public ListTablesResponse listTables(
+ String id, String delimiter, String pageToken, Integer limit) {
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+}
diff --git a/lance/lance-rest-server/build.gradle.kts
b/lance/lance-rest-server/build.gradle.kts
index 0337609593..4e4ca7db3c 100644
--- a/lance/lance-rest-server/build.gradle.kts
+++ b/lance/lance-rest-server/build.gradle.kts
@@ -36,6 +36,7 @@ dependencies {
implementation(project(":server-common")) {
exclude("*")
}
+
implementation(project(":lance:lance-common"))
implementation(libs.bundles.jetty)
@@ -43,6 +44,7 @@ dependencies {
implementation(libs.bundles.log4j)
implementation(libs.bundles.metrics)
implementation(libs.bundles.prometheus)
+ implementation(libs.lance.namespace.core)
implementation(libs.metrics.jersey2)
implementation(libs.guava)
implementation(libs.jackson.annotations)
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 e85dc37b4a..123781262f 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
@@ -18,14 +18,18 @@
*/
package org.apache.gravitino.lance;
+import static
org.apache.gravitino.lance.common.config.LanceConfig.NAMESPACE_BACKEND;
+
+import java.lang.reflect.Constructor;
import java.util.Map;
import javax.servlet.Servlet;
import org.apache.gravitino.auxiliary.GravitinoAuxiliaryService;
import org.apache.gravitino.lance.common.config.LanceConfig;
-import org.apache.gravitino.lance.common.ops.LanceCatalogService;
-import org.apache.gravitino.lance.service.rest.LanceNamespaceOperations;
+import org.apache.gravitino.lance.common.ops.LanceNamespaceBackend;
+import org.apache.gravitino.lance.common.ops.NamespaceWrapper;
import org.apache.gravitino.server.web.JettyServer;
import org.apache.gravitino.server.web.JettyServerConfig;
+import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;
@@ -41,7 +45,7 @@ public class LanceRESTService implements
GravitinoAuxiliaryService {
public static final String LANCE_SPEC = "/lance/*";
private JettyServer server;
- private LanceCatalogService catalogService;
+ private NamespaceWrapper lanceNamespace;
@Override
public String shortName() {
@@ -56,11 +60,18 @@ public class LanceRESTService implements
GravitinoAuxiliaryService {
server = new JettyServer();
server.initialize(serverConfig, SERVICE_NAME, false);
- catalogService = new LanceCatalogService(lanceConfig);
+ this.lanceNamespace = loadNamespaceImpl(lanceConfig);
ResourceConfig resourceConfig = new ResourceConfig();
resourceConfig.register(JacksonFeature.class);
- resourceConfig.register(new LanceNamespaceOperations(catalogService));
+ resourceConfig.packages("org.apache.gravitino.lance.service.rest");
+ resourceConfig.register(
+ new AbstractBinder() {
+ @Override
+ protected void configure() {
+ bind(lanceNamespace).to(NamespaceWrapper.class).ranked(1);
+ }
+ });
Servlet container = new ServletContainer(resourceConfig);
server.addServlet(container, LANCE_SPEC);
@@ -84,8 +95,8 @@ public class LanceRESTService implements
GravitinoAuxiliaryService {
server.stop();
LOG.info("Lance REST service stopped");
}
- if (catalogService != null) {
- catalogService.close();
+ if (lanceNamespace != null) {
+ lanceNamespace.close();
}
}
@@ -94,4 +105,18 @@ public class LanceRESTService implements
GravitinoAuxiliaryService {
server.join();
}
}
+
+ private NamespaceWrapper loadNamespaceImpl(LanceConfig lanceConfig) {
+ String backendType = lanceConfig.get(NAMESPACE_BACKEND);
+ LanceNamespaceBackend lanceNamespaceBackend =
LanceNamespaceBackend.fromType(backendType);
+
+ 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);
+ throw new RuntimeException("Failed to load namespace implementation", e);
+ }
+ }
}
diff --git
a/lance/lance-rest-server/src/main/java/org/apache/gravitino/lance/service/LanceExceptionMapper.java
b/lance/lance-rest-server/src/main/java/org/apache/gravitino/lance/service/LanceExceptionMapper.java
new file mode 100644
index 0000000000..2465e6ed63
--- /dev/null
+++
b/lance/lance-rest-server/src/main/java/org/apache/gravitino/lance/service/LanceExceptionMapper.java
@@ -0,0 +1,88 @@
+/*
+ * 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;
+
+import static org.apache.commons.lang3.exception.ExceptionUtils.getStackTrace;
+
+import com.lancedb.lance.namespace.LanceNamespaceException;
+import com.lancedb.lance.namespace.model.ErrorResponse;
+import java.util.Optional;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+import org.apache.gravitino.exceptions.NotFoundException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Provider
+public class LanceExceptionMapper implements ExceptionMapper<Exception> {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(LanceExceptionMapper.class);
+
+ public static Response toRESTResponse(String instance, Exception ex) {
+ LanceNamespaceException lanceException =
+ ex instanceof LanceNamespaceException
+ ? (LanceNamespaceException) ex
+ : toLanceNamespaceException(instance, ex);
+
+ return handleLanceNamespaceException(lanceException);
+ }
+
+ @Override
+ public Response toResponse(Exception ex) {
+ return toRESTResponse("", ex);
+ }
+
+ private static LanceNamespaceException toLanceNamespaceException(String
instance, Exception ex) {
+ if (ex instanceof NotFoundException) {
+ return LanceNamespaceException.notFound(
+ ex.getMessage(), ex.getClass().getSimpleName(), instance,
getStackTrace(ex));
+
+ } else if (ex instanceof IllegalArgumentException) {
+ return LanceNamespaceException.badRequest(
+ ex.getMessage(), ex.getClass().getSimpleName(), instance,
getStackTrace(ex));
+
+ } else if (ex instanceof UnsupportedOperationException) {
+ return LanceNamespaceException.unsupportedOperation(
+ ex.getMessage(), ex.getClass().getSimpleName(), instance,
getStackTrace(ex));
+
+ } else {
+ LOG.warn("Lance REST server unexpected exception:", ex);
+ return LanceNamespaceException.serverError(
+ ex.getMessage(), ex.getClass().getSimpleName(), instance,
getStackTrace(ex));
+ }
+ }
+
+ // Referred from lance-namespace-adapter's LanceNamespaces exception handling
+ // com.lancedb.lance.namespace.adapter.GlobalExceptionHandler
+ private static Response
handleLanceNamespaceException(LanceNamespaceException ex) {
+ ErrorResponse errResp = new ErrorResponse();
+ Optional<ErrorResponse> errorResponse = ex.getErrorResponse();
+ if (errorResponse.isPresent() && errorResponse.get().getCode() != null) {
+ errResp = errorResponse.get();
+
+ } else {
+ // Transform error info into ErrorResponse
+ errResp.setCode(ex.getCode());
+ errResp.setError(ex.getMessage());
+ }
+
+ return Response.status(errResp.getCode()).entity(errResp).build();
+ }
+}
diff --git
a/lance/lance-rest-server/src/main/java/org/apache/gravitino/lance/service/rest/LanceListNamespacesResponse.java
b/lance/lance-rest-server/src/main/java/org/apache/gravitino/lance/service/rest/LanceListNamespacesResponse.java
deleted file mode 100644
index 11ec7d3c3c..0000000000
---
a/lance/lance-rest-server/src/main/java/org/apache/gravitino/lance/service/rest/LanceListNamespacesResponse.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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 com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import java.util.List;
-
-@JsonInclude(JsonInclude.Include.NON_NULL)
-public class LanceListNamespacesResponse {
-
- @JsonProperty("id")
- private final String id;
-
- @JsonProperty("delimiter")
- private final String delimiter;
-
- @JsonProperty("namespaces")
- private final List<String> namespaces;
-
- @JsonProperty("next_page_token")
- private final String nextPageToken;
-
- public LanceListNamespacesResponse(
- String id, String delimiter, List<String> namespaces, String
nextPageToken) {
- this.id = id;
- this.delimiter = delimiter;
- this.namespaces = List.copyOf(namespaces);
- this.nextPageToken = nextPageToken;
- }
-
- public String getId() {
- return id;
- }
-
- public String getDelimiter() {
- return delimiter;
- }
-
- public List<String> getNamespaces() {
- return namespaces;
- }
-
- public String getNextPageToken() {
- return nextPageToken;
- }
-}
diff --git
a/lance/lance-rest-server/src/main/java/org/apache/gravitino/lance/service/rest/LanceListTablesResponse.java
b/lance/lance-rest-server/src/main/java/org/apache/gravitino/lance/service/rest/LanceListTablesResponse.java
deleted file mode 100644
index 82e2a90978..0000000000
---
a/lance/lance-rest-server/src/main/java/org/apache/gravitino/lance/service/rest/LanceListTablesResponse.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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 com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import java.util.List;
-
-@JsonInclude(JsonInclude.Include.NON_NULL)
-public class LanceListTablesResponse {
-
- @JsonProperty("id")
- private final String namespaceId;
-
- @JsonProperty("delimiter")
- private final String delimiter;
-
- @JsonProperty("tables")
- private final List<String> tables;
-
- @JsonProperty("next_page_token")
- private final String nextPageToken;
-
- public LanceListTablesResponse(
- String namespaceId, String delimiter, List<String> tables, String
nextPageToken) {
- this.namespaceId = namespaceId;
- this.delimiter = delimiter;
- this.tables = List.copyOf(tables);
- this.nextPageToken = nextPageToken;
- }
-
- public String getNamespaceId() {
- return namespaceId;
- }
-
- public String getDelimiter() {
- return delimiter;
- }
-
- public List<String> getTables() {
- return tables;
- }
-
- public String getNextPageToken() {
- return nextPageToken;
- }
-}
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 0ac9457eff..6c0477a51c 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
@@ -18,75 +18,48 @@
*/
package org.apache.gravitino.lance.service.rest;
-import java.util.NoSuchElementException;
-import javax.ws.rs.BadRequestException;
+import static
org.apache.gravitino.lance.common.ops.NamespaceWrapper.NAMESPACE_DELIMITER_DEFAULT;
+
+import com.lancedb.lance.namespace.model.ListNamespacesResponse;
+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.NotFoundException;
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.MediaType;
import javax.ws.rs.core.Response;
-import org.apache.gravitino.lance.common.ops.LanceCatalogService;
+import org.apache.gravitino.lance.common.ops.NamespaceWrapper;
+import org.apache.gravitino.lance.service.LanceExceptionMapper;
@Path("/v1/namespace")
+@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class LanceNamespaceOperations {
- private final LanceCatalogService catalogService;
+ private final NamespaceWrapper lanceNamespace;
- public LanceNamespaceOperations(LanceCatalogService catalogService) {
- this.catalogService = catalogService;
+ @Inject
+ public LanceNamespaceOperations(NamespaceWrapper lanceNamespace) {
+ this.lanceNamespace = lanceNamespace;
}
@GET
@Path("/{id}/list")
public Response listNamespaces(
@Encoded @PathParam("id") String namespaceId,
- @DefaultValue("$") @QueryParam("delimiter") String delimiter,
- @QueryParam("page_token") String pageToken,
- @QueryParam("limit") Integer limit) {
- try {
- LanceCatalogService.NamespaceListingResult result =
- catalogService.listChildNamespaces(namespaceId, delimiter,
pageToken, limit);
- LanceListNamespacesResponse payload =
- new LanceListNamespacesResponse(
- result.getParentId(),
- result.getDelimiter(),
- result.getNamespaces(),
- result.getNextPageToken().orElse(null));
- return Response.ok(payload).build();
- } catch (NoSuchElementException nse) {
- throw new NotFoundException(nse.getMessage(), nse);
- } catch (IllegalArgumentException iae) {
- throw new BadRequestException(iae.getMessage(), iae);
- }
- }
-
- @GET
- @Path("/{id}/table/list")
- public Response listTables(
- @Encoded @PathParam("id") String namespaceId,
- @DefaultValue("$") @QueryParam("delimiter") String delimiter,
+ @DefaultValue(NAMESPACE_DELIMITER_DEFAULT) @QueryParam("delimiter")
String delimiter,
@QueryParam("page_token") String pageToken,
@QueryParam("limit") Integer limit) {
try {
- LanceCatalogService.TableListingResult result =
- catalogService.listTables(namespaceId, delimiter, pageToken, limit);
- LanceListTablesResponse payload =
- new LanceListTablesResponse(
- result.getNamespaceId(),
- result.getDelimiter(),
- result.getTables(),
- result.getNextPageToken().orElse(null));
- return Response.ok(payload).build();
- } catch (NoSuchElementException nse) {
- throw new NotFoundException(nse.getMessage(), nse);
- } catch (IllegalArgumentException iae) {
- throw new BadRequestException(iae.getMessage(), iae);
+ ListNamespacesResponse response =
+ lanceNamespace.asNamespaceOps().listNamespaces(namespaceId,
delimiter, pageToken, limit);
+ return Response.ok(response).build();
+ } catch (Exception e) {
+ return LanceExceptionMapper.toRESTResponse(namespaceId, e);
}
}
}
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
new file mode 100644
index 0000000000..10f7399c40
--- /dev/null
+++
b/lance/lance-rest-server/src/main/java/org/apache/gravitino/lance/service/rest/LanceTableOperations.java
@@ -0,0 +1,63 @@
+/*
+ * 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 com.lancedb.lance.namespace.model.ListTablesResponse;
+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.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import org.apache.gravitino.lance.common.ops.NamespaceWrapper;
+import org.apache.gravitino.lance.service.LanceExceptionMapper;
+
+@Path("/v1/namespace/{id}/table")
+@Consumes(MediaType.APPLICATION_JSON)
+@Produces(MediaType.APPLICATION_JSON)
+public class LanceTableOperations {
+
+ private final NamespaceWrapper lanceNamespace;
+
+ @Inject
+ public LanceTableOperations(NamespaceWrapper lanceNamespace) {
+ this.lanceNamespace = lanceNamespace;
+ }
+
+ @GET
+ @Path("/list")
+ public Response listTables(
+ @Encoded @PathParam("id") String namespaceId,
+ @DefaultValue("$") @QueryParam("delimiter") String delimiter,
+ @QueryParam("page_token") String pageToken,
+ @QueryParam("limit") Integer limit) {
+ try {
+ ListTablesResponse response =
+ lanceNamespace.asTableOps().listTables(namespaceId, delimiter,
pageToken, limit);
+ return Response.ok(response).build();
+ } catch (Exception e) {
+ return LanceExceptionMapper.toRESTResponse(namespaceId, e);
+ }
+ }
+}