diff --git a/rust/lancedb/src/database/listing.rs b/rust/lancedb/src/database/listing.rs index 824f73e9..35fd07e3 100644 --- a/rust/lancedb/src/database/listing.rs +++ b/rust/lancedb/src/database/listing.rs @@ -587,7 +587,13 @@ impl ListingDatabase { #[async_trait::async_trait] impl Database for ListingDatabase { - async fn list_namespaces(&self, _request: ListNamespacesRequest) -> Result> { + async fn list_namespaces(&self, request: ListNamespacesRequest) -> Result> { + if !request.namespace.is_empty() { + return Err(Error::NotSupported { + message: "Namespace operations are not supported for listing database".into(), + }); + } + Ok(Vec::new()) } diff --git a/rust/lancedb/src/utils.rs b/rust/lancedb/src/utils.rs index 8d2304e3..02614958 100644 --- a/rust/lancedb/src/utils.rs +++ b/rust/lancedb/src/utils.rs @@ -20,6 +20,8 @@ use datafusion_physical_plan::SendableRecordBatchStream; lazy_static! { static ref TABLE_NAME_REGEX: regex::Regex = regex::Regex::new(r"^[a-zA-Z0-9_\-\.]+$").unwrap(); + static ref NAMESPACE_NAME_REGEX: regex::Regex = + regex::Regex::new(r"^[a-zA-Z0-9_\-\.]+$").unwrap(); } pub trait PatchStoreParam { @@ -98,6 +100,53 @@ pub fn validate_table_name(name: &str) -> Result<()> { Ok(()) } +/// Validate a namespace name component +/// +/// Namespace names must: +/// - Not be empty +/// - Only contain alphanumeric characters, underscores, hyphens, and periods +/// +/// # Arguments +/// * `name` - A single namespace component (not the full path) +/// +/// # Returns +/// * `Ok(())` if the namespace name is valid +/// * `Err(Error)` if the namespace name is invalid +pub fn validate_namespace_name(name: &str) -> Result<()> { + if name.is_empty() { + return Err(Error::InvalidInput { + message: "Namespace names cannot be empty strings".to_string(), + }); + } + if !NAMESPACE_NAME_REGEX.is_match(name) { + return Err(Error::InvalidInput { + message: format!( + "Invalid namespace name '{}': Namespace names can only contain alphanumeric characters, underscores, hyphens, and periods", + name + ), + }); + } + Ok(()) +} + +/// Validate all components of a namespace +/// +/// Iterates through all namespace components and validates each one. +/// Returns an error if any component is invalid. +/// +/// # Arguments +/// * `namespace` - The namespace components to validate +/// +/// # Returns +/// * `Ok(())` if all namespace components are valid +/// * `Err(Error)` if any component is invalid +pub fn validate_namespace(namespace: &[String]) -> Result<()> { + for component in namespace { + validate_namespace_name(component)?; + } + Ok(()) +} + /// Find one default column to create index or perform vector query. pub(crate) fn default_vector_column(schema: &Schema, dim: Option) -> Result { // Try to find a vector column. @@ -345,6 +394,61 @@ mod tests { assert!(validate_table_name("name with space").is_err()); } + #[test] + fn test_validate_namespace_name() { + // Valid namespace names + assert!(validate_namespace_name("ns1").is_ok()); + assert!(validate_namespace_name("namespace_123").is_ok()); + assert!(validate_namespace_name("my-namespace").is_ok()); + assert!(validate_namespace_name("my.namespace").is_ok()); + assert!(validate_namespace_name("NS_1.2.3").is_ok()); + assert!(validate_namespace_name("a").is_ok()); + assert!(validate_namespace_name("123").is_ok()); + assert!(validate_namespace_name("_underscore").is_ok()); + assert!(validate_namespace_name("-hyphen").is_ok()); + assert!(validate_namespace_name(".period").is_ok()); + + // Invalid namespace names + assert!(validate_namespace_name("").is_err()); + assert!(validate_namespace_name("namespace with spaces").is_err()); + assert!(validate_namespace_name("namespace/with/slashes").is_err()); + assert!(validate_namespace_name("namespace\\with\\backslashes").is_err()); + assert!(validate_namespace_name("namespace$with$delimiter").is_err()); + assert!(validate_namespace_name("namespace@special").is_err()); + assert!(validate_namespace_name("namespace#hash").is_err()); + } + + #[test] + fn test_validate_namespace() { + // Valid namespace with single component + assert!(validate_namespace(&["ns1".to_string()]).is_ok()); + + // Valid namespace with multiple components + assert!( + validate_namespace(&["ns1".to_string(), "ns2".to_string(), "ns3".to_string()]).is_ok() + ); + + // Empty namespace (root) is valid + assert!(validate_namespace(&[]).is_ok()); + + // Invalid: contains empty component + assert!(validate_namespace(&["ns1".to_string(), "".to_string()]).is_err()); + + // Invalid: contains component with spaces + assert!(validate_namespace(&["ns1".to_string(), "ns 2".to_string()]).is_err()); + + // Invalid: contains component with special characters + assert!(validate_namespace(&["ns1".to_string(), "ns@2".to_string()]).is_err()); + assert!(validate_namespace(&["ns1".to_string(), "ns/2".to_string()]).is_err()); + assert!(validate_namespace(&["ns1".to_string(), "ns$2".to_string()]).is_err()); + + // Valid: underscores, hyphens, and periods are allowed + assert!( + validate_namespace(&["ns_1".to_string(), "ns-2".to_string(), "ns.3".to_string()]) + .is_ok() + ); + } + #[test] fn test_string_to_datatype() { let string = "int32";