|
| 1 | +//! Generate regresql `sql/` stubs for the top-N clusters |
| 2 | +
|
| 3 | +use std::fs; |
| 4 | +use std::path::Path; |
| 5 | +use std::sync::OnceLock; |
| 6 | + |
| 7 | +use anyhow::{Context, Result}; |
| 8 | +use qshape_core::Cluster; |
| 9 | +use regex::Regex; |
| 10 | + |
| 11 | +use crate::loader::load_clusters; |
| 12 | + |
| 13 | +pub fn run(in_path: Option<&str>, out_dir: &str, top: usize, min_calls: i64) -> Result<()> { |
| 14 | + let doc = load_clusters(in_path)?; |
| 15 | + |
| 16 | + let sql_dir = Path::new(out_dir).join("sql"); |
| 17 | + fs::create_dir_all(&sql_dir).with_context(|| format!("create {}", sql_dir.display()))?; |
| 18 | + |
| 19 | + let mut emitted = 0usize; |
| 20 | + for c in &doc.clusters { |
| 21 | + if emitted >= top { |
| 22 | + break; |
| 23 | + } |
| 24 | + if c.fingerprint.is_empty() || c.total_calls <= min_calls { |
| 25 | + continue; |
| 26 | + } |
| 27 | + emitted += 1; |
| 28 | + let slug = stub_slug(emitted, &c.fingerprint); |
| 29 | + let sql = rewrite_params(&c.canonical); |
| 30 | + let path = sql_dir.join(format!("{slug}.sql")); |
| 31 | + write_sql_stub(&path, &slug, c, &sql)?; |
| 32 | + } |
| 33 | + |
| 34 | + eprintln!("wrote {emitted} stubs to {out_dir}"); |
| 35 | + Ok(()) |
| 36 | +} |
| 37 | + |
| 38 | +fn stub_slug(rank: usize, fp: &str) -> String { |
| 39 | + let body = fp.strip_prefix("sha1:").unwrap_or(fp); |
| 40 | + let prefix: String = body.chars().take(8).collect(); |
| 41 | + format!("q{rank:02}-{prefix}") |
| 42 | +} |
| 43 | + |
| 44 | +// $N to:paramN |
| 45 | +fn rewrite_params(sql: &str) -> String { |
| 46 | + static R: OnceLock<Regex> = OnceLock::new(); |
| 47 | + let re = R.get_or_init(|| Regex::new(r"\$(\d+)").expect("static regex")); |
| 48 | + re.replace_all(sql, ":param$1").into_owned() |
| 49 | +} |
| 50 | + |
| 51 | +fn write_sql_stub(path: &Path, slug: &str, c: &Cluster, sql: &str) -> Result<()> { |
| 52 | + let trailing = if sql.ends_with('\n') { "" } else { "\n" }; |
| 53 | + let content = format!( |
| 54 | + "-- name: {slug}\n\ |
| 55 | + -- Generated from qshape cluster {fp}\n\ |
| 56 | + -- Total calls (prod): {tc} across {n} member variants\n\ |
| 57 | + -- TODO: rename this slug, review canonical SQL, replace :paramN with meaningful names\n\ |
| 58 | + {sql}{trailing}", |
| 59 | + fp = c.fingerprint, |
| 60 | + tc = c.total_calls, |
| 61 | + n = c.members.len(), |
| 62 | + ); |
| 63 | + fs::write(path, content).with_context(|| format!("write {}", path.display())) |
| 64 | +} |
0 commit comments