Skip to main content

safe_migrate/db/
cache.rs

1// FILE: src/db/cache.rs
2use crate::ast::identifiers::ObjectId;
3use crate::model::constraint::ConstraintState;
4use crate::model::function::FunctionState;
5use crate::model::relation::RelationState;
6use crate::model::trigger::TriggerEnableMode;
7use crate::model::types::TypeState;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12pub struct ForeignKeyCache {
13    pub constraint_name: String,
14    pub from_table: ObjectId,
15    pub to_table: ObjectId,
16}
17
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
19pub struct IndexCache {
20    pub index_id: ObjectId,
21    pub table_id: ObjectId,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct TriggerCache {
26    pub trigger_id: ObjectId,
27    pub table_id: ObjectId,
28    pub function_id: ObjectId,
29    pub enabled_mode: TriggerEnableMode,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct LegacyTriggerCache {
34    pub trigger_id: ObjectId,
35    pub table_id: ObjectId,
36    pub function_id: ObjectId,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct DependencyCache {
41    pub classid: u32,
42    pub objid: u32,
43    pub objsubid: i32,
44    pub refclassid: u32,
45    pub refobjid: u32,
46    pub refobjsubid: i32,
47    pub deptype: String,
48    pub obj_schema: Option<String>,
49    pub obj_name: Option<String>,
50    pub ref_schema: Option<String>,
51    pub ref_name: Option<String>,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct DbCacheV1 {
56    pub pg_version_num: Option<u32>,
57    pub relations: HashMap<ObjectId, RelationState>,
58    #[serde(default)]
59    pub foreign_keys: Vec<ForeignKeyCache>,
60    #[serde(default)]
61    pub indexes: Vec<IndexCache>,
62    #[serde(default)]
63    pub triggers: Vec<LegacyTriggerCache>,
64    #[serde(default)]
65    pub functions: HashMap<ObjectId, FunctionState>,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct DbCacheV2 {
70    pub pg_version_num: Option<u32>,
71    pub relations: HashMap<ObjectId, RelationState>,
72    pub foreign_keys: Vec<ForeignKeyCache>,
73    pub indexes: Vec<IndexCache>,
74    pub triggers: Vec<LegacyTriggerCache>,
75    pub functions: HashMap<ObjectId, FunctionState>,
76    pub dependencies: Vec<DependencyCache>,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct DbCache {
81    pub pg_version_num: Option<u32>,
82    pub search_path: Vec<String>,
83    pub relations: HashMap<ObjectId, RelationState>,
84    pub foreign_keys: Vec<ForeignKeyCache>,
85    pub indexes: Vec<IndexCache>,
86    pub constraints: Vec<ConstraintState>,
87    pub triggers: Vec<TriggerCache>,
88    pub functions: HashMap<ObjectId, FunctionState>,
89    pub types: HashMap<ObjectId, TypeState>,
90    pub dependencies: Vec<DependencyCache>,
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct DbCacheV3 {
95    pub pg_version_num: Option<u32>,
96    pub search_path: Vec<String>,
97    pub relations: HashMap<ObjectId, RelationState>,
98    pub foreign_keys: Vec<ForeignKeyCache>,
99    pub indexes: Vec<IndexCache>,
100    pub triggers: Vec<LegacyTriggerCache>,
101    pub functions: HashMap<ObjectId, FunctionState>,
102    pub dependencies: Vec<DependencyCache>,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct DbCacheV4 {
107    pub pg_version_num: Option<u32>,
108    pub search_path: Vec<String>,
109    pub relations: HashMap<ObjectId, RelationState>,
110    pub foreign_keys: Vec<ForeignKeyCache>,
111    pub indexes: Vec<IndexCache>,
112    pub constraints: Vec<ConstraintState>,
113    pub triggers: Vec<TriggerCache>,
114    pub functions: HashMap<ObjectId, FunctionState>,
115    pub dependencies: Vec<DependencyCache>,
116}
117
118pub const CACHE_FORMAT_VERSION: u32 = 5;
119
120const _: () = assert!(
121    CACHE_FORMAT_VERSION == 5,
122    "CACHE_FORMAT_VERSION must be updated when new DbCacheVersioned variants are added",
123);
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub enum DbCacheVersioned {
127    V1(DbCacheV1),
128    V2(DbCacheV2),
129    V3(DbCacheV3),
130    V4(DbCacheV4),
131    V5(DbCache),
132}
133
134impl DbCacheVersioned {
135    pub fn format_version(&self) -> u32 {
136        match self {
137            DbCacheVersioned::V1(_) => 1,
138            DbCacheVersioned::V2(_) => 2,
139            DbCacheVersioned::V3(_) => 3,
140            DbCacheVersioned::V4(_) => 4,
141            DbCacheVersioned::V5(_) => 5,
142        }
143    }
144
145    pub fn into_cache(self) -> Result<DbCache, String> {
146        match self {
147            DbCacheVersioned::V1(c) => Ok(DbCache {
148                pg_version_num: c.pg_version_num,
149                relations: c.relations,
150                foreign_keys: c.foreign_keys,
151                indexes: c.indexes,
152                constraints: Vec::new(),
153                triggers: upgrade_legacy_triggers(c.triggers),
154                functions: c.functions,
155                types: HashMap::new(),
156                dependencies: Vec::new(),
157                search_path: vec!["public".to_string()],
158            }),
159            DbCacheVersioned::V2(c) => Ok(DbCache {
160                pg_version_num: c.pg_version_num,
161                search_path: vec!["public".to_string()],
162                relations: c.relations,
163                foreign_keys: c.foreign_keys,
164                indexes: c.indexes,
165                constraints: Vec::new(),
166                triggers: upgrade_legacy_triggers(c.triggers),
167                functions: c.functions,
168                types: HashMap::new(),
169                dependencies: c.dependencies,
170            }),
171            DbCacheVersioned::V3(c) => Ok(DbCache {
172                pg_version_num: c.pg_version_num,
173                search_path: c.search_path,
174                relations: c.relations,
175                foreign_keys: c.foreign_keys,
176                indexes: c.indexes,
177                constraints: Vec::new(),
178                triggers: upgrade_legacy_triggers(c.triggers),
179                functions: c.functions,
180                types: HashMap::new(),
181                dependencies: c.dependencies,
182            }),
183            DbCacheVersioned::V4(c) => Ok(DbCache {
184                pg_version_num: c.pg_version_num,
185                search_path: c.search_path,
186                relations: c.relations,
187                foreign_keys: c.foreign_keys,
188                indexes: c.indexes,
189                constraints: c.constraints,
190                triggers: c.triggers,
191                functions: c.functions,
192                types: HashMap::new(),
193                dependencies: c.dependencies,
194            }),
195            DbCacheVersioned::V5(c) => Ok(c),
196        }
197    }
198}
199
200fn upgrade_legacy_triggers(triggers: Vec<LegacyTriggerCache>) -> Vec<TriggerCache> {
201    triggers
202        .into_iter()
203        .map(|trigger| TriggerCache {
204            trigger_id: trigger.trigger_id,
205            table_id: trigger.table_id,
206            function_id: trigger.function_id,
207            enabled_mode: TriggerEnableMode::Origin,
208        })
209        .collect()
210}
211
212impl Default for DbCache {
213    fn default() -> Self {
214        Self::new()
215    }
216}
217
218impl DbCache {
219    pub fn new() -> Self {
220        Self {
221            pg_version_num: None,
222            search_path: vec!["public".to_string()],
223            relations: HashMap::new(),
224            foreign_keys: Vec::new(),
225            indexes: Vec::new(),
226            constraints: Vec::new(),
227            triggers: Vec::new(),
228            functions: HashMap::new(),
229            types: HashMap::new(),
230            dependencies: Vec::new(),
231        }
232    }
233
234    pub fn insert_baseline(&mut self, id: ObjectId, state: RelationState) {
235        self.relations.insert(id, state);
236    }
237
238    pub fn baseline_relations(&self) -> impl Iterator<Item = (&ObjectId, &RelationState)> {
239        self.relations.iter()
240    }
241}
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246
247    #[test]
248    fn test_bug019_into_cache_succeeds_for_v1() {
249        // 1d: into_cache() must succeed for the current format version (V1).
250        let cache = DbCacheV1 {
251            pg_version_num: None,
252            relations: HashMap::new(),
253            foreign_keys: Vec::new(),
254            indexes: Vec::new(),
255            triggers: Vec::new(),
256            functions: HashMap::new(),
257        };
258        let versioned = DbCacheVersioned::V1(cache);
259        assert_eq!(versioned.format_version(), 1);
260        let result = versioned.into_cache();
261        assert!(
262            result.is_ok(),
263            "into_cache() should succeed for V1: {:?}",
264            result
265        );
266    }
267
268    #[test]
269    fn test_bug019_format_version_constant_matches_v1_variant() {
270        // 1d: CACHE_FORMAT_VERSION must equal the discriminant reported by V1.
271        let versioned = DbCacheVersioned::V1(DbCacheV1 {
272            pg_version_num: None,
273            relations: HashMap::new(),
274            foreign_keys: Vec::new(),
275            indexes: Vec::new(),
276            triggers: Vec::new(),
277            functions: HashMap::new(),
278        });
279        assert_eq!(
280            versioned.format_version(),
281            1,
282            "format_version() for V1 must match 1"
283        );
284    }
285}