Skip to main content

storage_kit/databases/config/
lance.rs

1use std::path::{Path, PathBuf};
2
3use serde::{Deserialize, Serialize};
4
5use crate::StorageError;
6
7/// LanceDB embedded configuration.
8///
9/// This configuration accepts local filesystem directories and `memory://`.
10/// Native remote routes and `file:` URLs are rejected at validation. Construction
11/// does not open the database or create a directory.
12///
13/// # Examples
14///
15/// ```
16/// use storage_kit::database::config::{DatabaseConfig, LanceConfig};
17/// let config = LanceConfig::embedded_path("memory://");
18/// assert_eq!(config.default_vector_column.as_deref(), Some("vector"));
19/// DatabaseConfig::lance(config).validate()?;
20/// # Ok::<(), storage_kit::StorageError>(())
21/// ```
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct LanceConfig {
24    /// Filesystem directory used for embedded tables, or `memory://`.
25    /// Native remote and `file:` URI routes are not accepted.
26    pub path: PathBuf,
27    /// Default vector column used when a query omits its vector field.
28    pub default_vector_column: Option<String>,
29}
30
31impl LanceConfig {
32    /// Builds an embedded LanceDB configuration rooted at `path`.
33    ///
34    /// The default vector column is `vector`. The path is checked by
35    /// [`DatabaseConfig::validate`](crate::database::config::DatabaseConfig::validate)
36    /// and [`crate::Database::connect`], not by this infallible constructor.
37    ///
38    /// # Examples
39    ///
40    /// ```
41    /// use storage_kit::database::config::{DatabaseConfig, LanceConfig};
42    /// let config = LanceConfig::embedded_path("data/documents");
43    /// assert_eq!(config.path, std::path::PathBuf::from("data/documents"));
44    /// DatabaseConfig::lance(config).validate()?;
45    /// # Ok::<(), storage_kit::StorageError>(())
46    /// ```
47    #[must_use]
48    pub fn embedded_path(path: impl Into<PathBuf>) -> Self {
49        Self {
50            path: path.into(),
51            default_vector_column: Some("vector".into()),
52        }
53    }
54
55    pub(crate) fn validate(&self) -> Result<(), StorageError> {
56        if self.path.as_os_str().is_empty() {
57            return Err(StorageError::InvalidConfig(
58                "LanceConfig.path must not be empty".into(),
59            ));
60        }
61        let path = self.uri_string();
62        let bytes = path.as_bytes();
63        let windows_drive = bytes.len() >= 3
64            && bytes[0].is_ascii_alphabetic()
65            && bytes[1] == b':'
66            && matches!(bytes[2], b'/' | b'\\');
67        if path != "memory://" && !windows_drive && url::Url::parse(&path).is_ok() {
68            return Err(StorageError::InvalidConfig(
69                "LanceConfig.path accepts filesystem paths or memory://, not native URI routes"
70                    .into(),
71            ));
72        }
73        if self
74            .default_vector_column
75            .as_deref()
76            .is_some_and(|column| column.trim().is_empty())
77        {
78            return Err(StorageError::InvalidConfig(
79                "LanceConfig.default_vector_column must not be empty when configured".into(),
80            ));
81        }
82        Ok(())
83    }
84
85    pub(crate) fn uri_string(&self) -> String {
86        self.path.to_string_lossy().into_owned()
87    }
88
89    pub(crate) fn path(&self) -> &Path {
90        &self.path
91    }
92}