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