Skip to main content

safe_migrate/
sync.rs

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    // FIX: Explcitly trap -1 (unanalyzed tables) to safely force a fallback to u64::MAX
15    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        // If table is unanalyzed (-1), assume worst case scenario
31        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}