diff --git a/Cargo.lock b/Cargo.lock index 90564d1475..f6f767f7b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2675,6 +2675,7 @@ dependencies = [ "sqlx-core", "sqlx-rt", "syn", + "tempfile", "url", ] diff --git a/sqlx-macros/Cargo.toml b/sqlx-macros/Cargo.toml index 4207796667..2884db4169 100644 --- a/sqlx-macros/Cargo.toml +++ b/sqlx-macros/Cargo.toml @@ -34,7 +34,7 @@ _rt-async-std = [] _rt-tokio = [] # offline building support -offline = ["sqlx-core/offline", "serde", "serde_json", "hex", "sha2"] +offline = ["sqlx-core/offline", "serde", "serde_json", "hex", "sha2", "tempfile"] # database mysql = [ "sqlx-core/mysql" ] @@ -67,5 +67,6 @@ serde = { version = "1.0.111", optional = true } serde_json = { version = "1.0.30", features = [ "preserve_order" ], optional = true } sha2 = { version = "0.9.1", optional = true } syn = { version = "1.0.30", default-features = false, features = [ "full" ] } +tempfile = { version = "3.1.0", optional = true } quote = { version = "1.0.6", default-features = false } url = { version = "2.1.1", default-features = false } diff --git a/sqlx-macros/src/query/data.rs b/sqlx-macros/src/query/data.rs index ee123b503f..aa0623a639 100644 --- a/sqlx-macros/src/query/data.rs +++ b/sqlx-macros/src/query/data.rs @@ -46,6 +46,7 @@ pub mod offline { use proc_macro2::Span; use serde::de::{Deserializer, IgnoredAny, MapAccess, Visitor}; use sqlx_core::describe::Describe; + use tempfile::NamedTempFile; #[derive(serde::Deserialize)] pub struct DynQueryData { @@ -99,22 +100,33 @@ pub mod offline { } } - pub fn save_in(&self, dir: impl AsRef, input_span: Span) -> crate::Result<()> { - // we save under the hash of the span representation because that should be unique - // per invocation - let path = dir.as_ref().join(format!( - "query-{}.json", - hash_string(&format!("{:?}", input_span)) - )); - - serde_json::to_writer_pretty( - BufWriter::new( - File::create(&path) - .map_err(|e| format!("failed to open path {}: {}", path.display(), e))?, - ), - self, - ) - .map_err(Into::into) + pub fn save_in(&self, dir: impl AsRef) -> crate::Result<()> { + let dir = dir.as_ref(); + + // We first write to a temporary file to then move it to the final location. + // This ensures no file corruption happens in case this method is called concurrently + // for the same query. + let file = NamedTempFile::new_in(dir).map_err(|e| { + format!( + "failed to create temporary file in {}: {}", + dir.display(), + e, + ) + })?; + + serde_json::to_writer_pretty(BufWriter::new(&file), self)?; + + let path = dir.join(format!("query-{}.json", hash_string(&self.query))); + file.persist(&path).map_err(|e| { + format!( + "failed to move temporary file {} to {}: {}", + e.file.path().display(), + path.display(), + e.error, + ) + })?; + + Ok(()) } } diff --git a/sqlx-macros/src/query/mod.rs b/sqlx-macros/src/query/mod.rs index be7e37d0d5..8c3f70eb4a 100644 --- a/sqlx-macros/src/query/mod.rs +++ b/sqlx-macros/src/query/mod.rs @@ -309,14 +309,15 @@ where // If the build is offline, the cache is our input so it's pointless to also write data for it. #[cfg(feature = "offline")] if !offline { - let mut save_dir = std::path::PathBuf::from( - env::var("CARGO_TARGET_DIR").unwrap_or_else(|_| "target/".into()), - ); + use std::path::Path; - save_dir.push("sqlx"); + let save_dir = env::var_os("CARGO_MANIFEST_DIR") + .map(Path::new) + .unwrap_or_else(|| Path::new(".")) + .join(".sqlx"); std::fs::create_dir_all(&save_dir)?; - data.save_in(save_dir, input.src_span)?; + data.save_in(save_dir)?; } Ok(ret_tokens)