1use std::fs;
25use std::path::{Path, PathBuf};
26use std::process::Command;
27use std::time::SystemTime;
28
29use fse_schema::Error;
30
31use crate::config::{self, OrmConfig};
32
33pub fn run(root: &Path, cfg: &OrmConfig, database_url: Option<&str>) -> Result<(), Error> {
34 let url = config::resolve_database_url(root, cfg, database_url)?;
35
36 let cache_dir = root.join(".sqlx");
37 fs::create_dir_all(&cache_dir)
38 .map_err(|e| Error::new(format!("cannot create {}: {e}", cache_dir.display())))?;
39
40 for file in query_files(&cache_dir)? {
43 fs::remove_file(&file)
44 .map_err(|e| Error::new(format!("cannot remove {}: {e}", file.display())))?;
45 }
46
47 touch_rs_files(&root.join("src"))?;
48 touch_rs_files(&root.join("tests"))?;
49
50 let cache_dir_abs = cache_dir
51 .canonicalize()
52 .map_err(|e| Error::new(format!("cannot resolve {}: {e}", cache_dir.display())))?;
53
54 println!("refreshing query cache ...");
55 let status = Command::new("cargo")
56 .arg("check")
57 .arg("--tests")
58 .current_dir(root)
59 .env("DATABASE_URL", &url)
60 .env("SQLX_OFFLINE", "false")
61 .env("SQLX_OFFLINE_DIR", &cache_dir_abs)
62 .status()
63 .map_err(|e| Error::new(format!("failed to run `cargo check`: {e}")))?;
64
65 if !status.success() {
66 return Err(Error::new(
67 "`cargo check` failed while refreshing the query cache — fix the build error and rerun",
68 ));
69 }
70
71 let count = query_files(&cache_dir)?.len();
72 if count == 0 {
73 println!(
74 "warning: no queries found — nothing written to .sqlx (no find!/insert!/update!/query! call sites?)"
75 );
76 } else {
77 let plural = if count == 1 { "query" } else { "queries" };
78 println!(
79 "wrote {count} {plural} to .sqlx — commit this directory so Docker builds work without a live database."
80 );
81 }
82 Ok(())
83}
84
85fn query_files(dir: &Path) -> Result<Vec<PathBuf>, Error> {
86 if !dir.exists() {
87 return Ok(Vec::new());
88 }
89 let mut out = Vec::new();
90 for entry in fs::read_dir(dir).map_err(|e| Error::new(format!("{}: {e}", dir.display())))? {
91 let path = entry.map_err(|e| Error::new(e.to_string()))?.path();
92 let is_query_file = path
93 .file_name()
94 .and_then(|n| n.to_str())
95 .is_some_and(|n| n.starts_with("query-") && n.ends_with(".json"));
96 if is_query_file {
97 out.push(path);
98 }
99 }
100 Ok(out)
101}
102
103fn touch_rs_files(dir: &Path) -> Result<(), Error> {
109 if !dir.exists() {
110 return Ok(());
111 }
112 let now = SystemTime::now();
113 let mut stack = vec![dir.to_path_buf()];
114 while let Some(current) = stack.pop() {
115 for entry in
116 fs::read_dir(¤t).map_err(|e| Error::new(format!("{}: {e}", current.display())))?
117 {
118 let path = entry.map_err(|e| Error::new(e.to_string()))?.path();
119 if path.is_dir() {
120 stack.push(path);
121 } else if path.extension().is_some_and(|e| e == "rs") {
122 let file = fs::OpenOptions::new()
123 .write(true)
124 .open(&path)
125 .map_err(|e| Error::new(format!("cannot open {}: {e}", path.display())))?;
126 file.set_modified(now)
127 .map_err(|e| Error::new(format!("cannot touch {}: {e}", path.display())))?;
128 }
129 }
130 }
131 Ok(())
132}