1use crate::model::{CacheData, CacheEntry};
2use anyhow::{Context, Result};
3use postgres::{Client, NoTls};
4use std::collections::HashMap;
5use std::fs;
6use std::path::Path;
7use std::time::{SystemTime, UNIX_EPOCH};
8
9pub fn sync_cache(db_url: &str, out_path: &Path) -> Result<()> {
10 let mut client = Client::connect(db_url, NoTls).context("Failed to connect to PostgreSQL")?;
11
12 let mut tables = HashMap::new();
13
14 let table_query = "
16 SELECT
17 n.nspname || '.' || c.relname AS canonical_key,
18 CASE WHEN c.reltuples < 0 THEN -1 ELSE c.reltuples::bigint END AS estimated_rows,
19 GREATEST(c.relpages::bigint, 0) AS relpages
20 FROM pg_class c
21 JOIN pg_namespace n ON n.oid = c.relnamespace
22 WHERE c.relkind IN ('r', 'p') AND n.nspname NOT IN ('pg_catalog', 'information_schema');
23 ";
24
25 for row in client.query(table_query, &[])? {
26 let key: String = row.get("canonical_key");
27 let raw_rows: i64 = row.get("estimated_rows");
28 let relpages: i64 = row.get("relpages");
29
30 let estimated_rows = if raw_rows < 0 {
32 u64::MAX
33 } else {
34 raw_rows as u64
35 };
36
37 tables.insert(
38 key,
39 CacheEntry {
40 estimated_rows,
41 relpages: Some(relpages as u64),
42 },
43 );
44 }
45
46 let mut indexes = HashMap::new();
47 let index_query = "
48 SELECT
49 n.nspname || '.' || i.relname AS index_key,
50 n.nspname || '.' || t.relname AS table_key
51 FROM pg_index x
52 JOIN pg_class i ON i.oid = x.indexrelid
53 JOIN pg_class t ON t.oid = x.indrelid
54 JOIN pg_namespace n ON n.oid = t.relnamespace;
55 ";
56
57 for row in client.query(index_query, &[])? {
58 let index_key: String = row.get("index_key");
59 let table_key: String = row.get("table_key");
60 indexes.insert(index_key, table_key);
61 }
62
63 let cache = CacheData {
64 last_updated: SystemTime::now()
65 .duration_since(UNIX_EPOCH)
66 .unwrap()
67 .as_secs(),
68 tables,
69 indexes,
70 };
71
72 let json = serde_json::to_string_pretty(&cache)?;
73 fs::write(out_path, json).context("Failed to write cache file")?;
74
75 Ok(())
76}