diff --git a/Cargo.lock b/Cargo.lock index abce62f2d2..c25d495a97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2031,6 +2031,7 @@ dependencies = [ "tempfile", "tokio", "tracing-appender", + "url", ] [[package]] diff --git a/src/cli/Cargo.toml b/src/cli/Cargo.toml index 90b4388e2b..2c8db6a945 100644 --- a/src/cli/Cargo.toml +++ b/src/cli/Cargo.toml @@ -72,3 +72,4 @@ common-test-util.workspace = true common-version.workspace = true serde.workspace = true tempfile.workspace = true +url.workspace = true diff --git a/src/cli/src/metadata/snapshot.rs b/src/cli/src/metadata/snapshot.rs index 5de20bc9bf..648d3a687d 100644 --- a/src/cli/src/metadata/snapshot.rs +++ b/src/cli/src/metadata/snapshot.rs @@ -288,6 +288,7 @@ fn build_object_store_and_resolve_file_path( #[cfg(test)] mod tests { use std::env; + use std::path::Path; use std::sync::Arc; use std::time::Duration; @@ -300,6 +301,14 @@ mod tests { use super::*; use crate::metadata::snapshot::RestoreCommand; + fn create_raftengine_url(path: &std::path::Path) -> String { + let mut path = path.to_string_lossy().replace('\\', "/"); + if !path.starts_with('/') { + path = format!("/{}", path); + } + format!("raftengine://{}", path) + } + #[tokio::test] async fn test_cmd_resolve_file_path() { common_telemetry::init_default_ut_logging(); @@ -364,25 +373,32 @@ mod tests { let root = temp_dir.path().display().to_string(); let object_store = new_fs_object_store(&root).unwrap(); setup_backup_file(object_store, "/backup/metadata_snapshot.metadata.fb").await; - + let metadata_path = temp_dir.path().join("metadata"); { let cmd = RestoreCommand::parse_from([ "", "--file_name", - format!("{}/backup/metadata_snapshot.metadata.fb", root).as_str(), + temp_dir + .path() + .join("backup") + .join("metadata_snapshot.metadata.fb") + .to_str() + .unwrap(), "--backend", "raft-engine-store", "--store-addrs", - format!("raftengine:///{}/metadata", root).as_str(), + &create_raftengine_url(&metadata_path), ]); let tool = cmd.build().await.unwrap(); tool.do_work().await.unwrap(); } // Waits for the raft engine release the file lock. tokio::time::sleep(Duration::from_secs(1)).await; - let kv = - standalone::build_metadata_kvbackend(format!("{}/metadata", root), Default::default()) - .unwrap(); + let kv = standalone::build_metadata_kvbackend( + metadata_path.display().to_string(), + Default::default(), + ) + .unwrap(); let value = kv.get(b"test").await.unwrap().unwrap().value; assert_eq!(value, b"test"); @@ -393,9 +409,10 @@ mod tests { common_telemetry::init_default_ut_logging(); let temp_dir = tempfile::tempdir().unwrap(); let root = temp_dir.path().display().to_string(); + let metadata_path = temp_dir.path().join("metadata"); { let kv = standalone::build_metadata_kvbackend( - format!("{}/metadata", root), + metadata_path.to_string_lossy().to_string(), Default::default(), ) .unwrap(); @@ -413,11 +430,16 @@ mod tests { let cmd = SaveCommand::parse_from([ "", "--file_name", - format!("{}/backup/metadata_snapshot.metadata.fb", root).as_str(), + temp_dir + .path() + .join("backup") + .join("metadata_snapshot.metadata.fb") + .to_str() + .unwrap(), "--backend", "raft-engine-store", "--store-addrs", - format!("raftengine:///{}/metadata", root).as_str(), + &create_raftengine_url(&metadata_path), ]); let tool = cmd.build().await.unwrap(); tool.do_work().await.unwrap(); @@ -434,4 +456,20 @@ mod tests { let value = kv_backend.get(b"test").await.unwrap().unwrap().value; assert_eq!(value, b"test"); } + + #[test] + fn test_path() { + let path = "C:\\Users\\user\\AppData\\Local\\Temp\\.tmpuPiVuB\\metadata"; + let path = Path::new(path); + let url = create_raftengine_url(path); + assert_eq!( + url, + "raftengine:///C:/Users/user/AppData/Local/Temp/.tmpuPiVuB/metadata" + ); + let url = url::Url::parse(&url).unwrap(); + assert_eq!( + url.path(), + "/C:/Users/user/AppData/Local/Temp/.tmpuPiVuB/metadata" + ); + } } diff --git a/src/standalone/src/metadata.rs b/src/standalone/src/metadata.rs index a439b1ca6a..9ff98829da 100644 --- a/src/standalone/src/metadata.rs +++ b/src/standalone/src/metadata.rs @@ -17,7 +17,7 @@ use std::sync::Arc; use common_config::KvBackendConfig; use common_error::ext::BoxedError; use common_meta::kv_backend::KvBackendRef; -use common_telemetry::info; +use common_telemetry::{debug, info}; use log_store::raft_engine::RaftEngineBackend; use snafu::{ResultExt, ensure}; use url::Url; @@ -39,6 +39,7 @@ pub fn build_metadata_kvbackend(dir: String, config: KvBackendConfig) -> Result< /// Builds the metadata kvbackend from a list of URLs. pub fn build_metadata_kv_from_url(url: &str) -> Result { + debug!("Building metadata kvbackend from url: {}", url); let url = Url::parse(url).context(ParseUrlSnafu { url })?; ensure!( url.scheme() == "raftengine", @@ -47,5 +48,21 @@ pub fn build_metadata_kv_from_url(url: &str) -> Result { } ); - build_metadata_kvbackend(url.path().to_string(), Default::default()) + let path = normalize_raftengine_path(url.path()); + build_metadata_kvbackend(path, Default::default()) +} + +fn normalize_raftengine_path(path: &str) -> String { + if cfg!(windows) { + let mut path = path.replace('\\', "/"); + if let Some(stripped) = path.strip_prefix('/') { + let bytes = stripped.as_bytes(); + if bytes.len() >= 2 && bytes[0].is_ascii_alphabetic() && bytes[1] == b':' { + path = stripped.to_string(); + } + } + path + } else { + path.to_string() + } }