Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 52 additions & 1 deletion src/database/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -209,12 +210,28 @@ pub struct DatabaseManager {
callees_memo: std::sync::RwLock<HashMap<CallerMemoKey, Vec<String>>>,
}

/// 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<Self> {
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?;
Expand Down Expand Up @@ -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}");
}
}
}
36 changes: 36 additions & 0 deletions tests/relative_db_path.rs
Original file line number Diff line number Diff line change
@@ -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"));
}
Loading