1use 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, Default)]
55pub struct CacheMetadata {
56 pub created_at_unix_secs: Option<u64>,
59 pub source_database: Option<String>,
62 pub schemas: Option<Vec<String>>,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct DbCacheV1 {
69 pub pg_version_num: Option<u32>,
70 pub relations: HashMap<ObjectId, RelationState>,
71 #[serde(default)]
72 pub foreign_keys: Vec<ForeignKeyCache>,
73 #[serde(default)]
74 pub indexes: Vec<IndexCache>,
75 #[serde(default)]
76 pub triggers: Vec<LegacyTriggerCache>,
77 #[serde(default)]
78 pub functions: HashMap<ObjectId, FunctionState>,
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct DbCacheV2 {
83 pub pg_version_num: Option<u32>,
84 pub relations: HashMap<ObjectId, RelationState>,
85 pub foreign_keys: Vec<ForeignKeyCache>,
86 pub indexes: Vec<IndexCache>,
87 pub triggers: Vec<LegacyTriggerCache>,
88 pub functions: HashMap<ObjectId, FunctionState>,
89 pub dependencies: Vec<DependencyCache>,
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct DbCache {
94 pub pg_version_num: Option<u32>,
95 pub metadata: CacheMetadata,
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 constraints: Vec<ConstraintState>,
101 pub triggers: Vec<TriggerCache>,
102 pub functions: HashMap<ObjectId, FunctionState>,
103 pub types: HashMap<ObjectId, TypeState>,
104 pub dependencies: Vec<DependencyCache>,
105}
106
107pub const CACHE_FORMAT_VERSION: u32 = 3;
108
109pub const CACHE_V3_MAGIC: &[u8] = b"SMCACHE03";
113
114#[derive(Debug, Clone, Serialize, Deserialize)]
115pub enum DbCacheVersioned {
116 V1(DbCacheV1),
117 V2(DbCacheV2),
118 V3(DbCache),
119}
120
121impl DbCacheVersioned {
122 pub fn format_version(&self) -> u32 {
123 match self {
124 DbCacheVersioned::V1(_) => 1,
125 DbCacheVersioned::V2(_) => 2,
126 DbCacheVersioned::V3(_) => 3,
127 }
128 }
129
130 pub fn into_cache(self) -> Result<DbCache, String> {
131 match self {
132 DbCacheVersioned::V1(c) => Ok(DbCache {
133 pg_version_num: c.pg_version_num,
134 metadata: CacheMetadata::default(),
135 relations: c.relations,
136 foreign_keys: c.foreign_keys,
137 indexes: c.indexes,
138 constraints: Vec::new(),
139 triggers: upgrade_legacy_triggers(c.triggers),
140 functions: c.functions,
141 types: HashMap::new(),
142 dependencies: Vec::new(),
143 search_path: vec!["public".to_string()],
144 }),
145 DbCacheVersioned::V2(c) => Ok(DbCache {
146 pg_version_num: c.pg_version_num,
147 metadata: CacheMetadata::default(),
148 search_path: vec!["public".to_string()],
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: c.dependencies,
157 }),
158 DbCacheVersioned::V3(c) => Ok(c),
159 }
160 }
161}
162
163pub fn legacy_cache_format_version(payload: &[u8]) -> Option<u32> {
169 payload.first().map(|tag| u32::from(*tag) + 1)
170}
171
172fn upgrade_legacy_triggers(triggers: Vec<LegacyTriggerCache>) -> Vec<TriggerCache> {
173 triggers
174 .into_iter()
175 .map(|trigger| TriggerCache {
176 trigger_id: trigger.trigger_id,
177 table_id: trigger.table_id,
178 function_id: trigger.function_id,
179 enabled_mode: TriggerEnableMode::Origin,
180 })
181 .collect()
182}
183
184impl Default for DbCache {
185 fn default() -> Self {
186 Self::new()
187 }
188}
189
190impl DbCache {
191 pub fn new() -> Self {
192 Self {
193 pg_version_num: None,
194 metadata: CacheMetadata::default(),
195 search_path: vec!["public".to_string()],
196 relations: HashMap::new(),
197 foreign_keys: Vec::new(),
198 indexes: Vec::new(),
199 constraints: Vec::new(),
200 triggers: Vec::new(),
201 functions: HashMap::new(),
202 types: HashMap::new(),
203 dependencies: Vec::new(),
204 }
205 }
206
207 pub fn insert_baseline(&mut self, id: ObjectId, state: RelationState) {
208 self.relations.insert(id, state);
209 }
210
211 pub fn baseline_relations(&self) -> impl Iterator<Item = (&ObjectId, &RelationState)> {
212 self.relations.iter()
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 #[test]
221 fn legacy_v1_cache_upgrades_to_current_model() {
222 let cache = DbCacheV1 {
223 pg_version_num: None,
224 relations: HashMap::new(),
225 foreign_keys: Vec::new(),
226 indexes: Vec::new(),
227 triggers: Vec::new(),
228 functions: HashMap::new(),
229 };
230 let versioned = DbCacheVersioned::V1(cache);
231 assert_eq!(versioned.format_version(), 1);
232 let result = versioned.into_cache();
233 assert!(
234 result.is_ok(),
235 "into_cache() should succeed for V1: {:?}",
236 result
237 );
238 }
239
240 #[test]
241 fn current_cache_format_is_v3() {
242 assert_eq!(CACHE_FORMAT_VERSION, 3);
243 assert_eq!(DbCacheVersioned::V3(DbCache::new()).format_version(), 3);
244 assert_eq!(CACHE_V3_MAGIC, b"SMCACHE03");
245 }
246}