mirror of
https://github.com/lancedb/lancedb.git
synced 2025-12-26 06:39:57 +00:00
feat: use rest namespace for lancedb java sdk (#2845)
After the refactoring on both client and server side, we should have the ability to fully use lance REST namespace to call into LanceDB cloud and enterprise. We can avoid having a JNI implementation (which today does not really do anything except for vending a connection object), and just use lance-core's RestNamespace. We will at this moment have a LanceDbRestNamespaceBuilder to allow users to more easily build the RestNamespace to talk to LanceDB Cloud or Enterprise endpoint. In the future, we could extend this further to also support the local mode through DirectoryNamespace. That will be a separated PR.
This commit is contained in:
99
java/lancedb-core/pom.xml
Normal file
99
java/lancedb-core/pom.xml
Normal file
@@ -0,0 +1,99 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.lancedb</groupId>
|
||||
<artifactId>lancedb-parent</artifactId>
|
||||
<version>0.23.0-beta.0</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>lancedb-core</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<description>Utilities to work with LanceDB Cloud and Enterprise via Lance REST Namespace</description>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.lance</groupId>
|
||||
<artifactId>lance-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.arrow</groupId>
|
||||
<artifactId>arrow-vector</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.arrow</groupId>
|
||||
<artifactId>arrow-memory-netty</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-junit-jupiter</artifactId>
|
||||
<version>5.18.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>2.0.16</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-slf4j2-impl</artifactId>
|
||||
<version>2.24.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-core</artifactId>
|
||||
<version>2.24.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-api</artifactId>
|
||||
<version>2.24.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar-no-fork</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Licensed 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 com.lancedb;
|
||||
|
||||
import org.lance.namespace.RestNamespace;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Util class to help construct a {@link RestNamespace} for LanceDB.
|
||||
*
|
||||
* <p>For LanceDB Cloud, use the simplified builder API:
|
||||
*
|
||||
* <pre>{@code
|
||||
* import org.lance.namespace.RestNamespace;
|
||||
*
|
||||
* // If your DB url is db://example-db, then your database here is example-db
|
||||
* RestNamespace namespace = LanceDbRestNamespaceBuilder.newBuilder()
|
||||
* .apiKey("your_lancedb_cloud_api_key")
|
||||
* .database("your_database_name")
|
||||
* .build();
|
||||
* }</pre>
|
||||
*
|
||||
* <p>For LanceDB Enterprise deployments, use your custom endpoint:
|
||||
*
|
||||
* <pre>{@code
|
||||
* RestNamespace namespace = LanceDbRestNamespaceBuilder.newBuilder()
|
||||
* .apiKey("your_lancedb_enterprise_api_key")
|
||||
* .database("your_database_name")
|
||||
* .endpoint("<your_enterprise_endpoint>")
|
||||
* .build();
|
||||
* }</pre>
|
||||
*/
|
||||
public class LanceDbRestNamespaceBuilder {
|
||||
private static final String DEFAULT_REGION = "us-east-1";
|
||||
private static final String CLOUD_URL_PATTERN = "https://%s.%s.api.lancedb.com";
|
||||
|
||||
private String apiKey;
|
||||
private String database;
|
||||
private Optional<String> endpoint = Optional.empty();
|
||||
private Optional<String> region = Optional.empty();
|
||||
private Map<String, String> additionalConfig = new HashMap<>();
|
||||
|
||||
private LanceDbRestNamespaceBuilder() {}
|
||||
|
||||
/**
|
||||
* Create a new builder instance.
|
||||
*
|
||||
* @return A new RestNamespaceBuilder
|
||||
*/
|
||||
public static LanceDbRestNamespaceBuilder newBuilder() {
|
||||
return new LanceDbRestNamespaceBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the API key (required).
|
||||
*
|
||||
* @param apiKey The LanceDB API key
|
||||
* @return This builder
|
||||
*/
|
||||
public LanceDbRestNamespaceBuilder apiKey(String apiKey) {
|
||||
if (apiKey == null || apiKey.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("API key cannot be null or empty");
|
||||
}
|
||||
this.apiKey = apiKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the database name (required).
|
||||
*
|
||||
* @param database The database name
|
||||
* @return This builder
|
||||
*/
|
||||
public LanceDbRestNamespaceBuilder database(String database) {
|
||||
if (database == null || database.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("Database cannot be null or empty");
|
||||
}
|
||||
this.database = database;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a custom endpoint URL (optional). When set, this overrides the default LanceDB Cloud URL
|
||||
* construction. Use this for LanceDB Enterprise deployments.
|
||||
*
|
||||
* @param endpoint The complete base URL for your LanceDB Enterprise deployment
|
||||
* @return This builder
|
||||
*/
|
||||
public LanceDbRestNamespaceBuilder endpoint(String endpoint) {
|
||||
this.endpoint = Optional.ofNullable(endpoint);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the region for LanceDB Cloud (optional). Defaults to "us-east-1" if not specified. This is
|
||||
* ignored when endpoint is set.
|
||||
*
|
||||
* @param region The AWS region (e.g., "us-east-1", "eu-west-1")
|
||||
* @return This builder
|
||||
*/
|
||||
public LanceDbRestNamespaceBuilder region(String region) {
|
||||
this.region = Optional.ofNullable(region);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additional configuration parameters.
|
||||
*
|
||||
* @param key The configuration key
|
||||
* @param value The configuration value
|
||||
* @return This builder
|
||||
*/
|
||||
public LanceDbRestNamespaceBuilder config(String key, String value) {
|
||||
this.additionalConfig.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the Lance RestNamespace instance.
|
||||
*
|
||||
* @return A configured Lance RestNamespace
|
||||
* @throws IllegalStateException if required parameters are missing
|
||||
*/
|
||||
public RestNamespace build() {
|
||||
// Validate required fields
|
||||
if (apiKey == null) {
|
||||
throw new IllegalStateException("API key is required");
|
||||
}
|
||||
if (database == null) {
|
||||
throw new IllegalStateException("Database is required");
|
||||
}
|
||||
|
||||
// Build configuration map
|
||||
Map<String, String> config = new HashMap<>(additionalConfig);
|
||||
config.put("header.x-lancedb-database", database);
|
||||
config.put("header.x-api-key", apiKey);
|
||||
|
||||
// Determine base URL
|
||||
String uri;
|
||||
if (endpoint.isPresent()) {
|
||||
uri = endpoint.get();
|
||||
} else {
|
||||
String effectiveRegion = region.orElse(DEFAULT_REGION);
|
||||
uri = String.format(CLOUD_URL_PATTERN, database, effectiveRegion);
|
||||
}
|
||||
config.put("uri", uri);
|
||||
RestNamespace ns = new RestNamespace();
|
||||
ns.initialize(config, null);
|
||||
return ns;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Licensed 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 com.lancedb;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/** Unit tests for LanceDbRestNamespaceBuilder. */
|
||||
public class LanceDbRestNamespaceBuilderTest {
|
||||
|
||||
@Test
|
||||
public void testBuilderRequiresApiKey() {
|
||||
LanceDbRestNamespaceBuilder builder =
|
||||
LanceDbRestNamespaceBuilder.newBuilder().database("test-db");
|
||||
|
||||
IllegalStateException exception = assertThrows(IllegalStateException.class, builder::build);
|
||||
assertEquals("API key is required", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuilderRequiresDatabase() {
|
||||
LanceDbRestNamespaceBuilder builder =
|
||||
LanceDbRestNamespaceBuilder.newBuilder().apiKey("test-api-key");
|
||||
|
||||
IllegalStateException exception = assertThrows(IllegalStateException.class, builder::build);
|
||||
assertEquals("Database is required", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApiKeyCannotBeNull() {
|
||||
IllegalArgumentException exception =
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> LanceDbRestNamespaceBuilder.newBuilder().apiKey(null));
|
||||
assertEquals("API key cannot be null or empty", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApiKeyCannotBeEmpty() {
|
||||
IllegalArgumentException exception =
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> LanceDbRestNamespaceBuilder.newBuilder().apiKey(" "));
|
||||
assertEquals("API key cannot be null or empty", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDatabaseCannotBeNull() {
|
||||
IllegalArgumentException exception =
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> LanceDbRestNamespaceBuilder.newBuilder().database(null));
|
||||
assertEquals("Database cannot be null or empty", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDatabaseCannotBeEmpty() {
|
||||
IllegalArgumentException exception =
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> LanceDbRestNamespaceBuilder.newBuilder().database(" "));
|
||||
assertEquals("Database cannot be null or empty", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuilderFluentApi() {
|
||||
// Verify the builder returns itself for chaining
|
||||
LanceDbRestNamespaceBuilder builder = LanceDbRestNamespaceBuilder.newBuilder();
|
||||
|
||||
assertSame(builder, builder.apiKey("test-key"));
|
||||
assertSame(builder, builder.database("test-db"));
|
||||
assertSame(builder, builder.endpoint("http://localhost:8080"));
|
||||
assertSame(builder, builder.region("eu-west-1"));
|
||||
assertSame(builder, builder.config("custom-key", "custom-value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNewBuilderCreatesNewInstance() {
|
||||
LanceDbRestNamespaceBuilder builder1 = LanceDbRestNamespaceBuilder.newBuilder();
|
||||
LanceDbRestNamespaceBuilder builder2 = LanceDbRestNamespaceBuilder.newBuilder();
|
||||
|
||||
assertNotSame(builder1, builder2);
|
||||
}
|
||||
}
|
||||
32
java/lancedb-core/src/test/resources/log4j2.xml
Normal file
32
java/lancedb-core/src/test/resources/log4j2.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
|
||||
<!--
|
||||
Licensed 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.
|
||||
-->
|
||||
|
||||
<configuration monitorInterval="30">
|
||||
<appenders>
|
||||
<Console name='Console' target='SYSTEM_ERR'>
|
||||
<PatternLayout pattern='%d{HH:mm:ss.SSS} %p [%t] %C{1}.%M: %m%n'/>
|
||||
</Console>
|
||||
</appenders>
|
||||
<loggers>
|
||||
<logger name='com.lancedb' level='DEBUG' additivity='false'>
|
||||
<appender-ref ref='Console'/>
|
||||
</logger>
|
||||
<root level='INFO'>
|
||||
<appender-ref ref='Console'/>
|
||||
</root>
|
||||
</loggers>
|
||||
</configuration>
|
||||
Reference in New Issue
Block a user