lance_graph/namespace/
directory.rs1use async_trait::async_trait;
4use lance_namespace::models::{DescribeTableRequest, DescribeTableResponse};
5use lance_namespace::{Error as NamespaceError, LanceNamespace, Result};
6use snafu::location;
7
8#[derive(Debug, Clone)]
10pub struct DirNamespace {
11 base_uri: String,
12}
13
14impl DirNamespace {
15 pub fn new(base_uri: impl Into<String>) -> Self {
19 let uri = base_uri.into();
20 let clean_uri = uri.trim_end_matches('/').to_string();
21 Self {
22 base_uri: clean_uri,
23 }
24 }
25
26 pub fn base_uri(&self) -> &str {
28 &self.base_uri
29 }
30}
31
32#[async_trait]
33impl LanceNamespace for DirNamespace {
34 fn namespace_id(&self) -> String {
35 format!("DirNamespace {{ base_uri: '{}' }}", self.base_uri)
36 }
37
38 async fn describe_table(&self, request: DescribeTableRequest) -> Result<DescribeTableResponse> {
39 let id = request.id.ok_or_else(|| {
40 NamespaceError::invalid_input(
41 "DirNamespace requires the table identifier to be provided",
42 location!(),
43 )
44 })?;
45
46 if id.len() != 1 {
47 return Err(NamespaceError::invalid_input(
48 format!(
49 "DirNamespace expects identifiers with a single component, got {:?}",
50 id
51 ),
52 location!(),
53 ));
54 }
55
56 let table_name = &id[0];
57 let location = format!("{}/{}.lance", self.base_uri, table_name);
58
59 let mut response = DescribeTableResponse::new();
60 response.location = Some(location);
61 response.storage_options = None;
62 Ok(response)
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69
70 #[tokio::test]
71 async fn describe_table_returns_clean_location() {
72 let namespace = DirNamespace::new("s3://bucket/path/");
73 let mut request = DescribeTableRequest::new();
74 request.id = Some(vec!["users".to_string()]);
75
76 let response = namespace.describe_table(request).await.unwrap();
77 assert_eq!(
78 response.location.as_deref(),
79 Some("s3://bucket/path/users.lance")
80 );
81 }
82
83 #[tokio::test]
84 async fn describe_table_rejects_missing_identifier() {
85 let namespace = DirNamespace::new("file:///tmp");
86 let request = DescribeTableRequest::new();
87
88 let err = namespace.describe_table(request).await.unwrap_err();
89 assert!(
90 err.to_string()
91 .contains("DirNamespace requires the table identifier"),
92 "unexpected error: {err}"
93 );
94 }
95
96 #[tokio::test]
97 async fn describe_table_rejects_multi_component_identifier() {
98 let namespace = DirNamespace::new("memory://namespace");
99 let mut request = DescribeTableRequest::new();
100 request.id = Some(vec!["foo".into(), "bar".into()]);
101
102 let err = namespace.describe_table(request).await.unwrap_err();
103 assert!(
104 err.to_string()
105 .contains("expects identifiers with a single component"),
106 "unexpected error: {err}"
107 );
108 }
109}