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

Reply via email to