use std::collections::HashMap;
use std::sync::Arc;
use lance::session::Session;
use lance_core::Result;
use lance_namespace::LanceNamespace;
use lance_namespace::error::NamespaceError;
use crate::context::DynamicContextProvider;
#[derive(Clone)]
pub struct ConnectBuilder {
impl_name: String,
properties: HashMap<String, String>,
session: Option<Arc<Session>>,
context_provider: Option<Arc<dyn DynamicContextProvider>>,
}
impl std::fmt::Debug for ConnectBuilder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ConnectBuilder")
.field("impl_name", &self.impl_name)
.field("properties", &self.properties)
.field("session", &self.session)
.field(
"context_provider",
&self.context_provider.as_ref().map(|_| "Some(...)"),
)
.finish()
}
}
impl ConnectBuilder {
pub fn new(impl_name: impl Into<String>) -> Self {
Self {
impl_name: impl_name.into(),
properties: HashMap::new(),
session: None,
context_provider: None,
}
}
pub fn property(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.properties.insert(key.into(), value.into());
self
}
pub fn properties(mut self, properties: HashMap<String, String>) -> Self {
self.properties.extend(properties);
self
}
pub fn session(mut self, session: Arc<Session>) -> Self {
self.session = Some(session);
self
}
pub fn context_provider(mut self, provider: Arc<dyn DynamicContextProvider>) -> Self {
self.context_provider = Some(provider);
self
}
pub async fn connect(self) -> Result<Arc<dyn LanceNamespace>> {
match self.impl_name.as_str() {
#[cfg(feature = "rest")]
"rest" => {
let mut builder =
crate::rest::RestNamespaceBuilder::from_properties(self.properties)?;
if let Some(provider) = self.context_provider {
builder = builder.context_provider(provider);
}
Ok(Arc::new(builder.build()) as Arc<dyn LanceNamespace>)
}
#[cfg(not(feature = "rest"))]
"rest" => Err(NamespaceError::Unsupported {
message: "REST namespace implementation requires 'rest' feature to be enabled"
.to_string(),
}
.into()),
"dir" => {
let mut builder = crate::dir::DirectoryNamespaceBuilder::from_properties(
self.properties,
self.session,
)?;
if let Some(provider) = self.context_provider {
builder = builder.context_provider(provider);
}
builder
.build()
.await
.map(|ns| Arc::new(ns) as Arc<dyn LanceNamespace>)
}
_ => Err(NamespaceError::Unsupported {
message: format!(
"Implementation '{}' is not available. Supported: dir{}",
self.impl_name,
if cfg!(feature = "rest") { ", rest" } else { "" }
),
}
.into()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use lance_core::utils::tempfile::TempStdDir;
use lance_namespace::models::ListTablesRequest;
use std::collections::HashMap;
#[tokio::test]
async fn test_connect_builder_basic() {
let temp_dir = TempStdDir::default();
let namespace = ConnectBuilder::new("dir")
.property("root", temp_dir.to_str().unwrap())
.connect()
.await
.unwrap();
let mut request = ListTablesRequest::new();
request.id = Some(vec![]);
let response = namespace.list_tables(request).await.unwrap();
assert_eq!(response.tables.len(), 0);
}
#[tokio::test]
async fn test_connect_builder_with_properties() {
let temp_dir = TempStdDir::default();
let mut props = HashMap::new();
props.insert("storage.option1".to_string(), "value1".to_string());
let namespace = ConnectBuilder::new("dir")
.property("root", temp_dir.to_str().unwrap())
.properties(props)
.connect()
.await
.unwrap();
let mut request = ListTablesRequest::new();
request.id = Some(vec![]);
let response = namespace.list_tables(request).await.unwrap();
assert_eq!(response.tables.len(), 0);
}
#[tokio::test]
async fn test_connect_builder_with_session() {
let temp_dir = TempStdDir::default();
let session = Arc::new(Session::default());
let namespace = ConnectBuilder::new("dir")
.property("root", temp_dir.to_str().unwrap())
.session(session.clone())
.connect()
.await
.unwrap();
let mut request = ListTablesRequest::new();
request.id = Some(vec![]);
let response = namespace.list_tables(request).await.unwrap();
assert_eq!(response.tables.len(), 0);
}
#[tokio::test]
async fn test_connect_builder_invalid_impl() {
let result = ConnectBuilder::new("invalid")
.property("root", "/tmp")
.connect()
.await;
assert!(result.is_err());
let err = result.err().unwrap();
assert!(err.to_string().contains("not available"));
}
}