mirror of
https://github.com/lancedb/lancedb.git
synced 2026-05-22 06:20:39 +00:00
feat(nodejs): expose connectNamespace for namespace-backed connections (#3383)
### Summary Adds a `connectNamespace(implName, properties, options?)` to the NodeJS SDK`. Closes #3380. ### Testing - pnpm test - Ran smoke test ``` import { connectNamespace } from "lancedb" import { tmpdir } from "os"; import { mkdtempSync } from "fs"; import { join } from "path"; const dir = mkdtempSync(join(tmpdir(), "lancedb-connect-namespace-smoke-")); console.log(`Using temp dir: ${dir}\n`); // 1. Happy path: connect via the "dir" namespace impl, create + list a table. console.log('Connecting via connectNamespace("dir", { root })...'); const db = await connectNamespace("dir", { root: dir }); console.log(" ✓ connected:", db.display()); console.log("Creating a table and listing it..."); await db.createTable("users", [ { id: 1, name: "alice" }, { id: 2, name: "bob" }, ]); console.log(" ✓ tableNames ->", await db.tableNames()); const table = await db.openTable("users"); console.log(" ✓ users.countRows ->", await table.countRows()); // 2. Storage options pass-through. console.log("\nReconnecting with storageOptions (plumbing check)..."); const dbWithOpts = await connectNamespace( "dir", { root: dir }, { storageOptions: { newTableDataStorageVersion: "stable" } }, ); console.log(" ✓ connected with storageOptions:", dbWithOpts.display()); await dbWithOpts.close(); // 3. Empty implName -> clear error. console.log("\nCalling connectNamespace('', {}) (expect error)..."); try { await connectNamespace("", {}); console.error(" UNEXPECTED: empty implName did not throw"); } catch (err) { console.log(` ✓ Got expected error: ${err.message.split("\n")[0]}`); } // 4. Unknown impl -> error. console.log("\nCalling connectNamespace('not-a-real-impl', {}) (expect error)..."); try { await connectNamespace("not-a-real-impl", {}); console.error(" UNEXPECTED: unknown impl did not throw"); } catch (err) { console.log(` ✓ Got expected error: ${err.message.split("\n")[0]}`); } // 5. Create a table inside a child namespace, then reconnect with a fresh // connectNamespace call and confirm the table is reachable via that // namespace path. (The dir+manifest impl keeps the namespace hierarchy in // a root manifest, so "scoping" happens via namespacePath args, not by // pointing root at a subdir.) console.log("\nCreating a table inside a child namespace..."); const dir2 = mkdtempSync(join(tmpdir(), "lancedb-connect-namespace-smoke-")); const writer = await connectNamespace("dir", { root: dir2, manifest_enabled: "true", }); await writer.createNamespace(["analytics"]); await writer.createTable( "orders", [ { id: 1, total: 10 }, { id: 2, total: 20 }, ], ["analytics"], ); console.log( " ✓ writer sees tables under [analytics] ->", await writer.tableNames(["analytics"]), ); await writer.close(); console.log("Reconnecting and reading the table via its namespace path..."); const reader = await connectNamespace("dir", { root: dir2, manifest_enabled: "true", }); console.log( " ✓ reader tableNames(['analytics']) ->", await reader.tableNames(["analytics"]), ); const orders = await reader.openTable("orders", ["analytics"]); console.log(" ✓ orders.countRows via reader ->", await orders.countRows()); await reader.close(); await db.close(); console.log("\nAll checks passed."); ``` ``` Using temp dir: /var/folders/bj/hn6jv9c50y301d1nx0y8xmn00000gn/T/lancedb-connect-namespace-smoke-WByF1P Connecting via connectNamespace("dir", { root })... ✓ connected: LanceNamespaceDatabase Creating a table and listing it... ✓ tableNames -> [ 'users' ] ✓ users.countRows -> 2 Reconnecting with storageOptions (plumbing check)... ✓ connected with storageOptions: LanceNamespaceDatabase Calling connectNamespace('', {}) (expect error)... ✓ Got expected error: implName must be a non-empty string Calling connectNamespace('not-a-real-impl', {}) (expect error)... ✓ Got expected error: Invalid input, Failed to connect to namespace: Namespace { source: Unsupported { message: "Implementation 'not-a-real-impl' is not available. Supported: dir, rest" }, location: Location { file: "/Users/brendan/.cargo/git/checkouts/lance-8ddea23c38163eda/f693245/rust/lance-namespace-impls/src/connect.rs", line: 216, column: 14 } } Creating a table inside a child namespace... ✓ writer sees tables under [analytics] -> [ 'orders' ] Reconnecting and reading the table via its namespace path... ✓ reader tableNames(['analytics']) -> [ 'orders' ] ✓ orders.countRows via reader -> 2 All checks passed. ``` ### Docs - regenerated docs
This commit is contained in:
@@ -8,11 +8,12 @@ use lancedb::database::{CreateTableMode, Database};
|
||||
use napi::bindgen_prelude::*;
|
||||
use napi_derive::*;
|
||||
|
||||
use crate::ConnectNamespaceOptions;
|
||||
use crate::ConnectionOptions;
|
||||
use crate::error::NapiErrorExt;
|
||||
use crate::header::JsHeaderProvider;
|
||||
use crate::table::Table;
|
||||
use lancedb::connection::{ConnectBuilder, Connection as LanceDBConnection};
|
||||
use lancedb::connection::{ConnectBuilder, Connection as LanceDBConnection, connect_namespace};
|
||||
|
||||
use lance_namespace::models::{
|
||||
CreateNamespaceRequest, DescribeNamespaceRequest, DropNamespaceRequest, ListNamespacesRequest,
|
||||
@@ -132,6 +133,39 @@ impl Connection {
|
||||
Ok(Self::inner_new(builder.execute().await.default_error()?))
|
||||
}
|
||||
|
||||
/// Create a new Connection instance backed by a namespace implementation.
|
||||
#[napi(factory)]
|
||||
pub async fn new_with_namespace(
|
||||
impl_name: String,
|
||||
properties: HashMap<String, String>,
|
||||
options: ConnectNamespaceOptions,
|
||||
) -> napi::Result<Self> {
|
||||
if impl_name.is_empty() {
|
||||
return Err(napi::Error::from_reason(
|
||||
"implName must be a non-empty string",
|
||||
));
|
||||
}
|
||||
|
||||
let mut builder = connect_namespace(&impl_name, properties);
|
||||
if let Some(interval) = options.read_consistency_interval {
|
||||
builder =
|
||||
builder.read_consistency_interval(std::time::Duration::from_secs_f64(interval));
|
||||
}
|
||||
if let Some(storage_options) = options.storage_options {
|
||||
for (key, value) in storage_options {
|
||||
builder = builder.storage_option(key, value);
|
||||
}
|
||||
}
|
||||
if let Some(namespace_client_properties) = options.namespace_client_properties {
|
||||
builder = builder.namespace_client_properties(namespace_client_properties);
|
||||
}
|
||||
if let Some(session) = options.session {
|
||||
builder = builder.session(session.inner.clone());
|
||||
}
|
||||
|
||||
Ok(Self::inner_new(builder.execute().await.default_error()?))
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn display(&self) -> napi::Result<String> {
|
||||
Ok(self.get_inner()?.to_string())
|
||||
|
||||
@@ -67,6 +67,26 @@ pub struct OpenTableOptions {
|
||||
pub storage_options: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
#[derive(Debug)]
|
||||
pub struct ConnectNamespaceOptions {
|
||||
/// The interval, in seconds, at which to check for updates to the table
|
||||
/// from other processes. If None, then consistency is not checked. For
|
||||
/// performance reasons, this is the default. For strong consistency, set
|
||||
/// this to zero seconds. Then every read will check for updates from other
|
||||
/// processes. As a compromise, you can set this to a non-zero value for
|
||||
/// eventual consistency.
|
||||
pub read_consistency_interval: Option<f64>,
|
||||
/// Configuration for object storage. The available options are described
|
||||
/// at https://docs.lancedb.com/storage/
|
||||
pub storage_options: Option<HashMap<String, String>>,
|
||||
/// Extra properties for the backing namespace client.
|
||||
pub namespace_client_properties: Option<HashMap<String, String>>,
|
||||
/// The session to use for this connection. Holds shared caches and other
|
||||
/// session-specific state.
|
||||
pub session: Option<session::Session>,
|
||||
}
|
||||
|
||||
#[napi_derive::module_init]
|
||||
fn init() {
|
||||
let env = Env::new()
|
||||
|
||||
Reference in New Issue
Block a user