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; 0] {
564 []
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 pub const PAGER_DWB_EXTENSION_V0: &str = "rdb-dwb";
586
587 pub fn pager_header_path_v0(data_path: &Path) -> PathBuf {
588 data_path.with_extension(PAGER_HEADER_EXTENSION_V0)
589 }
590
591 pub fn pager_meta_path_v0(data_path: &Path) -> PathBuf {
592 data_path.with_extension(PAGER_META_EXTENSION_V0)
593 }
594
595 pub fn pager_dwb_path_v0(data_path: &Path) -> PathBuf {
596 data_path.with_extension(PAGER_DWB_EXTENSION_V0)
597 }
598
599 pub fn pager_header_shadow_path_v0(data_path: &Path) -> PathBuf {
600 path_with_dash_suffix(data_path, "hdr")
601 }
602
603 pub fn pager_meta_shadow_path_v0(data_path: &Path) -> PathBuf {
604 path_with_dash_suffix(data_path, "meta")
605 }
606
607 pub fn pager_dwb_shadow_path_v0(data_path: &Path) -> PathBuf {
608 path_with_dash_suffix(data_path, "dwb")
609 }
610
611 pub fn phase1_sidecar_paths(data_path: &Path) -> [PathBuf; 4] {
615 [
616 pager_header_path_v0(data_path),
617 pager_meta_path_v0(data_path),
618 pager_header_shadow_path_v0(data_path),
619 pager_meta_shadow_path_v0(data_path),
620 ]
621 }
622
623 pub fn first_present_phase1_sidecar(data_path: &Path) -> Option<PathBuf> {
626 phase1_sidecar_paths(data_path)
627 .into_iter()
628 .find(|path| path.exists())
629 }
630
631 pub fn phase3_dwb_sidecar_paths(data_path: &Path) -> [PathBuf; 2] {
633 [
634 pager_dwb_path_v0(data_path),
635 pager_dwb_shadow_path_v0(data_path),
636 ]
637 }
638
639 pub fn first_present_phase3_dwb_sidecar(data_path: &Path) -> Option<PathBuf> {
640 phase3_dwb_sidecar_paths(data_path)
641 .into_iter()
642 .find(|path| path.exists())
643 }
644}
645
646pub fn shm_path(data_path: &Path) -> PathBuf {
647 sibling_path(
648 data_path,
649 &format!("{}-{SHM_FILE_SUFFIX}", data_file_name(data_path)),
650 )
651}
652
653pub fn physical_metadata_json_path(data_path: &Path) -> PathBuf {
654 sibling_path(
655 data_path,
656 &format!(
657 "{}.{PHYSICAL_METADATA_JSON_SUFFIX}",
658 data_file_name(data_path)
659 ),
660 )
661}
662
663pub fn physical_metadata_binary_path(data_path: &Path) -> PathBuf {
664 sibling_path(
665 data_path,
666 &format!(
667 "{}.{PHYSICAL_METADATA_BINARY_EXTENSION}",
668 data_file_name(data_path)
669 ),
670 )
671}
672
673pub fn physical_metadata_journal_path(data_path: &Path, sequence: u64) -> PathBuf {
674 sibling_path(
675 data_path,
676 &format!(
677 "{}.{PHYSICAL_METADATA_BINARY_EXTENSION}.seq-{sequence:020}",
678 data_file_name(data_path)
679 ),
680 )
681}
682
683pub fn physical_metadata_journal_prefix(data_path: &Path) -> String {
684 format!(
685 "{}.{PHYSICAL_METADATA_BINARY_EXTENSION}.seq-",
686 data_file_name(data_path)
687 )
688}
689
690pub fn physical_export_data_path(data_path: &Path, name: &str) -> PathBuf {
691 let file_name = data_file_name(data_path);
692 let stem = file_name.strip_suffix(".rdb").unwrap_or(&file_name);
693 sibling_path(
694 data_path,
695 &format!("{stem}.export.{}.rdb", sanitize_export_name(name)),
696 )
697}
698
699pub fn local_cas_lock_path(dest: &Path) -> PathBuf {
700 let file_name = data_file_name(dest);
701 dest.with_file_name(format!(".{file_name}.{LOCAL_CAS_LOCK_SUFFIX}"))
702}
703
704pub fn local_upload_temp_path(dest: &Path, pid: u32, unique: u64) -> PathBuf {
705 let file_name = data_file_name(dest);
706 dest.with_file_name(format!(
707 ".{file_name}.{LOCAL_UPLOAD_TEMP_TAG}-{pid}-{unique}"
708 ))
709}
710
711pub fn rebootstrap_staging_root(data_path: &Path) -> PathBuf {
712 data_path.with_extension(REBOOTSTRAP_STAGING_EXTENSION)
713}
714
715pub fn rebootstrap_pending_path(data_path: &Path) -> PathBuf {
716 data_path.with_extension(REBOOTSTRAP_PENDING_EXTENSION)
717}
718
719pub fn rebootstrap_ready_marker_path(data_path: &Path) -> PathBuf {
720 data_path.with_extension(REBOOTSTRAP_READY_EXTENSION)
721}
722
723pub fn rebootstrap_intent_log_path(data_path: &Path) -> PathBuf {
724 data_path.with_extension(REBOOTSTRAP_INTENT_LOG_EXTENSION)
725}
726
727pub fn rebootstrap_previous_path(data_path: &Path) -> PathBuf {
728 data_path.with_extension(REBOOTSTRAP_PREVIOUS_EXTENSION)
729}
730
731pub fn primary_replica_root(data_path: &Path) -> PathBuf {
732 data_path.with_extension(PRIMARY_REPLICA_ROOT_EXTENSION)
733}
734
735pub fn legacy_logical_slots_path(data_path: &Path) -> PathBuf {
736 let file_name = data_path
737 .file_name()
738 .and_then(|name| name.to_str())
739 .unwrap_or("reddb.rdb");
740 sibling_path(
741 data_path,
742 &format!("{file_name}.{LEGACY_LOGICAL_SLOTS_SUFFIX}"),
743 )
744}
745
746pub fn legacy_logical_slots_temp_path(path: &Path) -> PathBuf {
747 path.with_extension(LEGACY_LOGICAL_SLOTS_TEMP_EXTENSION)
748}
749
750pub fn legacy_audit_log_path(data_path: &Path) -> PathBuf {
751 sibling_path(data_path, LEGACY_AUDIT_LOG_FILE_NAME)
752}
753
754pub fn audit_log_rotated_plain_path(active_path: &Path, timestamp_nanos: u128) -> PathBuf {
755 sibling_path(
756 active_path,
757 &format!("{}.{timestamp_nanos}", audit_log_file_name(active_path)),
758 )
759}
760
761pub fn audit_log_rotated_compressed_path(active_path: &Path, timestamp_nanos: u128) -> PathBuf {
762 sibling_path(
763 active_path,
764 &format!(
765 "{}.{timestamp_nanos}.{AUDIT_LOG_ROTATED_COMPRESSED_EXTENSION}",
766 audit_log_file_name(active_path)
767 ),
768 )
769}
770
771pub fn parse_audit_log_rotated_timestamp(
772 active_path: &Path,
773 candidate_file_name: &str,
774) -> Option<u128> {
775 let active_name = audit_log_file_name(active_path);
776 let rotated = candidate_file_name.strip_prefix(&format!("{active_name}."))?;
777 let timestamp = rotated
778 .strip_suffix(&format!(".{AUDIT_LOG_ROTATED_COMPRESSED_EXTENSION}"))
779 .unwrap_or(rotated);
780 timestamp.parse::<u128>().ok()
781}
782
783pub fn legacy_slow_query_log_path(log_dir: &Path) -> PathBuf {
784 log_dir.join(LEGACY_SLOW_QUERY_LOG_FILE_NAME)
785}
786
787pub fn serverless_root(data_path: &Path) -> PathBuf {
788 data_path.with_extension(SERVERLESS_ROOT_EXTENSION)
789}
790
791pub fn serverless_namespace(data_path: &Path) -> String {
792 data_path
793 .file_stem()
794 .and_then(|stem| stem.to_str())
795 .filter(|stem| !stem.is_empty())
796 .unwrap_or("default")
797 .to_string()
798}
799
800pub fn serverless_cache_root(root: &Path, namespace: &str) -> PathBuf {
801 root.join(namespace).join(SERVERLESS_CACHE_DIR)
802}
803
804fn audit_log_file_name(path: &Path) -> String {
805 path.file_name()
806 .and_then(|name| name.to_str())
807 .unwrap_or(LEGACY_AUDIT_LOG_FILE_NAME)
808 .to_string()
809}
810
811fn push_parent(dirs: &mut Vec<PathBuf>, path: &Path) {
812 if let Some(parent) = path.parent() {
813 if !parent.as_os_str().is_empty() {
814 dirs.push(parent.to_path_buf());
815 }
816 }
817}
818
819fn push_optional(dirs: &mut Vec<PathBuf>, path: Option<&PathBuf>) {
820 if let Some(path) = path {
821 dirs.push(path.clone());
822 }
823}
824
825fn sanitize_export_name(name: &str) -> String {
826 let mut out = String::new();
827 for ch in name.chars() {
828 if ch.is_ascii_alphanumeric() || ch == '-' || ch == '_' {
829 out.push(ch);
830 } else {
831 out.push('_');
832 }
833 }
834 if out.is_empty() {
835 "export".to_string()
836 } else {
837 out
838 }
839}
840
841#[cfg(test)]
842mod tests {
843 use super::*;
844
845 #[test]
846 fn derives_standard_sidecars_next_to_database() {
847 let path = Path::new("/var/lib/reddb/main.rdb");
848
849 assert_eq!(
850 support_dir_for(path),
851 PathBuf::from("/var/lib/reddb/main.rdb.red")
852 );
853 assert_eq!(
854 unified_wal_path(path),
855 PathBuf::from("/var/lib/reddb/main.rdb-uwal")
856 );
857 assert_eq!(
858 store_commit_coord_temp_wal_path(Path::new("/tmp"), "burst", 7, 99),
859 PathBuf::from("/tmp/rb_commit_coord_burst_7_99.wal")
860 );
861 assert_eq!(
862 group_commit_temp_wal_path(Path::new("/tmp"), "batch", 7, 99),
863 PathBuf::from("/tmp/rb_group_commit_batch_7_99.wal")
864 );
865 assert_eq!(
866 wal_component_temp_path(Path::new("/tmp"), "writer", "create", 7),
867 PathBuf::from("/tmp/rb_wal_writer_create_7.wal")
868 );
869 assert_eq!(
870 wal_component_temp_path(Path::new("/tmp"), "reader", "empty", 7),
871 PathBuf::from("/tmp/rb_wal_reader_empty_7.wal")
872 );
873 assert_eq!(
874 wal_component_unique_temp_path(Path::new("/tmp"), "coord", "single", 7, 99),
875 PathBuf::from("/tmp/rb_wal_coord_single_7_99.wal")
876 );
877 assert_eq!(
878 backup_temp_json_path(
879 Path::new("/tmp"),
880 "reddb-archived-change-records",
881 7,
882 99,
883 3,
884 Some(10),
885 Some(20)
886 ),
887 PathBuf::from("/tmp/reddb-archived-change-records-7-10-20-99-3.json")
888 );
889 assert_eq!(
890 backup_temp_json_path(Path::new("/tmp"), "reddb-json-object", 7, 99, 3, None, None),
891 PathBuf::from("/tmp/reddb-json-object-7-99-3.json")
892 );
893 assert_eq!(
894 logical_wal_path(path),
895 PathBuf::from("/var/lib/reddb/main.rdb.logical.wal")
896 );
897 assert_eq!(
898 logical_wal_temp_path(&logical_wal_path(path)),
899 PathBuf::from("/var/lib/reddb/main.rdb.logical.logical.wal.tmp")
900 );
901 assert_eq!(
902 temp_path(path),
903 PathBuf::from("/var/lib/reddb/main.rdb-tmp")
904 );
905 assert_eq!(
906 atomic_temp_path(&pager_dwb_path(path)),
907 PathBuf::from("/var/lib/reddb/main.tmp")
908 );
909 assert_eq!(
910 result_cache_l2_path(path),
911 PathBuf::from("/var/lib/reddb/main.result-cache.l2")
912 );
913 assert_eq!(
914 engine_wal_path(path),
915 PathBuf::from("/var/lib/reddb/main.rdb-wal")
916 );
917 assert_eq!(
918 pager_legacy_wal_path(path),
919 PathBuf::from("/var/lib/reddb/main.wal")
920 );
921 assert_eq!(
922 pager_dwb_shadow_path(path),
923 PathBuf::from("/var/lib/reddb/main.rdb-dwb")
924 );
925 assert_eq!(shm_path(path), PathBuf::from("/var/lib/reddb/main.rdb-shm"));
926 assert_eq!(
927 physical_metadata_json_path(path),
928 PathBuf::from("/var/lib/reddb/main.rdb.meta.json")
929 );
930 assert_eq!(
931 physical_metadata_binary_path(path),
932 PathBuf::from("/var/lib/reddb/main.rdb.meta.rdbx")
933 );
934 assert_eq!(
935 physical_metadata_journal_path(path, 7),
936 PathBuf::from("/var/lib/reddb/main.rdb.meta.rdbx.seq-00000000000000000007")
937 );
938 assert_eq!(
939 physical_metadata_journal_prefix(path),
940 "main.rdb.meta.rdbx.seq-"
941 );
942 assert_eq!(
943 physical_export_data_path(path, "nightly backup"),
944 PathBuf::from("/var/lib/reddb/main.export.nightly_backup.rdb")
945 );
946 assert_eq!(
947 local_cas_lock_path(path),
948 PathBuf::from("/var/lib/reddb/.main.rdb.cas.lock")
949 );
950 assert_eq!(
951 local_upload_temp_path(path, 123, 7),
952 PathBuf::from("/var/lib/reddb/.main.rdb.tmp-123-7")
953 );
954 assert_eq!(
955 rebootstrap_staging_root(path),
956 PathBuf::from("/var/lib/reddb/main.rebootstrap.redbase")
957 );
958 assert_eq!(
959 rebootstrap_pending_path(path),
960 PathBuf::from("/var/lib/reddb/main.rebootstrap.pending.rdb")
961 );
962 assert_eq!(
963 rebootstrap_ready_marker_path(path),
964 PathBuf::from("/var/lib/reddb/main.rebootstrap.ready")
965 );
966 assert_eq!(
967 rebootstrap_intent_log_path(path),
968 PathBuf::from("/var/lib/reddb/main.rebootstrap.intent.jsonl")
969 );
970 assert_eq!(
971 rebootstrap_previous_path(path),
972 PathBuf::from("/var/lib/reddb/main.rebootstrap.previous.rdb")
973 );
974 assert_eq!(
975 primary_replica_root(path),
976 PathBuf::from("/var/lib/reddb/main.primary-replica")
977 );
978 assert_eq!(
979 legacy_logical_slots_path(path),
980 PathBuf::from("/var/lib/reddb/main.rdb.logical.slots.json")
981 );
982 assert_eq!(
983 legacy_logical_slots_temp_path(&legacy_logical_slots_path(path)),
984 PathBuf::from("/var/lib/reddb/main.rdb.logical.slots.logical.slots.tmp")
985 );
986 assert_eq!(
987 legacy_audit_log_path(path),
988 PathBuf::from("/var/lib/reddb/.audit.log")
989 );
990 assert_eq!(
991 audit_log_rotated_plain_path(&legacy_audit_log_path(path), 42),
992 PathBuf::from("/var/lib/reddb/.audit.log.42")
993 );
994 assert_eq!(
995 audit_log_rotated_compressed_path(&legacy_audit_log_path(path), 42),
996 PathBuf::from("/var/lib/reddb/.audit.log.42.zst")
997 );
998 assert_eq!(
999 parse_audit_log_rotated_timestamp(&legacy_audit_log_path(path), ".audit.log.42"),
1000 Some(42)
1001 );
1002 assert_eq!(
1003 parse_audit_log_rotated_timestamp(&legacy_audit_log_path(path), ".audit.log.42.zst"),
1004 Some(42)
1005 );
1006 assert_eq!(
1007 parse_audit_log_rotated_timestamp(&legacy_audit_log_path(path), "other.42.zst"),
1008 None
1009 );
1010 assert_eq!(
1011 legacy_slow_query_log_path(Path::new("/var/log/reddb")),
1012 PathBuf::from("/var/log/reddb/red-slow.log")
1013 );
1014 assert_eq!(
1015 serverless_root(path),
1016 PathBuf::from("/var/lib/reddb/main.serverless")
1017 );
1018 assert_eq!(serverless_namespace(path), "main");
1019 assert_eq!(
1020 serverless_cache_root(&serverless_root(path), &serverless_namespace(path)),
1021 PathBuf::from("/var/lib/reddb/main.serverless/main/cache")
1022 );
1023 }
1024
1025 #[test]
1026 fn derives_dedicated_support_sidecars() {
1027 let path = Path::new("/var/lib/reddb/main.rdb");
1028 let support = support_dir_for(path);
1029
1030 assert_eq!(
1031 unified_wal_path_in(&support, path),
1032 PathBuf::from("/var/lib/reddb/main.rdb.red/wal/main.rdb-uwal")
1033 );
1034 assert_eq!(
1035 logical_wal_path_in(&support, path),
1036 PathBuf::from("/var/lib/reddb/main.rdb.red/wal/main.rdb.logical.wal")
1037 );
1038 assert_eq!(
1039 temp_path_in(&support, path),
1040 PathBuf::from("/var/lib/reddb/main.rdb.red/tmp/main.rdb-tmp")
1041 );
1042 }
1043
1044 #[test]
1045 fn derives_primary_replica_segment_names() {
1046 assert_eq!(
1047 primary_wal_segment_file_name(2),
1048 "00000000000000000002.redwal"
1049 );
1050 assert_eq!(
1051 relay_segment_relative_path(10, 20),
1052 PathBuf::from("relay-00000000000000000010-00000000000000000020.redwal")
1053 );
1054 }
1055
1056 #[test]
1057 fn the_live_pager_sidecar_set_is_empty_after_phase3() {
1058 let path = Path::new("/var/lib/reddb/main.rdb");
1059 let expected: [PathBuf; 0] = [];
1060 assert_eq!(pager_shadow_sidecar_paths(path), expected);
1061 }
1062
1063 #[test]
1064 fn retired_phase1_and_phase3_names_stay_reachable_only_for_migration_and_refusal() {
1065 let path = Path::new("/var/lib/reddb/main.rdb");
1066 assert_eq!(
1069 retired::phase1_sidecar_paths(path),
1070 [
1071 PathBuf::from("/var/lib/reddb/main.rdb-hdr"),
1072 PathBuf::from("/var/lib/reddb/main.rdb-meta"),
1073 PathBuf::from("/var/lib/reddb/main.rdb-hdr"),
1074 PathBuf::from("/var/lib/reddb/main.rdb-meta"),
1075 ]
1076 );
1077 assert_eq!(
1078 retired::phase3_dwb_sidecar_paths(path),
1079 [
1080 PathBuf::from("/var/lib/reddb/main.rdb-dwb"),
1081 PathBuf::from("/var/lib/reddb/main.rdb-dwb"),
1082 ]
1083 );
1084 let l2 = Path::new("/var/lib/reddb/main.result-cache.l2");
1087 assert_eq!(
1088 retired::pager_header_path_v0(l2),
1089 PathBuf::from("/var/lib/reddb/main.result-cache.rdb-hdr")
1090 );
1091 assert_eq!(
1092 retired::pager_header_shadow_path_v0(l2),
1093 PathBuf::from("/var/lib/reddb/main.result-cache.l2-hdr")
1094 );
1095 }
1096}