1use std::path::{Path, PathBuf};
8
9use serde::{Deserialize, Serialize};
10
11pub const SUPPORT_DIR_SUFFIX: &str = "red";
12pub const UNIFIED_WAL_EXTENSION: &str = "rdb-uwal";
13pub const LOGICAL_WAL_SUFFIX: &str = "logical.wal";
14pub const TEMP_EXTENSION: &str = "rdb-tmp";
15pub const ATOMIC_TEMP_EXTENSION: &str = "tmp";
16pub const PRIMARY_WAL_EXTENSION: &str = "redwal";
17pub const PAGER_LEGACY_WAL_EXTENSION: &str = "wal";
18pub const ENGINE_WAL_EXTENSION: &str = "rdb-wal";
19pub const PAGER_DWB_EXTENSION: &str = "rdb-dwb";
20pub const PAGER_DWB_SHADOW_SUFFIX: &str = "dwb";
21pub const SHM_FILE_SUFFIX: &str = "shm";
22pub const PHYSICAL_METADATA_JSON_SUFFIX: &str = "meta.json";
23pub const PHYSICAL_METADATA_BINARY_EXTENSION: &str = "meta.rdbx";
24pub const REBOOTSTRAP_STAGING_EXTENSION: &str = "rebootstrap.redbase";
25pub const REBOOTSTRAP_PENDING_EXTENSION: &str = "rebootstrap.pending.rdb";
26pub const REBOOTSTRAP_READY_EXTENSION: &str = "rebootstrap.ready";
27pub const REBOOTSTRAP_INTENT_LOG_EXTENSION: &str = "rebootstrap.intent.jsonl";
28pub const REBOOTSTRAP_PREVIOUS_EXTENSION: &str = "rebootstrap.previous.rdb";
29pub const PRIMARY_REPLICA_ROOT_EXTENSION: &str = "primary-replica";
30pub const LEGACY_LOGICAL_SLOTS_SUFFIX: &str = "logical.slots.json";
31pub const LEGACY_LOGICAL_SLOTS_TEMP_EXTENSION: &str = "logical.slots.tmp";
32pub const LEGACY_AUDIT_LOG_FILE_NAME: &str = ".audit.log";
33pub const AUDIT_LOG_ROTATED_COMPRESSED_EXTENSION: &str = "zst";
34pub const LEGACY_SLOW_QUERY_LOG_FILE_NAME: &str = "red-slow.log";
35pub const SERVERLESS_ROOT_EXTENSION: &str = "serverless";
36pub const SERVERLESS_CACHE_DIR: &str = "cache";
37pub const RESULT_CACHE_L2_EXTENSION: &str = "result-cache.l2";
38pub const LOCAL_CAS_LOCK_SUFFIX: &str = "cas.lock";
39pub const LOCAL_UPLOAD_TEMP_TAG: &str = "tmp";
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
43#[serde(rename_all = "kebab-case")]
44#[derive(Default)]
45pub enum StorageLayout {
46 Minimal,
48 #[default]
50 Standard,
51 Performance,
53 Max,
55}
56
57#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
59#[serde(default)]
60pub struct LayoutOverrides {
61 pub dedicated_wal_dir: Option<bool>,
62 pub dedicated_index_dir: Option<bool>,
63 pub dedicated_cache_dir: Option<bool>,
64 pub dedicated_snapshot_dir: Option<bool>,
65 pub dedicated_blob_dir: Option<bool>,
66 pub dedicated_temp_dir: Option<bool>,
67 pub dedicated_metrics_dir: Option<bool>,
68 #[serde(default)]
70 pub logs: LogRoutingOverrides,
71}
72
73#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
75#[serde(rename_all = "kebab-case", tag = "kind", content = "path")]
76pub enum LogDestination {
77 Stderr,
78 File(PathBuf),
79 Syslog,
80}
81
82impl LogDestination {
83 pub fn describe(&self) -> String {
85 match self {
86 Self::Stderr => "stderr".to_string(),
87 Self::Syslog => "syslog".to_string(),
88 Self::File(path) => format!("file:{}", path.display()),
89 }
90 }
91
92 pub fn file_path(&self) -> Option<&Path> {
94 match self {
95 Self::File(path) => Some(path.as_path()),
96 _ => None,
97 }
98 }
99}
100
101#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
103#[serde(default)]
104pub struct LogRoutingOverrides {
105 pub audit_log: Option<LogDestination>,
106 pub slow_log: Option<LogDestination>,
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
111pub struct LayoutToggles {
112 pub dedicated_wal_dir: bool,
113 pub dedicated_index_dir: bool,
114 pub dedicated_cache_dir: bool,
115 pub dedicated_snapshot_dir: bool,
116 pub dedicated_blob_dir: bool,
117 pub dedicated_temp_dir: bool,
118 pub dedicated_metrics_dir: bool,
119}
120
121impl StorageLayout {
122 pub fn default_audit_log_in(self, support_dir: &Path) -> LogDestination {
124 match self {
125 Self::Performance | Self::Max => {
126 LogDestination::File(support_dir.join("logs").join("audit.log"))
127 }
128 Self::Minimal | Self::Standard => LogDestination::Stderr,
129 }
130 }
131
132 pub fn default_slow_log_in(self, support_dir: &Path) -> LogDestination {
134 match self {
135 Self::Performance | Self::Max => {
136 LogDestination::File(support_dir.join("logs").join("slow.log"))
137 }
138 Self::Minimal | Self::Standard => LogDestination::Stderr,
139 }
140 }
141
142 pub fn expand(self, overrides: &LayoutOverrides) -> LayoutToggles {
143 let mut toggles = match self {
144 Self::Minimal => LayoutToggles {
145 dedicated_wal_dir: false,
146 dedicated_index_dir: false,
147 dedicated_cache_dir: false,
148 dedicated_snapshot_dir: false,
149 dedicated_blob_dir: false,
150 dedicated_temp_dir: false,
151 dedicated_metrics_dir: false,
152 },
153 Self::Standard => LayoutToggles {
154 dedicated_wal_dir: false,
155 dedicated_index_dir: true,
156 dedicated_cache_dir: false,
157 dedicated_snapshot_dir: true,
158 dedicated_blob_dir: false,
159 dedicated_temp_dir: false,
160 dedicated_metrics_dir: false,
161 },
162 Self::Performance => LayoutToggles {
163 dedicated_wal_dir: true,
164 dedicated_index_dir: true,
165 dedicated_cache_dir: true,
166 dedicated_snapshot_dir: true,
167 dedicated_blob_dir: true,
168 dedicated_temp_dir: false,
169 dedicated_metrics_dir: false,
170 },
171 Self::Max => LayoutToggles {
172 dedicated_wal_dir: true,
173 dedicated_index_dir: true,
174 dedicated_cache_dir: true,
175 dedicated_snapshot_dir: true,
176 dedicated_blob_dir: true,
177 dedicated_temp_dir: true,
178 dedicated_metrics_dir: true,
179 },
180 };
181
182 if let Some(value) = overrides.dedicated_wal_dir {
183 toggles.dedicated_wal_dir = value;
184 }
185 if let Some(value) = overrides.dedicated_index_dir {
186 toggles.dedicated_index_dir = value;
187 }
188 if let Some(value) = overrides.dedicated_cache_dir {
189 toggles.dedicated_cache_dir = value;
190 }
191 if let Some(value) = overrides.dedicated_snapshot_dir {
192 toggles.dedicated_snapshot_dir = value;
193 }
194 if let Some(value) = overrides.dedicated_blob_dir {
195 toggles.dedicated_blob_dir = value;
196 }
197 if let Some(value) = overrides.dedicated_temp_dir {
198 toggles.dedicated_temp_dir = value;
199 }
200 if let Some(value) = overrides.dedicated_metrics_dir {
201 toggles.dedicated_metrics_dir = value;
202 }
203
204 toggles
205 }
206}
207
208#[derive(Debug, Clone, PartialEq, Eq)]
210pub struct TieredLayoutPaths {
211 pub data_file: PathBuf,
212 pub support_dir: PathBuf,
213 pub wal_file: PathBuf,
214 pub logical_wal_file: PathBuf,
215 pub temp_file: PathBuf,
216 pub snapshot_dir: Option<PathBuf>,
217 pub index_dir: Option<PathBuf>,
218 pub cache_dir: Option<PathBuf>,
219 pub blob_dir: Option<PathBuf>,
220 pub metrics_dir: Option<PathBuf>,
221 pub logs_dir: Option<PathBuf>,
222 pub audit_log_destination: LogDestination,
223 pub slow_log_destination: LogDestination,
224 pub toggles: LayoutToggles,
225}
226
227impl TieredLayoutPaths {
228 pub fn new(
229 data_path: &Path,
230 layout: StorageLayout,
231 overrides: LayoutOverrides,
232 ) -> TieredLayoutPaths {
233 let toggles = layout.expand(&overrides);
234 let data_file = data_path.to_path_buf();
235 let support_dir = support_dir_for(data_path);
236
237 let wal_file = if toggles.dedicated_wal_dir {
238 unified_wal_path_in(&support_dir, data_path)
239 } else {
240 unified_wal_path(data_path)
241 };
242 let logical_wal_file = if toggles.dedicated_wal_dir {
243 logical_wal_path_in(&support_dir, data_path)
244 } else {
245 logical_wal_path(data_path)
246 };
247 let temp_file = if toggles.dedicated_temp_dir {
248 temp_path_in(&support_dir, data_path)
249 } else {
250 temp_path(data_path)
251 };
252
253 let audit_log_destination = overrides
254 .logs
255 .audit_log
256 .clone()
257 .unwrap_or_else(|| layout.default_audit_log_in(&support_dir));
258 let slow_log_destination = overrides
259 .logs
260 .slow_log
261 .clone()
262 .unwrap_or_else(|| layout.default_slow_log_in(&support_dir));
263 let logs_dir = match (
264 audit_log_destination.file_path(),
265 slow_log_destination.file_path(),
266 ) {
267 (None, None) => None,
268 _ => Some(support_dir.join("logs")),
269 };
270
271 TieredLayoutPaths {
272 data_file,
273 support_dir: support_dir.clone(),
274 wal_file,
275 logical_wal_file,
276 temp_file,
277 snapshot_dir: toggles
278 .dedicated_snapshot_dir
279 .then(|| support_dir.join("snapshots")),
280 index_dir: toggles
281 .dedicated_index_dir
282 .then(|| support_dir.join("indexes")),
283 cache_dir: toggles
284 .dedicated_cache_dir
285 .then(|| support_dir.join("cache")),
286 blob_dir: toggles
287 .dedicated_blob_dir
288 .then(|| support_dir.join("blobs")),
289 metrics_dir: toggles
290 .dedicated_metrics_dir
291 .then(|| support_dir.join("metrics")),
292 logs_dir,
293 audit_log_destination,
294 slow_log_destination,
295 toggles,
296 }
297 }
298
299 pub fn dirs_to_create(&self) -> Vec<PathBuf> {
300 let mut dirs = Vec::new();
301 push_parent(&mut dirs, &self.data_file);
302 push_parent(&mut dirs, &self.wal_file);
303 push_parent(&mut dirs, &self.logical_wal_file);
304 push_parent(&mut dirs, &self.temp_file);
305 push_optional(&mut dirs, self.snapshot_dir.as_ref());
306 push_optional(&mut dirs, self.index_dir.as_ref());
307 push_optional(&mut dirs, self.cache_dir.as_ref());
308 push_optional(&mut dirs, self.blob_dir.as_ref());
309 push_optional(&mut dirs, self.metrics_dir.as_ref());
310 push_optional(&mut dirs, self.logs_dir.as_ref());
311 if let Some(path) = self.audit_log_destination.file_path() {
312 push_parent(&mut dirs, path);
313 }
314 if let Some(path) = self.slow_log_destination.file_path() {
315 push_parent(&mut dirs, path);
316 }
317 dirs.sort();
318 dirs.dedup();
319 dirs
320 }
321
322 pub fn ensure_dirs(&self) -> std::io::Result<()> {
323 for dir in self.dirs_to_create() {
324 std::fs::create_dir_all(dir)?;
325 }
326 Ok(())
327 }
328
329 pub fn turbo_snapshot_path(&self, collection: &str) -> Option<PathBuf> {
331 if !self.toggles.dedicated_snapshot_dir {
332 return None;
333 }
334 if let Some(dir) = &self.snapshot_dir {
335 return Some(dir.join(format!("{collection}.tv")));
336 }
337 let stem = data_file_name(&self.data_file);
338 Some(sibling_path(
339 &self.data_file,
340 &format!("{stem}.{collection}.tv"),
341 ))
342 }
343}
344
345pub fn data_file_name(path: &Path) -> String {
346 path.file_name()
347 .and_then(|name| name.to_str())
348 .unwrap_or(DEFAULT_DATABASE_FILE_NAME)
349 .to_string()
350}
351
352pub const DEFAULT_DATABASE_FILE_NAME: &str = "data.rdb";
353pub const DEFAULT_SERVICE_DATABASE_PATH: &str = "/var/lib/reddb/data.rdb";
354pub const TRANSACTION_WAL_FILE_NAME: &str = "wal.log";
355
356pub fn default_database_path() -> PathBuf {
357 PathBuf::from(DEFAULT_DATABASE_FILE_NAME)
358}
359
360pub fn default_service_database_path() -> PathBuf {
361 PathBuf::from(DEFAULT_SERVICE_DATABASE_PATH)
362}
363
364pub fn default_transaction_wal_path() -> PathBuf {
365 PathBuf::from(TRANSACTION_WAL_FILE_NAME)
366}
367
368pub fn sibling_path(path: &Path, file_name: &str) -> PathBuf {
369 match path.parent() {
370 Some(parent) if !parent.as_os_str().is_empty() => parent.join(file_name),
371 _ => PathBuf::from(file_name),
372 }
373}
374
375pub fn sidecar_file_name(path: &Path, extension: &str) -> String {
376 path.with_extension(extension)
377 .file_name()
378 .and_then(|name| name.to_str())
379 .unwrap_or(DEFAULT_DATABASE_FILE_NAME)
380 .to_string()
381}
382
383pub fn support_dir_for(data_path: &Path) -> PathBuf {
384 let file_name = data_file_name(data_path);
385 sibling_path(data_path, &format!("{file_name}.{SUPPORT_DIR_SUFFIX}"))
386}
387
388pub fn unified_wal_path(data_path: &Path) -> PathBuf {
389 data_path.with_extension(UNIFIED_WAL_EXTENSION)
390}
391
392pub fn unified_wal_path_in(support_dir: &Path, data_path: &Path) -> PathBuf {
393 support_dir
394 .join("wal")
395 .join(sidecar_file_name(data_path, UNIFIED_WAL_EXTENSION))
396}
397
398pub fn store_commit_coord_temp_wal_path(
399 temp_dir: &Path,
400 name: &str,
401 process_id: u32,
402 nanos: u128,
403) -> PathBuf {
404 temp_dir.join(store_commit_coord_temp_wal_file_name(
405 name, process_id, nanos,
406 ))
407}
408
409pub fn store_commit_coord_temp_wal_file_name(name: &str, process_id: u32, nanos: u128) -> String {
410 format!("rb_commit_coord_{name}_{process_id}_{nanos}.wal")
411}
412
413pub fn group_commit_temp_wal_path(
414 temp_dir: &Path,
415 name: &str,
416 process_id: u32,
417 nanos: u128,
418) -> PathBuf {
419 temp_dir.join(group_commit_temp_wal_file_name(name, process_id, nanos))
420}
421
422pub fn group_commit_temp_wal_file_name(name: &str, process_id: u32, nanos: u128) -> String {
423 format!("rb_group_commit_{name}_{process_id}_{nanos}.wal")
424}
425
426pub fn wal_component_temp_path(
427 temp_dir: &Path,
428 component: &str,
429 name: &str,
430 process_id: u32,
431) -> PathBuf {
432 temp_dir.join(wal_component_temp_file_name(component, name, process_id))
433}
434
435pub fn wal_component_temp_file_name(component: &str, name: &str, process_id: u32) -> String {
436 format!("rb_wal_{component}_{name}_{process_id}.wal")
437}
438
439pub fn wal_component_unique_temp_path(
440 temp_dir: &Path,
441 component: &str,
442 name: &str,
443 process_id: u32,
444 nanos: u128,
445) -> PathBuf {
446 temp_dir.join(wal_component_unique_temp_file_name(
447 component, name, process_id, nanos,
448 ))
449}
450
451pub fn wal_component_unique_temp_file_name(
452 component: &str,
453 name: &str,
454 process_id: u32,
455 nanos: u128,
456) -> String {
457 format!("rb_wal_{component}_{name}_{process_id}_{nanos}.wal")
458}
459
460pub fn backup_temp_json_path(
461 temp_dir: &Path,
462 prefix: &str,
463 process_id: u32,
464 nanos: u128,
465 unique: u64,
466 start_lsn: Option<u64>,
467 end_lsn: Option<u64>,
468) -> PathBuf {
469 temp_dir.join(backup_temp_json_file_name(
470 prefix, process_id, nanos, unique, start_lsn, end_lsn,
471 ))
472}
473
474pub fn backup_temp_json_file_name(
475 prefix: &str,
476 process_id: u32,
477 nanos: u128,
478 unique: u64,
479 start_lsn: Option<u64>,
480 end_lsn: Option<u64>,
481) -> String {
482 match (start_lsn, end_lsn) {
486 (Some(start_lsn), Some(end_lsn)) => {
487 format!("{prefix}-{process_id}-{start_lsn}-{end_lsn}-{nanos}-{unique}.json")
488 }
489 _ => format!("{prefix}-{process_id}-{nanos}-{unique}.json"),
490 }
491}
492
493pub fn logical_wal_path(data_path: &Path) -> PathBuf {
494 sibling_path(
495 data_path,
496 &format!("{}.{}", data_file_name(data_path), LOGICAL_WAL_SUFFIX),
497 )
498}
499
500pub fn logical_wal_temp_path(logical_wal_path: &Path) -> PathBuf {
501 logical_wal_path.with_extension("logical.wal.tmp")
502}
503
504pub fn logical_wal_path_in(support_dir: &Path, data_path: &Path) -> PathBuf {
505 support_dir.join("wal").join(format!(
506 "{}.{}",
507 data_file_name(data_path),
508 LOGICAL_WAL_SUFFIX
509 ))
510}
511
512pub fn temp_path(data_path: &Path) -> PathBuf {
513 data_path.with_extension(TEMP_EXTENSION)
514}
515
516pub fn atomic_temp_path(path: &Path) -> PathBuf {
517 path.with_extension(ATOMIC_TEMP_EXTENSION)
518}
519
520pub fn result_cache_l2_path(data_path: &Path) -> PathBuf {
521 data_path.with_extension(RESULT_CACHE_L2_EXTENSION)
522}
523
524pub fn temp_path_in(support_dir: &Path, data_path: &Path) -> PathBuf {
525 support_dir
526 .join("tmp")
527 .join(sidecar_file_name(data_path, TEMP_EXTENSION))
528}
529
530pub fn primary_wal_segment_file_name(segment_index: u64) -> String {
531 format!("{segment_index:020}.{PRIMARY_WAL_EXTENSION}")
532}
533
534pub fn relay_segment_relative_path(start_lsn: u64, end_lsn: u64) -> PathBuf {
535 PathBuf::from(format!(
536 "relay-{start_lsn:020}-{end_lsn:020}.{PRIMARY_WAL_EXTENSION}"
537 ))
538}
539
540pub fn pager_legacy_wal_path(data_path: &Path) -> PathBuf {
541 data_path.with_extension(PAGER_LEGACY_WAL_EXTENSION)
542}
543
544pub fn engine_wal_path(data_path: &Path) -> PathBuf {
545 data_path.with_extension(ENGINE_WAL_EXTENSION)
546}
547
548pub fn pager_dwb_path(data_path: &Path) -> PathBuf {
549 data_path.with_extension(PAGER_DWB_EXTENSION)
550}
551
552fn path_with_dash_suffix(data_path: &Path, suffix: &str) -> PathBuf {
553 let mut path = data_path.to_path_buf().into_os_string();
554 path.push("-");
555 path.push(suffix);
556 PathBuf::from(path)
557}
558
559pub fn pager_dwb_shadow_path(data_path: &Path) -> PathBuf {
560 path_with_dash_suffix(data_path, PAGER_DWB_SHADOW_SUFFIX)
561}
562
563pub fn pager_shadow_sidecar_paths(data_path: &Path) -> [PathBuf; 1] {
564 [pager_dwb_shadow_path(data_path)]
565}
566
567pub mod retired {
577 use super::path_with_dash_suffix;
578 use std::path::{Path, PathBuf};
579
580 pub const PAGER_HEADER_EXTENSION_V0: &str = "rdb-hdr";
582 pub const PAGER_META_EXTENSION_V0: &str = "rdb-meta";
584
585 pub fn pager_header_path_v0(data_path: &Path) -> PathBuf {
586 data_path.with_extension(PAGER_HEADER_EXTENSION_V0)
587 }
588
589 pub fn pager_meta_path_v0(data_path: &Path) -> PathBuf {
590 data_path.with_extension(PAGER_META_EXTENSION_V0)
591 }
592
593 pub fn pager_header_shadow_path_v0(data_path: &Path) -> PathBuf {
594 path_with_dash_suffix(data_path, "hdr")
595 }
596
597 pub fn pager_meta_shadow_path_v0(data_path: &Path) -> PathBuf {
598 path_with_dash_suffix(data_path, "meta")
599 }
600
601 pub fn phase1_sidecar_paths(data_path: &Path) -> [PathBuf; 4] {
605 [
606 pager_header_path_v0(data_path),
607 pager_meta_path_v0(data_path),
608 pager_header_shadow_path_v0(data_path),
609 pager_meta_shadow_path_v0(data_path),
610 ]
611 }
612
613 pub fn first_present_phase1_sidecar(data_path: &Path) -> Option<PathBuf> {
616 phase1_sidecar_paths(data_path)
617 .into_iter()
618 .find(|path| path.exists())
619 }
620}
621
622pub fn shm_path(data_path: &Path) -> PathBuf {
623 sibling_path(
624 data_path,
625 &format!("{}-{SHM_FILE_SUFFIX}", data_file_name(data_path)),
626 )
627}
628
629pub fn physical_metadata_json_path(data_path: &Path) -> PathBuf {
630 sibling_path(
631 data_path,
632 &format!(
633 "{}.{PHYSICAL_METADATA_JSON_SUFFIX}",
634 data_file_name(data_path)
635 ),
636 )
637}
638
639pub fn physical_metadata_binary_path(data_path: &Path) -> PathBuf {
640 sibling_path(
641 data_path,
642 &format!(
643 "{}.{PHYSICAL_METADATA_BINARY_EXTENSION}",
644 data_file_name(data_path)
645 ),
646 )
647}
648
649pub fn physical_metadata_journal_path(data_path: &Path, sequence: u64) -> PathBuf {
650 sibling_path(
651 data_path,
652 &format!(
653 "{}.{PHYSICAL_METADATA_BINARY_EXTENSION}.seq-{sequence:020}",
654 data_file_name(data_path)
655 ),
656 )
657}
658
659pub fn physical_metadata_journal_prefix(data_path: &Path) -> String {
660 format!(
661 "{}.{PHYSICAL_METADATA_BINARY_EXTENSION}.seq-",
662 data_file_name(data_path)
663 )
664}
665
666pub fn physical_export_data_path(data_path: &Path, name: &str) -> PathBuf {
667 let file_name = data_file_name(data_path);
668 let stem = file_name.strip_suffix(".rdb").unwrap_or(&file_name);
669 sibling_path(
670 data_path,
671 &format!("{stem}.export.{}.rdb", sanitize_export_name(name)),
672 )
673}
674
675pub fn local_cas_lock_path(dest: &Path) -> PathBuf {
676 let file_name = data_file_name(dest);
677 dest.with_file_name(format!(".{file_name}.{LOCAL_CAS_LOCK_SUFFIX}"))
678}
679
680pub fn local_upload_temp_path(dest: &Path, pid: u32, unique: u64) -> PathBuf {
681 let file_name = data_file_name(dest);
682 dest.with_file_name(format!(
683 ".{file_name}.{LOCAL_UPLOAD_TEMP_TAG}-{pid}-{unique}"
684 ))
685}
686
687pub fn rebootstrap_staging_root(data_path: &Path) -> PathBuf {
688 data_path.with_extension(REBOOTSTRAP_STAGING_EXTENSION)
689}
690
691pub fn rebootstrap_pending_path(data_path: &Path) -> PathBuf {
692 data_path.with_extension(REBOOTSTRAP_PENDING_EXTENSION)
693}
694
695pub fn rebootstrap_ready_marker_path(data_path: &Path) -> PathBuf {
696 data_path.with_extension(REBOOTSTRAP_READY_EXTENSION)
697}
698
699pub fn rebootstrap_intent_log_path(data_path: &Path) -> PathBuf {
700 data_path.with_extension(REBOOTSTRAP_INTENT_LOG_EXTENSION)
701}
702
703pub fn rebootstrap_previous_path(data_path: &Path) -> PathBuf {
704 data_path.with_extension(REBOOTSTRAP_PREVIOUS_EXTENSION)
705}
706
707pub fn primary_replica_root(data_path: &Path) -> PathBuf {
708 data_path.with_extension(PRIMARY_REPLICA_ROOT_EXTENSION)
709}
710
711pub fn legacy_logical_slots_path(data_path: &Path) -> PathBuf {
712 let file_name = data_path
713 .file_name()
714 .and_then(|name| name.to_str())
715 .unwrap_or("reddb.rdb");
716 sibling_path(
717 data_path,
718 &format!("{file_name}.{LEGACY_LOGICAL_SLOTS_SUFFIX}"),
719 )
720}
721
722pub fn legacy_logical_slots_temp_path(path: &Path) -> PathBuf {
723 path.with_extension(LEGACY_LOGICAL_SLOTS_TEMP_EXTENSION)
724}
725
726pub fn legacy_audit_log_path(data_path: &Path) -> PathBuf {
727 sibling_path(data_path, LEGACY_AUDIT_LOG_FILE_NAME)
728}
729
730pub fn audit_log_rotated_plain_path(active_path: &Path, timestamp_nanos: u128) -> PathBuf {
731 sibling_path(
732 active_path,
733 &format!("{}.{timestamp_nanos}", audit_log_file_name(active_path)),
734 )
735}
736
737pub fn audit_log_rotated_compressed_path(active_path: &Path, timestamp_nanos: u128) -> PathBuf {
738 sibling_path(
739 active_path,
740 &format!(
741 "{}.{timestamp_nanos}.{AUDIT_LOG_ROTATED_COMPRESSED_EXTENSION}",
742 audit_log_file_name(active_path)
743 ),
744 )
745}
746
747pub fn parse_audit_log_rotated_timestamp(
748 active_path: &Path,
749 candidate_file_name: &str,
750) -> Option<u128> {
751 let active_name = audit_log_file_name(active_path);
752 let rotated = candidate_file_name.strip_prefix(&format!("{active_name}."))?;
753 let timestamp = rotated
754 .strip_suffix(&format!(".{AUDIT_LOG_ROTATED_COMPRESSED_EXTENSION}"))
755 .unwrap_or(rotated);
756 timestamp.parse::<u128>().ok()
757}
758
759pub fn legacy_slow_query_log_path(log_dir: &Path) -> PathBuf {
760 log_dir.join(LEGACY_SLOW_QUERY_LOG_FILE_NAME)
761}
762
763pub fn serverless_root(data_path: &Path) -> PathBuf {
764 data_path.with_extension(SERVERLESS_ROOT_EXTENSION)
765}
766
767pub fn serverless_namespace(data_path: &Path) -> String {
768 data_path
769 .file_stem()
770 .and_then(|stem| stem.to_str())
771 .filter(|stem| !stem.is_empty())
772 .unwrap_or("default")
773 .to_string()
774}
775
776pub fn serverless_cache_root(root: &Path, namespace: &str) -> PathBuf {
777 root.join(namespace).join(SERVERLESS_CACHE_DIR)
778}
779
780fn audit_log_file_name(path: &Path) -> String {
781 path.file_name()
782 .and_then(|name| name.to_str())
783 .unwrap_or(LEGACY_AUDIT_LOG_FILE_NAME)
784 .to_string()
785}
786
787fn push_parent(dirs: &mut Vec<PathBuf>, path: &Path) {
788 if let Some(parent) = path.parent() {
789 if !parent.as_os_str().is_empty() {
790 dirs.push(parent.to_path_buf());
791 }
792 }
793}
794
795fn push_optional(dirs: &mut Vec<PathBuf>, path: Option<&PathBuf>) {
796 if let Some(path) = path {
797 dirs.push(path.clone());
798 }
799}
800
801fn sanitize_export_name(name: &str) -> String {
802 let mut out = String::new();
803 for ch in name.chars() {
804 if ch.is_ascii_alphanumeric() || ch == '-' || ch == '_' {
805 out.push(ch);
806 } else {
807 out.push('_');
808 }
809 }
810 if out.is_empty() {
811 "export".to_string()
812 } else {
813 out
814 }
815}
816
817#[cfg(test)]
818mod tests {
819 use super::*;
820
821 #[test]
822 fn derives_standard_sidecars_next_to_database() {
823 let path = Path::new("/var/lib/reddb/main.rdb");
824
825 assert_eq!(
826 support_dir_for(path),
827 PathBuf::from("/var/lib/reddb/main.rdb.red")
828 );
829 assert_eq!(
830 unified_wal_path(path),
831 PathBuf::from("/var/lib/reddb/main.rdb-uwal")
832 );
833 assert_eq!(
834 store_commit_coord_temp_wal_path(Path::new("/tmp"), "burst", 7, 99),
835 PathBuf::from("/tmp/rb_commit_coord_burst_7_99.wal")
836 );
837 assert_eq!(
838 group_commit_temp_wal_path(Path::new("/tmp"), "batch", 7, 99),
839 PathBuf::from("/tmp/rb_group_commit_batch_7_99.wal")
840 );
841 assert_eq!(
842 wal_component_temp_path(Path::new("/tmp"), "writer", "create", 7),
843 PathBuf::from("/tmp/rb_wal_writer_create_7.wal")
844 );
845 assert_eq!(
846 wal_component_temp_path(Path::new("/tmp"), "reader", "empty", 7),
847 PathBuf::from("/tmp/rb_wal_reader_empty_7.wal")
848 );
849 assert_eq!(
850 wal_component_unique_temp_path(Path::new("/tmp"), "coord", "single", 7, 99),
851 PathBuf::from("/tmp/rb_wal_coord_single_7_99.wal")
852 );
853 assert_eq!(
854 backup_temp_json_path(
855 Path::new("/tmp"),
856 "reddb-archived-change-records",
857 7,
858 99,
859 3,
860 Some(10),
861 Some(20)
862 ),
863 PathBuf::from("/tmp/reddb-archived-change-records-7-10-20-99-3.json")
864 );
865 assert_eq!(
866 backup_temp_json_path(Path::new("/tmp"), "reddb-json-object", 7, 99, 3, None, None),
867 PathBuf::from("/tmp/reddb-json-object-7-99-3.json")
868 );
869 assert_eq!(
870 logical_wal_path(path),
871 PathBuf::from("/var/lib/reddb/main.rdb.logical.wal")
872 );
873 assert_eq!(
874 logical_wal_temp_path(&logical_wal_path(path)),
875 PathBuf::from("/var/lib/reddb/main.rdb.logical.logical.wal.tmp")
876 );
877 assert_eq!(
878 temp_path(path),
879 PathBuf::from("/var/lib/reddb/main.rdb-tmp")
880 );
881 assert_eq!(
882 atomic_temp_path(&pager_dwb_path(path)),
883 PathBuf::from("/var/lib/reddb/main.tmp")
884 );
885 assert_eq!(
886 result_cache_l2_path(path),
887 PathBuf::from("/var/lib/reddb/main.result-cache.l2")
888 );
889 assert_eq!(
890 engine_wal_path(path),
891 PathBuf::from("/var/lib/reddb/main.rdb-wal")
892 );
893 assert_eq!(
894 pager_legacy_wal_path(path),
895 PathBuf::from("/var/lib/reddb/main.wal")
896 );
897 assert_eq!(
898 pager_dwb_shadow_path(path),
899 PathBuf::from("/var/lib/reddb/main.rdb-dwb")
900 );
901 assert_eq!(shm_path(path), PathBuf::from("/var/lib/reddb/main.rdb-shm"));
902 assert_eq!(
903 physical_metadata_json_path(path),
904 PathBuf::from("/var/lib/reddb/main.rdb.meta.json")
905 );
906 assert_eq!(
907 physical_metadata_binary_path(path),
908 PathBuf::from("/var/lib/reddb/main.rdb.meta.rdbx")
909 );
910 assert_eq!(
911 physical_metadata_journal_path(path, 7),
912 PathBuf::from("/var/lib/reddb/main.rdb.meta.rdbx.seq-00000000000000000007")
913 );
914 assert_eq!(
915 physical_metadata_journal_prefix(path),
916 "main.rdb.meta.rdbx.seq-"
917 );
918 assert_eq!(
919 physical_export_data_path(path, "nightly backup"),
920 PathBuf::from("/var/lib/reddb/main.export.nightly_backup.rdb")
921 );
922 assert_eq!(
923 local_cas_lock_path(path),
924 PathBuf::from("/var/lib/reddb/.main.rdb.cas.lock")
925 );
926 assert_eq!(
927 local_upload_temp_path(path, 123, 7),
928 PathBuf::from("/var/lib/reddb/.main.rdb.tmp-123-7")
929 );
930 assert_eq!(
931 rebootstrap_staging_root(path),
932 PathBuf::from("/var/lib/reddb/main.rebootstrap.redbase")
933 );
934 assert_eq!(
935 rebootstrap_pending_path(path),
936 PathBuf::from("/var/lib/reddb/main.rebootstrap.pending.rdb")
937 );
938 assert_eq!(
939 rebootstrap_ready_marker_path(path),
940 PathBuf::from("/var/lib/reddb/main.rebootstrap.ready")
941 );
942 assert_eq!(
943 rebootstrap_intent_log_path(path),
944 PathBuf::from("/var/lib/reddb/main.rebootstrap.intent.jsonl")
945 );
946 assert_eq!(
947 rebootstrap_previous_path(path),
948 PathBuf::from("/var/lib/reddb/main.rebootstrap.previous.rdb")
949 );
950 assert_eq!(
951 primary_replica_root(path),
952 PathBuf::from("/var/lib/reddb/main.primary-replica")
953 );
954 assert_eq!(
955 legacy_logical_slots_path(path),
956 PathBuf::from("/var/lib/reddb/main.rdb.logical.slots.json")
957 );
958 assert_eq!(
959 legacy_logical_slots_temp_path(&legacy_logical_slots_path(path)),
960 PathBuf::from("/var/lib/reddb/main.rdb.logical.slots.logical.slots.tmp")
961 );
962 assert_eq!(
963 legacy_audit_log_path(path),
964 PathBuf::from("/var/lib/reddb/.audit.log")
965 );
966 assert_eq!(
967 audit_log_rotated_plain_path(&legacy_audit_log_path(path), 42),
968 PathBuf::from("/var/lib/reddb/.audit.log.42")
969 );
970 assert_eq!(
971 audit_log_rotated_compressed_path(&legacy_audit_log_path(path), 42),
972 PathBuf::from("/var/lib/reddb/.audit.log.42.zst")
973 );
974 assert_eq!(
975 parse_audit_log_rotated_timestamp(&legacy_audit_log_path(path), ".audit.log.42"),
976 Some(42)
977 );
978 assert_eq!(
979 parse_audit_log_rotated_timestamp(&legacy_audit_log_path(path), ".audit.log.42.zst"),
980 Some(42)
981 );
982 assert_eq!(
983 parse_audit_log_rotated_timestamp(&legacy_audit_log_path(path), "other.42.zst"),
984 None
985 );
986 assert_eq!(
987 legacy_slow_query_log_path(Path::new("/var/log/reddb")),
988 PathBuf::from("/var/log/reddb/red-slow.log")
989 );
990 assert_eq!(
991 serverless_root(path),
992 PathBuf::from("/var/lib/reddb/main.serverless")
993 );
994 assert_eq!(serverless_namespace(path), "main");
995 assert_eq!(
996 serverless_cache_root(&serverless_root(path), &serverless_namespace(path)),
997 PathBuf::from("/var/lib/reddb/main.serverless/main/cache")
998 );
999 }
1000
1001 #[test]
1002 fn derives_dedicated_support_sidecars() {
1003 let path = Path::new("/var/lib/reddb/main.rdb");
1004 let support = support_dir_for(path);
1005
1006 assert_eq!(
1007 unified_wal_path_in(&support, path),
1008 PathBuf::from("/var/lib/reddb/main.rdb.red/wal/main.rdb-uwal")
1009 );
1010 assert_eq!(
1011 logical_wal_path_in(&support, path),
1012 PathBuf::from("/var/lib/reddb/main.rdb.red/wal/main.rdb.logical.wal")
1013 );
1014 assert_eq!(
1015 temp_path_in(&support, path),
1016 PathBuf::from("/var/lib/reddb/main.rdb.red/tmp/main.rdb-tmp")
1017 );
1018 }
1019
1020 #[test]
1021 fn derives_primary_replica_segment_names() {
1022 assert_eq!(
1023 primary_wal_segment_file_name(2),
1024 "00000000000000000002.redwal"
1025 );
1026 assert_eq!(
1027 relay_segment_relative_path(10, 20),
1028 PathBuf::from("relay-00000000000000000010-00000000000000000020.redwal")
1029 );
1030 }
1031
1032 #[test]
1033 fn the_live_pager_sidecar_set_is_the_dwb_alone() {
1034 let path = Path::new("/var/lib/reddb/main.rdb");
1036 assert_eq!(
1037 pager_shadow_sidecar_paths(path),
1038 [PathBuf::from("/var/lib/reddb/main.rdb-dwb")]
1039 );
1040 }
1041
1042 #[test]
1043 fn retired_phase1_names_stay_reachable_only_for_migration_and_refusal() {
1044 let path = Path::new("/var/lib/reddb/main.rdb");
1045 assert_eq!(
1048 retired::phase1_sidecar_paths(path),
1049 [
1050 PathBuf::from("/var/lib/reddb/main.rdb-hdr"),
1051 PathBuf::from("/var/lib/reddb/main.rdb-meta"),
1052 PathBuf::from("/var/lib/reddb/main.rdb-hdr"),
1053 PathBuf::from("/var/lib/reddb/main.rdb-meta"),
1054 ]
1055 );
1056 let l2 = Path::new("/var/lib/reddb/main.result-cache.l2");
1059 assert_eq!(
1060 retired::pager_header_path_v0(l2),
1061 PathBuf::from("/var/lib/reddb/main.result-cache.rdb-hdr")
1062 );
1063 assert_eq!(
1064 retired::pager_header_shadow_path_v0(l2),
1065 PathBuf::from("/var/lib/reddb/main.result-cache.l2-hdr")
1066 );
1067 }
1068}