From 8ece20896e3a9b446a094dd1b142ca499ae72600 Mon Sep 17 00:00:00 2001 From: Vlad Poenaru Date: Wed, 11 Nov 2026 08:15:54 -0800 Subject: [PATCH] database: open relative paths as local directories `semcode-index -d db/foo` fails with "cannot connect to LanceDb Cloud unless the 'remote' feature is enabled". Any relative database path whose first component starts with "db" fails to open the same way, `dbdata/x` included. lancedb 0.27 sends every URI that starts with "db" to its cloud client, though only "db://" is meant for it. `DatabaseManager::new()` now passes the path to lancedb through `local_uri()`, which prefixes "./" to a path that starts with "db" but not "db://". That names the same directory in a string that does not start with "db". Every other path passes unchanged, and "db://" behaves as before. `local_uri()` can go once semcode's lancedb dependency checks for "db://". `tests/relative_db_path.rs` fails on main with the cloud error above and passes with this change. It opens a database by its relative path under a "db"-prefixed directory in the working directory, and creates the tables. Unit tests over a table of paths cover `local_uri()` itself. --- src/database/connection.rs | 53 +++++++++++++++++++++++++++++++++++++- tests/relative_db_path.rs | 36 ++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 tests/relative_db_path.rs diff --git a/src/database/connection.rs b/src/database/connection.rs index 22d8d48..18f14f2 100644 --- a/src/database/connection.rs +++ b/src/database/connection.rs @@ -30,6 +30,7 @@ use crate::types::{ use crate::vectorizer::CodeVectorizer; use crate::workdir::WorkdirIndex; use crate::worktree::{WorkingCopy, WorkingCopyHashes}; +use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use std::path::Path; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; @@ -209,12 +210,28 @@ pub struct DatabaseManager { callees_memo: std::sync::RwLock>>, } +/// The URI to give lancedb for a local database path. +/// +/// lancedb 0.27 sends every URI that starts with `db` to its LanceDB Cloud +/// client, not only `db://` ones, so a relative path such as `db/x` or +/// `dbdata/x` fails to open. Such a path is passed with a leading `./`, +/// which names the same directory and does not start with `db`. Every +/// other path passes unchanged. This can go once semcode's lancedb checks +/// for `db://`. +fn local_uri(path: &str) -> Cow<'_, str> { + if path.starts_with("db") && !path.starts_with("db://") { + Cow::Owned(format!("./{path}")) + } else { + Cow::Borrowed(path) + } +} + impl DatabaseManager { pub async fn new(db_path: &str, git_repo_path: String) -> Result { let database_options = ListingDatabaseOptions::builder() .data_storage_version(LanceFileVersion::V2_2) .build(); - let connection = lancedb::connect(db_path) + let connection = lancedb::connect(&local_uri(db_path)) .database_options(&database_options) .execute() .await?; @@ -9401,3 +9418,37 @@ mod anonymous_members { assert!(!DatabaseManager::calls_json_names_function(r#"["read"]"#, "Io::read").unwrap()); } } + +#[cfg(test)] +mod local_uri_tests { + use super::local_uri; + + #[test] + fn paths_starting_with_db_get_a_leading_dot_slash() { + for (path, uri) in [ + ("db/x", "./db/x"), + ("dbx/y", "./dbx/y"), + ("db", "./db"), + ("dbx:y", "./dbx:y"), + ] { + assert_eq!(local_uri(path), uri, "{path}"); + } + } + + #[test] + fn every_other_path_passes_unchanged() { + for path in [ + "./db/x", + "../db", + "/abs/db", + "x/db", + ".semcode.db", + "file:/abs/db", + "db://cloud", + "s3://bucket/db", + "", + ] { + assert_eq!(local_uri(path), path, "{path}"); + } + } +} diff --git a/tests/relative_db_path.rs b/tests/relative_db_path.rs new file mode 100644 index 0000000..428856d --- /dev/null +++ b/tests/relative_db_path.rs @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// A database named by a relative path opens as a local directory, whatever +// its first component is called. +use semcode::DatabaseManager; + +/// A relative path whose first component starts with `db` names a local +/// directory, not a LanceDB Cloud database. +#[tokio::test] +async fn relative_path_starting_with_db_opens_locally() { + // Relative to the working directory, which is where a relative `-d` + // resolves; removed when `dir` drops. + let dir = tempfile::Builder::new() + .prefix("db") + .tempdir_in(".") + .unwrap(); + let first_component = dir.path().file_name().unwrap().to_str().unwrap(); + let db_path = format!("{first_component}/.semcode.db"); + assert!(db_path.starts_with("db"), "{db_path}"); + + let db = DatabaseManager::new(&db_path, ".".to_string()) + .await + .unwrap(); + db.create_tables().await.unwrap(); + + assert!( + dir.path().join(".semcode.db").is_dir(), + "tables were not written under {db_path}" + ); + assert!(db + .list_tables() + .await + .unwrap() + .iter() + .any(|t| t == "functions")); +}