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