diff --git a/rust/lancedb/src/connection.rs b/rust/lancedb/src/connection.rs index 89e59e12e..77eddd441 100644 --- a/rust/lancedb/src/connection.rs +++ b/rust/lancedb/src/connection.rs @@ -656,6 +656,7 @@ pub struct ConnectRequest { /// - `/path/to/database` - local database on file system. /// - `s3://bucket/path/to/database` or `gs://bucket/path/to/database` - database on cloud object store /// - `db://dbname` - LanceDB Cloud + /// - `db://` with a host override - remote tables in the storage root pub uri: String, #[cfg(feature = "remote")] @@ -768,6 +769,8 @@ impl ConnectBuilder { /// /// This option is only used when connecting to LanceDB Cloud (db:// URIs) /// and will be ignored for other URIs. + /// Use the URI `db://` together with a host override to connect to remote + /// tables stored directly in the storage root. /// /// # Arguments /// @@ -1354,6 +1357,34 @@ mod tests { } } + #[cfg(feature = "remote")] + #[tokio::test] + async fn test_connect_remote_storage_root() { + let conn = ConnectBuilder::new("db://") + .region("us-east-1") + .api_key("my-api-key") + .host_override("https://example.com") + .execute() + .await + .unwrap(); + + let (impl_name, properties) = conn.namespace_client_config().await.unwrap(); + assert_eq!(impl_name, "rest"); + assert_eq!(properties["uri"], "https://example.com"); + assert_eq!(properties["header.x-lancedb-database"], ""); + + let result = ConnectBuilder::new("db://") + .region("us-east-1") + .api_key("my-api-key") + .execute() + .await; + assert!(matches!( + result, + Err(Error::InvalidInput { message }) + if message.contains("A host override is required") + )); + } + #[cfg(feature = "remote")] #[tokio::test] async fn test_connect_rejects_header_provider_with_oauth_config() { diff --git a/rust/lancedb/src/lib.rs b/rust/lancedb/src/lib.rs index 70d023ccc..d03e2eb93 100644 --- a/rust/lancedb/src/lib.rs +++ b/rust/lancedb/src/lib.rs @@ -54,6 +54,7 @@ //! - `/path/to/database` - local database on file system. //! - `s3://bucket/path/to/database` or `gs://bucket/path/to/database` - database on cloud object store //! - `db://dbname` - Lance Cloud +//! - `db://` with a host override - remote tables in the storage root //! //! You can also use [`ConnectBuilder`] to configure the connection to the database. //! diff --git a/rust/lancedb/src/remote/client.rs b/rust/lancedb/src/remote/client.rs index 9e34fca9f..bb781be82 100644 --- a/rust/lancedb/src/remote/client.rs +++ b/rust/lancedb/src/remote/client.rs @@ -349,18 +349,22 @@ pub struct ParsedDbUrl { /// Parse a database URL and extract the database name and optional prefix. /// -/// Expected format: `db://db_name` or `db://db_name/prefix` +/// Expected format: `db://db_name`, `db://db_name/prefix`, or `db://` when +/// connecting to the storage root through a host override. pub fn parse_db_url(db_url: &str) -> Result { let parsed_url = url::Url::parse(db_url).map_err(|err| Error::InvalidInput { message: format!("db_url is not a valid URL. '{db_url}'. Error: {err}"), })?; debug_assert_eq!(parsed_url.scheme(), "db"); - if !parsed_url.has_host() { - return Err(Error::InvalidInput { - message: format!("Invalid database URL (missing host) '{}'", db_url), - }); - } - let db_name = parsed_url.host_str().unwrap().to_string(); + let db_name = match parsed_url.host_str() { + Some(db_name) => db_name.to_string(), + None if matches!(parsed_url.path(), "" | "/") => String::new(), + None => { + return Err(Error::InvalidInput { + message: format!("Invalid database URL (missing host) '{}'", db_url), + }); + } + }; let db_prefix = { let prefix = parsed_url.path().trim_start_matches('/'); if prefix.is_empty() { diff --git a/rust/lancedb/src/remote/db.rs b/rust/lancedb/src/remote/db.rs index 839cb3797..a8c7fa73a 100644 --- a/rust/lancedb/src/remote/db.rs +++ b/rust/lancedb/src/remote/db.rs @@ -272,6 +272,13 @@ impl RemoteDatabase { read_consistency_interval: Option, ) -> Result { let parsed = super::client::parse_db_url(uri)?; + if parsed.db_name.is_empty() && host_override.is_none() { + return Err(Error::InvalidInput { + message: + "A host override is required when connecting to the storage root with 'db://'" + .to_string(), + }); + } let header_map = RestfulLanceDbClient::::default_headers( api_key, region,