1use super::*;
2use crate::api::REDDB_FORMAT_VERSION;
3
4impl PhysicalMetadataFile {
5 pub fn from_state(
6 options: RedDBOptions,
7 catalog: CatalogSnapshot,
8 collection_roots: BTreeMap<String, u64>,
9 indexes: Vec<PhysicalIndexState>,
10 previous: Option<&PhysicalMetadataFile>,
11 ) -> Self {
12 let now = unix_ms_now();
13 let mut manifest = SchemaManifest::now(options, catalog.total_collections);
14 if let Some(previous) = previous {
15 manifest.created_at_unix_ms = previous.manifest.created_at_unix_ms;
16 }
17 manifest.updated_at_unix_ms = now;
18
19 let sequence = previous
20 .map(|previous| previous.superblock.sequence.saturating_add(1))
21 .unwrap_or(1);
22 let mut manifest_events = previous
23 .map(|previous| previous.manifest_events.clone())
24 .unwrap_or_default();
25 manifest_events.extend(build_manifest_events(
26 previous.map(|previous| &previous.superblock.collection_roots),
27 &collection_roots,
28 sequence,
29 ));
30 trim_manifest_history(&mut manifest_events);
31
32 let superblock = SuperblockHeader {
33 format_version: REDDB_FORMAT_VERSION,
34 sequence,
35 copies: DEFAULT_SUPERBLOCK_COPIES,
36 manifest: previous
37 .map(|previous| previous.superblock.manifest.clone())
38 .unwrap_or_default(),
39 free_set: previous
40 .map(|previous| previous.superblock.free_set)
41 .unwrap_or_default(),
42 collection_roots,
43 };
44
45 let mut snapshots = previous
46 .map(|previous| previous.snapshots.clone())
47 .unwrap_or_default();
48 snapshots.push(SnapshotDescriptor {
49 snapshot_id: sequence,
50 created_at_unix_ms: now,
51 superblock_sequence: sequence,
52 collection_count: catalog.total_collections,
53 total_entities: catalog.total_entities,
54 });
55 trim_snapshot_history(&mut snapshots, manifest.options.snapshot_retention);
56
57 Self {
58 protocol_version: PHYSICAL_METADATA_PROTOCOL_VERSION.to_string(),
59 generated_at_unix_ms: now,
60 last_loaded_from: previous.and_then(|previous| previous.last_loaded_from.clone()),
61 last_healed_at_unix_ms: previous.and_then(|previous| previous.last_healed_at_unix_ms),
62 manifest,
63 catalog,
64 manifest_events,
65 indexes,
66 graph_projections: previous
67 .map(|previous| previous.graph_projections.clone())
68 .unwrap_or_default(),
69 analytics_jobs: previous
70 .map(|previous| previous.analytics_jobs.clone())
71 .unwrap_or_default(),
72 tree_definitions: previous
73 .map(|previous| previous.tree_definitions.clone())
74 .unwrap_or_default(),
75 collection_ttl_defaults_ms: previous
76 .map(|previous| previous.collection_ttl_defaults_ms.clone())
77 .unwrap_or_default(),
78 collection_contracts: previous
79 .map(|previous| previous.collection_contracts.clone())
80 .unwrap_or_default(),
81 hypertables: previous
82 .map(|previous| previous.hypertables.clone())
83 .unwrap_or_default(),
84 exports: previous
85 .map(|previous| previous.exports.clone())
86 .unwrap_or_default(),
87 superblock,
88 snapshots,
89 }
90 }
91
92 pub fn metadata_path_for(data_path: &Path) -> PathBuf {
93 reddb_file::layout::physical_metadata_json_path(data_path)
94 }
95
96 pub fn metadata_binary_path_for(data_path: &Path) -> PathBuf {
97 reddb_file::layout::physical_metadata_binary_path(data_path)
98 }
99
100 pub fn metadata_journal_path_for(data_path: &Path, sequence: u64) -> PathBuf {
101 reddb_file::layout::physical_metadata_journal_path(data_path, sequence)
102 }
103
104 pub fn export_data_path_for(data_path: &Path, name: &str) -> PathBuf {
105 reddb_file::layout::physical_export_data_path(data_path, name)
106 }
107
108 pub fn load_for_data_path(data_path: &Path) -> io::Result<Self> {
109 Self::load_for_data_path_with_source(data_path).map(|(metadata, _)| metadata)
110 }
111
112 pub fn load_for_data_path_with_source(
113 data_path: &Path,
114 ) -> io::Result<(Self, PhysicalMetadataSource)> {
115 Self::load_for_data_path_with_repair(data_path, true)
116 }
117
118 pub fn inspect_for_data_path(data_path: &Path) -> io::Result<Self> {
120 Self::inspect_for_data_path_with_source(data_path).map(|(metadata, _)| metadata)
121 }
122
123 pub fn inspect_for_data_path_with_source(
124 data_path: &Path,
125 ) -> io::Result<(Self, PhysicalMetadataSource)> {
126 Self::load_for_data_path_with_repair(data_path, false)
127 }
128
129 fn load_for_data_path_with_repair(
130 data_path: &Path,
131 repair_primary: bool,
132 ) -> io::Result<(Self, PhysicalMetadataSource)> {
133 let binary_path = Self::metadata_binary_path_for(data_path);
134 if binary_path.exists() {
135 match Self::load_from_binary_path(&binary_path) {
136 Ok(metadata) => {
137 return Ok((metadata, PhysicalMetadataSource::Binary));
138 }
139 Err(error) if is_collection_contract_decode_error(&error) => return Err(error),
140 Err(_) => {
141 let mut journal_paths = Self::journal_paths_for_data_path(data_path)?;
142 journal_paths.reverse();
143 for journal_path in journal_paths {
144 match Self::load_from_binary_path(&journal_path) {
145 Ok(metadata) => {
146 if !repair_primary {
147 return Ok((metadata, PhysicalMetadataSource::BinaryJournal));
148 }
149 let healed =
150 metadata.mark_recovery(PhysicalMetadataSource::BinaryJournal);
151 let _ = healed.heal_primary_metadata_for_data_path(data_path);
152 return Ok((healed, PhysicalMetadataSource::BinaryJournal));
153 }
154 Err(error) if is_collection_contract_decode_error(&error) => {
155 return Err(error)
156 }
157 Err(_) => {}
158 }
159 }
160 }
161 }
162 }
163 Self::load_from_path(&Self::metadata_path_for(data_path)).map(|metadata| {
164 if !repair_primary {
165 return (metadata, PhysicalMetadataSource::Json);
166 }
167 let healed = metadata.mark_recovery(PhysicalMetadataSource::Json);
168 let _ = healed.heal_primary_metadata_for_data_path(data_path);
169 (healed, PhysicalMetadataSource::Json)
170 })
171 }
172
173 pub fn save_for_data_path(&self, data_path: &Path) -> io::Result<PathBuf> {
174 let binary_path = Self::metadata_binary_path_for(data_path);
175 if binary_path.exists() && super::seqn_journal_enabled() {
176 let sequence = Self::load_from_binary_path(&binary_path)
177 .map(|metadata| metadata.superblock.sequence)
178 .unwrap_or(self.superblock.sequence);
179 let _ = reddb_file::copy_physical_metadata_binary_to_journal(
180 data_path,
181 &binary_path,
182 sequence,
183 );
184 }
185 self.save_to_binary_path(&binary_path)?;
186 self.prune_journal_for_data_path(data_path)?;
187 if super::meta_json_sidecar_enabled() {
188 let json_path = Self::metadata_path_for(data_path);
189 self.save_to_path(&json_path)?;
190 }
191 Ok(binary_path)
192 }
193
194 pub fn load_from_path(path: &Path) -> io::Result<Self> {
195 let text = reddb_file::read_physical_metadata_document(path).map_err(|err| {
196 io::Error::new(
197 io::ErrorKind::InvalidData,
198 format!("invalid physical metadata JSON: {err}"),
199 )
200 })?;
201 Self::from_document_json(&text, "invalid physical metadata JSON")
202 }
203
204 pub fn save_to_path(&self, path: &Path) -> io::Result<()> {
205 let text = self.to_document_json(true)?;
206 reddb_file::write_physical_metadata_json_document(path, &text)
207 .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string()))
208 }
209
210 pub fn load_from_binary_path(path: &Path) -> io::Result<Self> {
211 let text = reddb_file::read_physical_metadata_document(path).map_err(|err| {
212 io::Error::new(
213 io::ErrorKind::InvalidData,
214 format!("invalid physical metadata binary: {err}"),
215 )
216 })?;
217 Self::from_document_json(&text, "invalid physical metadata binary")
218 }
219
220 pub fn save_to_binary_path(&self, path: &Path) -> io::Result<()> {
221 let text = self.to_document_json(false)?;
222 reddb_file::write_physical_metadata_binary_document(path, &text)
223 .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string()))
224 }
225
226 pub fn journal_paths_for_data_path(data_path: &Path) -> io::Result<Vec<PathBuf>> {
227 reddb_file::list_physical_metadata_journal_paths(data_path)
228 .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string()))
229 }
230
231 pub fn prune_journal_for_data_path(&self, data_path: &Path) -> io::Result<()> {
232 let retention = super::seqn_journal_retention();
233 reddb_file::prune_physical_metadata_journal_paths(data_path, retention)
234 .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string()))
235 }
236
237 pub fn heal_primary_metadata_for_data_path(&self, data_path: &Path) -> io::Result<()> {
238 let binary_path = Self::metadata_binary_path_for(data_path);
239 self.save_to_binary_path(&binary_path)?;
240 if super::meta_json_sidecar_enabled() {
241 let json_path = Self::metadata_path_for(data_path);
242 self.save_to_path(&json_path)?;
243 }
244 Ok(())
245 }
246
247 pub fn mark_recovery(&self, source: PhysicalMetadataSource) -> Self {
248 let mut metadata = self.clone();
249 metadata.last_loaded_from = Some(source.as_str().to_string());
250 metadata.last_healed_at_unix_ms = Some(unix_ms_now());
251 metadata
252 }
253
254 pub fn to_json_value(&self) -> JsonValue {
255 let json = self
256 .to_document_json(false)
257 .expect("physical metadata must encode as JSON");
258 crate::json::from_str::<JsonValue>(&json)
259 .expect("reddb-file emitted JSON the server can parse")
260 }
261
262 pub fn from_json_value(value: &JsonValue) -> io::Result<Self> {
263 Self::from_document_json(&value.to_string_compact(), "invalid physical metadata JSON")
264 }
265
266 fn to_document_json(&self, pretty: bool) -> io::Result<String> {
267 reddb_file::encode_physical_metadata_document_root_json(
268 &self.to_document_envelope(),
269 pretty,
270 )
271 .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string()))
272 }
273
274 fn from_document_json(text: &str, context: &'static str) -> io::Result<Self> {
275 let envelope =
276 reddb_file::decode_physical_metadata_document_root_json(text).map_err(|err| {
277 io::Error::new(io::ErrorKind::InvalidData, format!("{context}: {err}"))
278 })?;
279 Self::from_document_envelope(envelope)
280 }
281
282 fn to_document_envelope(&self) -> reddb_file::PhysicalMetadataDocumentEnvelope {
283 reddb_file::PhysicalMetadataDocumentEnvelope {
284 protocol_version: self.protocol_version.clone(),
285 generated_at_unix_ms: self.generated_at_unix_ms,
286 last_loaded_from: self.last_loaded_from.clone(),
287 last_healed_at_unix_ms: self.last_healed_at_unix_ms,
288 manifest_json: manifest_to_json(&self.manifest).to_string_compact(),
289 catalog_json: catalog_to_json(&self.catalog).to_string_compact(),
290 manifest_events_json: self
291 .manifest_events
292 .iter()
293 .map(|event| manifest_event_to_json(event).to_string_compact())
294 .collect(),
295 indexes_json: self
296 .indexes
297 .iter()
298 .map(|index| index_state_to_json(index).to_string_compact())
299 .collect(),
300 graph_projections_json: self
301 .graph_projections
302 .iter()
303 .map(|projection| graph_projection_to_json(projection).to_string_compact())
304 .collect(),
305 analytics_jobs_json: self
306 .analytics_jobs
307 .iter()
308 .map(|job| analytics_job_to_json(job).to_string_compact())
309 .collect(),
310 tree_definitions_json: self
311 .tree_definitions
312 .iter()
313 .map(|definition| tree_definition_to_json(definition).to_string_compact())
314 .collect(),
315 collection_ttl_defaults_ms: self.collection_ttl_defaults_ms.clone(),
316 collection_contracts_json: self
317 .collection_contracts
318 .iter()
319 .map(|contract| collection_contract_to_json(contract).to_string_compact())
320 .collect(),
321 hypertables_json: self
322 .hypertables
323 .iter()
324 .map(|hypertable| hypertable_to_json(hypertable).to_string_compact())
325 .collect(),
326 exports_json: self
327 .exports
328 .iter()
329 .map(|export| export_descriptor_to_json(export).to_string_compact())
330 .collect(),
331 superblock_json: superblock_to_json(&self.superblock).to_string_compact(),
332 snapshots_json: self
333 .snapshots
334 .iter()
335 .map(|snapshot| snapshot_descriptor_to_json(snapshot).to_string_compact())
336 .collect(),
337 }
338 }
339
340 fn from_document_envelope(
341 envelope: reddb_file::PhysicalMetadataDocumentEnvelope,
342 ) -> io::Result<Self> {
343 Ok(Self {
344 protocol_version: envelope.protocol_version,
345 generated_at_unix_ms: envelope.generated_at_unix_ms,
346 last_loaded_from: envelope.last_loaded_from,
347 last_healed_at_unix_ms: envelope.last_healed_at_unix_ms,
348 manifest: manifest_from_json(&parse_document_fragment(
349 &envelope.manifest_json,
350 "manifest",
351 )?)?,
352 catalog: catalog_from_json(&parse_document_fragment(
353 &envelope.catalog_json,
354 "catalog",
355 )?)?,
356 manifest_events: parse_document_fragments(
357 &envelope.manifest_events_json,
358 "manifest_events",
359 manifest_event_from_json,
360 )?,
361 indexes: parse_document_fragments(
362 &envelope.indexes_json,
363 "indexes",
364 index_state_from_json,
365 )?,
366 graph_projections: parse_document_fragments(
367 &envelope.graph_projections_json,
368 "graph_projections",
369 graph_projection_from_json,
370 )?,
371 analytics_jobs: parse_document_fragments(
372 &envelope.analytics_jobs_json,
373 "analytics_jobs",
374 analytics_job_from_json,
375 )?,
376 tree_definitions: parse_document_fragments(
377 &envelope.tree_definitions_json,
378 "tree_definitions",
379 tree_definition_from_json,
380 )?,
381 collection_ttl_defaults_ms: envelope.collection_ttl_defaults_ms,
382 collection_contracts: parse_document_fragments(
383 &envelope.collection_contracts_json,
384 "collection_contracts",
385 collection_contract_from_json,
386 )?,
387 hypertables: parse_document_fragments(
388 &envelope.hypertables_json,
389 "hypertables",
390 hypertable_from_json,
391 )?,
392 exports: parse_document_fragments(
393 &envelope.exports_json,
394 "exports",
395 export_descriptor_from_json,
396 )?,
397 superblock: superblock_from_json(&parse_document_fragment(
398 &envelope.superblock_json,
399 "superblock",
400 )?)?,
401 snapshots: parse_document_fragments(
402 &envelope.snapshots_json,
403 "snapshots",
404 snapshot_descriptor_from_json,
405 )?,
406 })
407 }
408}
409
410fn parse_document_fragment(json: &str, field: &'static str) -> io::Result<JsonValue> {
411 crate::json::from_str::<JsonValue>(json)
412 .map_err(|err| invalid_data(format!("invalid physical metadata field '{field}': {err}")))
413}
414
415fn parse_document_fragments<T>(
416 fragments: &[String],
417 field: &'static str,
418 decode: fn(&JsonValue) -> io::Result<T>,
419) -> io::Result<Vec<T>> {
420 fragments
421 .iter()
422 .map(|fragment| decode(&parse_document_fragment(fragment, field)?))
423 .collect()
424}