1use crate::ast::identifiers::ObjectId;
3use crate::model::constraint::ConstraintState;
4use crate::model::function::FunctionState;
5use crate::model::relation::RelationState;
6use crate::model::role::RoleState;
7use crate::model::trigger::TriggerEnableMode;
8use crate::model::types::TypeState;
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11
12#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13pub struct ForeignKeyCache {
14 pub constraint_name: String,
15 pub from_table: ObjectId,
16 pub to_table: ObjectId,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20pub struct IndexCache {
21 pub index_id: ObjectId,
22 pub table_id: ObjectId,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct TriggerCache {
27 pub trigger_id: ObjectId,
28 pub table_id: ObjectId,
29 pub function_id: ObjectId,
30 pub enabled_mode: TriggerEnableMode,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct LegacyTriggerCache {
35 pub trigger_id: ObjectId,
36 pub table_id: ObjectId,
37 pub function_id: ObjectId,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct DependencyCache {
42 pub classid: u32,
43 pub objid: u32,
44 pub objsubid: i32,
45 pub refclassid: u32,
46 pub refobjid: u32,
47 pub refobjsubid: i32,
48 pub deptype: String,
49 pub obj_schema: Option<String>,
50 pub obj_name: Option<String>,
51 pub ref_schema: Option<String>,
52 pub ref_name: Option<String>,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize, Default)]
56pub struct CacheMetadata {
57 pub created_at_unix_secs: Option<u64>,
60 pub source_database: Option<String>,
63 pub source_role: Option<String>,
66 pub source_session_role: Option<String>,
69 pub source_search_path: Option<Vec<String>>,
71 pub schemas: Option<Vec<String>>,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize, Default)]
79pub struct CacheMetadataV3 {
80 pub created_at_unix_secs: Option<u64>,
81 pub source_database: Option<String>,
82 pub schemas: Option<Vec<String>>,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct DbCacheV1 {
87 pub pg_version_num: Option<u32>,
88 pub relations: HashMap<ObjectId, RelationState>,
89 #[serde(default)]
90 pub foreign_keys: Vec<ForeignKeyCache>,
91 #[serde(default)]
92 pub indexes: Vec<IndexCache>,
93 #[serde(default)]
94 pub triggers: Vec<LegacyTriggerCache>,
95 #[serde(default)]
96 pub functions: HashMap<ObjectId, FunctionState>,
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct DbCacheV2 {
101 pub pg_version_num: Option<u32>,
102 pub relations: HashMap<ObjectId, RelationState>,
103 pub foreign_keys: Vec<ForeignKeyCache>,
104 pub indexes: Vec<IndexCache>,
105 pub triggers: Vec<LegacyTriggerCache>,
106 pub functions: HashMap<ObjectId, FunctionState>,
107 pub dependencies: Vec<DependencyCache>,
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct DbCacheV3 {
113 pub pg_version_num: Option<u32>,
114 pub metadata: CacheMetadataV3,
115 pub search_path: Vec<String>,
116 pub relations: HashMap<ObjectId, RelationState>,
117 pub foreign_keys: Vec<ForeignKeyCache>,
118 pub indexes: Vec<IndexCache>,
119 pub constraints: Vec<ConstraintState>,
120 pub triggers: Vec<TriggerCache>,
121 pub functions: HashMap<ObjectId, FunctionState>,
122 pub types: HashMap<ObjectId, TypeState>,
123 pub dependencies: Vec<DependencyCache>,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct DbCache {
128 pub pg_version_num: Option<u32>,
129 pub metadata: CacheMetadata,
130 pub search_path: Vec<String>,
131 pub relations: HashMap<ObjectId, RelationState>,
132 pub foreign_keys: Vec<ForeignKeyCache>,
133 pub indexes: Vec<IndexCache>,
134 pub constraints: Vec<ConstraintState>,
135 pub triggers: Vec<TriggerCache>,
136 pub functions: HashMap<ObjectId, FunctionState>,
137 pub types: HashMap<ObjectId, TypeState>,
138 pub roles: HashMap<ObjectId, RoleState>,
139 pub dependencies: Vec<DependencyCache>,
140}
141
142pub const CACHE_FORMAT_VERSION: u32 = 4;
143
144pub const CACHE_V3_MAGIC: &[u8] = b"SMCACHE03";
148pub const CACHE_V4_MAGIC: &[u8] = b"SMCACHE04";
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
151pub enum DbCacheVersioned {
152 V1(DbCacheV1),
153 V2(DbCacheV2),
154 V3(DbCacheV3),
155 V4(DbCache),
156}
157
158impl DbCacheVersioned {
159 pub fn format_version(&self) -> u32 {
160 match self {
161 DbCacheVersioned::V1(_) => 1,
162 DbCacheVersioned::V2(_) => 2,
163 DbCacheVersioned::V3(_) => 3,
164 DbCacheVersioned::V4(_) => 4,
165 }
166 }
167
168 pub fn into_cache(self) -> Result<DbCache, String> {
169 match self {
170 DbCacheVersioned::V1(_) | DbCacheVersioned::V2(_) => Err(
171 "This cache format is unsupported. Run `safe-migrate sync` to rebuild it."
172 .to_string(),
173 ),
174 DbCacheVersioned::V3(c) => Ok(c.into()),
175 DbCacheVersioned::V4(c) => Ok(c),
176 }
177 }
178}
179
180impl From<DbCacheV3> for DbCache {
181 fn from(cache: DbCacheV3) -> Self {
182 Self {
183 pg_version_num: cache.pg_version_num,
184 metadata: CacheMetadata {
185 created_at_unix_secs: cache.metadata.created_at_unix_secs,
186 source_database: cache.metadata.source_database,
187 source_role: None,
188 source_session_role: None,
189 source_search_path: None,
190 schemas: cache.metadata.schemas,
191 },
192 search_path: cache.search_path,
193 relations: cache.relations,
194 foreign_keys: cache.foreign_keys,
195 indexes: cache.indexes,
196 constraints: cache.constraints,
197 triggers: cache.triggers,
198 functions: cache.functions,
199 types: cache.types,
200 roles: HashMap::new(),
201 dependencies: cache.dependencies,
202 }
203 }
204}
205
206impl From<DbCache> for DbCacheV3 {
207 fn from(cache: DbCache) -> Self {
208 Self {
209 pg_version_num: cache.pg_version_num,
210 metadata: CacheMetadataV3 {
211 created_at_unix_secs: cache.metadata.created_at_unix_secs,
212 source_database: cache.metadata.source_database,
213 schemas: cache.metadata.schemas,
214 },
215 search_path: cache.search_path,
216 relations: cache.relations,
217 foreign_keys: cache.foreign_keys,
218 indexes: cache.indexes,
219 constraints: cache.constraints,
220 triggers: cache.triggers,
221 functions: cache.functions,
222 types: cache.types,
223 dependencies: cache.dependencies,
224 }
225 }
226}
227
228impl Default for DbCache {
229 fn default() -> Self {
230 Self::new()
231 }
232}
233
234impl DbCache {
235 pub fn new() -> Self {
236 Self {
237 pg_version_num: None,
238 metadata: CacheMetadata::default(),
239 search_path: vec!["public".to_string()],
240 relations: HashMap::new(),
241 foreign_keys: Vec::new(),
242 indexes: Vec::new(),
243 constraints: Vec::new(),
244 triggers: Vec::new(),
245 functions: HashMap::new(),
246 types: HashMap::new(),
247 roles: HashMap::new(),
248 dependencies: Vec::new(),
249 }
250 }
251
252 pub fn insert_baseline(&mut self, id: ObjectId, state: RelationState) {
253 self.relations.insert(id, state);
254 }
255
256 pub fn baseline_relations(&self) -> impl Iterator<Item = (&ObjectId, &RelationState)> {
257 self.relations.iter()
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264
265 #[test]
266 fn legacy_v1_cache_is_rejected() {
267 let cache = DbCacheV1 {
268 pg_version_num: None,
269 relations: HashMap::new(),
270 foreign_keys: Vec::new(),
271 indexes: Vec::new(),
272 triggers: Vec::new(),
273 functions: HashMap::new(),
274 };
275 let versioned = DbCacheVersioned::V1(cache);
276 assert_eq!(versioned.format_version(), 1);
277 let result = versioned.into_cache();
278 assert_eq!(
279 result.unwrap_err(),
280 "This cache format is unsupported. Run `safe-migrate sync` to rebuild it."
281 );
282 }
283
284 #[test]
285 fn current_cache_format_is_v4() {
286 assert_eq!(CACHE_FORMAT_VERSION, 4);
287 assert_eq!(DbCacheVersioned::V4(DbCache::new()).format_version(), 4);
288 assert_eq!(CACHE_V3_MAGIC, b"SMCACHE03");
289 assert_eq!(CACHE_V4_MAGIC, b"SMCACHE04");
290 }
291
292 #[test]
293 fn v3_cache_remains_readable_without_inventing_a_source_role() {
294 let v3 = DbCacheV3::from(DbCache::new());
295 let cache = DbCacheVersioned::V3(v3).into_cache().unwrap();
296 assert_eq!(cache.metadata.source_role, None);
297 }
298}