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 start_lsn: Option<u64>,
470 end_lsn: Option<u64>,
471) -> PathBuf {
472 temp_dir.join(backup_temp_json_file_name(
473 prefix, process_id, nanos, start_lsn, end_lsn,
474 ))
475}
476
477pub fn backup_temp_json_file_name(
478 prefix: &str,
479 process_id: u32,
480 nanos: u128,
481 start_lsn: Option<u64>,
482 end_lsn: Option<u64>,
483) -> String {
484 match (start_lsn, end_lsn) {
485 (Some(start_lsn), Some(end_lsn)) => {
486 format!("{prefix}-{process_id}-{start_lsn}-{end_lsn}-{nanos}.json")
487 }
488 _ => format!("{prefix}-{process_id}-{nanos}.json"),
489 }
490}
491
492pub fn logical_wal_path(data_path: &Path) -> PathBuf {
493 sibling_path(
494 data_path,
495 &format!("{}.{}", data_file_name(data_path), LOGICAL_WAL_SUFFIX),
496 )
497}
498
499pub fn logical_wal_temp_path(logical_wal_path: &Path) -> PathBuf {
500 logical_wal_path.with_extension("logical.wal.tmp")
501}
502
503pub fn logical_wal_path_in(support_dir: &Path, data_path: &Path) -> PathBuf {
504 support_dir.join("wal").join(format!(
505 "{}.{}",
506 data_file_name(data_path),
507 LOGICAL_WAL_SUFFIX
508 ))
509}
510
511pub fn temp_path(data_path: &Path) -> PathBuf {
512 data_path.with_extension(TEMP_EXTENSION)
513}
514
515pub fn atomic_temp_path(path: &Path) -> PathBuf {
516 path.with_extension(ATOMIC_TEMP_EXTENSION)
517}
518
519pub fn result_cache_l2_path(data_path: &Path) -> PathBuf {
520 data_path.with_extension(RESULT_CACHE_L2_EXTENSION)
521}
522
523pub fn temp_path_in(support_dir: &Path, data_path: &Path) -> PathBuf {
524 support_dir
525 .join("tmp")
526 .join(sidecar_file_name(data_path, TEMP_EXTENSION))
527}
528
529pub fn primary_wal_segment_file_name(segment_index: u64) -> String {
530 format!("{segment_index:020}.{PRIMARY_WAL_EXTENSION}")
531}
532
533pub fn relay_segment_relative_path(start_lsn: u64, end_lsn: u64) -> PathBuf {
534 PathBuf::from(format!(
535 "relay-{start_lsn:020}-{end_lsn:020}.{PRIMARY_WAL_EXTENSION}"
536 ))
537}
538
539pub fn pager_legacy_wal_path(data_path: &Path) -> PathBuf {
540 data_path.with_extension(PAGER_LEGACY_WAL_EXTENSION)
541}
542
543pub fn engine_wal_path(data_path: &Path) -> PathBuf {
544 data_path.with_extension(ENGINE_WAL_EXTENSION)
545}
546
547pub fn pager_header_path(data_path: &Path) -> PathBuf {
548 data_path.with_extension(PAGER_HEADER_EXTENSION)
549}
550
551pub fn pager_meta_path(data_path: &Path) -> PathBuf {
552 data_path.with_extension(PAGER_META_EXTENSION)
553}
554
555pub fn pager_dwb_path(data_path: &Path) -> PathBuf {
556 data_path.with_extension(PAGER_DWB_EXTENSION)
557}
558
559fn path_with_dash_suffix(data_path: &Path, suffix: &str) -> PathBuf {
560 let mut path = data_path.to_path_buf().into_os_string();
561 path.push("-");
562 path.push(suffix);
563 PathBuf::from(path)
564}
565
566pub fn pager_header_shadow_path(data_path: &Path) -> PathBuf {
567 path_with_dash_suffix(data_path, PAGER_HEADER_SHADOW_SUFFIX)
568}
569
570pub fn pager_meta_shadow_path(data_path: &Path) -> PathBuf {
571 path_with_dash_suffix(data_path, PAGER_META_SHADOW_SUFFIX)
572}
573
574pub fn pager_dwb_shadow_path(data_path: &Path) -> PathBuf {
575 path_with_dash_suffix(data_path, PAGER_DWB_SHADOW_SUFFIX)
576}
577
578pub fn pager_shadow_sidecar_paths(data_path: &Path) -> [PathBuf; 3] {
579 [
580 pager_header_shadow_path(data_path),
581 pager_meta_shadow_path(data_path),
582 pager_dwb_shadow_path(data_path),
583 ]
584}
585
586pub fn shm_path(data_path: &Path) -> PathBuf {
587 sibling_path(
588 data_path,
589 &format!("{}-{SHM_FILE_SUFFIX}", data_file_name(data_path)),
590 )
591}
592
593pub fn physical_metadata_json_path(data_path: &Path) -> PathBuf {
594 sibling_path(
595 data_path,
596 &format!(
597 "{}.{PHYSICAL_METADATA_JSON_SUFFIX}",
598 data_file_name(data_path)
599 ),
600 )
601}
602
603pub fn physical_metadata_binary_path(data_path: &Path) -> PathBuf {
604 sibling_path(
605 data_path,
606 &format!(
607 "{}.{PHYSICAL_METADATA_BINARY_EXTENSION}",
608 data_file_name(data_path)
609 ),
610 )
611}
612
613pub fn physical_metadata_journal_path(data_path: &Path, sequence: u64) -> PathBuf {
614 sibling_path(
615 data_path,
616 &format!(
617 "{}.{PHYSICAL_METADATA_BINARY_EXTENSION}.seq-{sequence:020}",
618 data_file_name(data_path)
619 ),
620 )
621}
622
623pub fn physical_metadata_journal_prefix(data_path: &Path) -> String {
624 format!(
625 "{}.{PHYSICAL_METADATA_BINARY_EXTENSION}.seq-",
626 data_file_name(data_path)
627 )
628}
629
630pub fn physical_export_data_path(data_path: &Path, name: &str) -> PathBuf {
631 let file_name = data_file_name(data_path);
632 let stem = file_name.strip_suffix(".rdb").unwrap_or(&file_name);
633 sibling_path(
634 data_path,
635 &format!("{stem}.export.{}.rdb", sanitize_export_name(name)),
636 )
637}
638
639pub fn local_cas_lock_path(dest: &Path) -> PathBuf {
640 let file_name = data_file_name(dest);
641 dest.with_file_name(format!(".{file_name}.{LOCAL_CAS_LOCK_SUFFIX}"))
642}
643
644pub fn local_upload_temp_path(dest: &Path, pid: u32, unique: u64) -> PathBuf {
645 let file_name = data_file_name(dest);
646 dest.with_file_name(format!(
647 ".{file_name}.{LOCAL_UPLOAD_TEMP_TAG}-{pid}-{unique}"
648 ))
649}
650
651pub fn rebootstrap_staging_root(data_path: &Path) -> PathBuf {
652 data_path.with_extension(REBOOTSTRAP_STAGING_EXTENSION)
653}
654
655pub fn rebootstrap_pending_path(data_path: &Path) -> PathBuf {
656 data_path.with_extension(REBOOTSTRAP_PENDING_EXTENSION)
657}
658
659pub fn rebootstrap_ready_marker_path(data_path: &Path) -> PathBuf {
660 data_path.with_extension(REBOOTSTRAP_READY_EXTENSION)
661}
662
663pub fn rebootstrap_intent_log_path(data_path: &Path) -> PathBuf {
664 data_path.with_extension(REBOOTSTRAP_INTENT_LOG_EXTENSION)
665}
666
667pub fn rebootstrap_previous_path(data_path: &Path) -> PathBuf {
668 data_path.with_extension(REBOOTSTRAP_PREVIOUS_EXTENSION)
669}
670
671pub fn primary_replica_root(data_path: &Path) -> PathBuf {
672 data_path.with_extension(PRIMARY_REPLICA_ROOT_EXTENSION)
673}
674
675pub fn legacy_logical_slots_path(data_path: &Path) -> PathBuf {
676 let file_name = data_path
677 .file_name()
678 .and_then(|name| name.to_str())
679 .unwrap_or("reddb.rdb");
680 sibling_path(
681 data_path,
682 &format!("{file_name}.{LEGACY_LOGICAL_SLOTS_SUFFIX}"),
683 )
684}
685
686pub fn legacy_logical_slots_temp_path(path: &Path) -> PathBuf {
687 path.with_extension(LEGACY_LOGICAL_SLOTS_TEMP_EXTENSION)
688}
689
690pub fn legacy_audit_log_path(data_path: &Path) -> PathBuf {
691 sibling_path(data_path, LEGACY_AUDIT_LOG_FILE_NAME)
692}
693
694pub fn audit_log_rotated_plain_path(active_path: &Path, timestamp_nanos: u128) -> PathBuf {
695 sibling_path(
696 active_path,
697 &format!("{}.{timestamp_nanos}", audit_log_file_name(active_path)),
698 )
699}
700
701pub fn audit_log_rotated_compressed_path(active_path: &Path, timestamp_nanos: u128) -> PathBuf {
702 sibling_path(
703 active_path,
704 &format!(
705 "{}.{timestamp_nanos}.{AUDIT_LOG_ROTATED_COMPRESSED_EXTENSION}",
706 audit_log_file_name(active_path)
707 ),
708 )
709}
710
711pub fn parse_audit_log_rotated_timestamp(
712 active_path: &Path,
713 candidate_file_name: &str,
714) -> Option<u128> {
715 let active_name = audit_log_file_name(active_path);
716 let rotated = candidate_file_name.strip_prefix(&format!("{active_name}."))?;
717 let timestamp = rotated
718 .strip_suffix(&format!(".{AUDIT_LOG_ROTATED_COMPRESSED_EXTENSION}"))
719 .unwrap_or(rotated);
720 timestamp.parse::<u128>().ok()
721}
722
723pub fn legacy_slow_query_log_path(log_dir: &Path) -> PathBuf {
724 log_dir.join(LEGACY_SLOW_QUERY_LOG_FILE_NAME)
725}
726
727pub fn serverless_root(data_path: &Path) -> PathBuf {
728 data_path.with_extension(SERVERLESS_ROOT_EXTENSION)
729}
730
731pub fn serverless_namespace(data_path: &Path) -> String {
732 data_path
733 .file_stem()
734 .and_then(|stem| stem.to_str())
735 .filter(|stem| !stem.is_empty())
736 .unwrap_or("default")
737 .to_string()
738}
739
740pub fn serverless_cache_root(root: &Path, namespace: &str) -> PathBuf {
741 root.join(namespace).join(SERVERLESS_CACHE_DIR)
742}
743
744fn audit_log_file_name(path: &Path) -> String {
745 path.file_name()
746 .and_then(|name| name.to_str())
747 .unwrap_or(LEGACY_AUDIT_LOG_FILE_NAME)
748 .to_string()
749}
750
751fn push_parent(dirs: &mut Vec<PathBuf>, path: &Path) {
752 if let Some(parent) = path.parent() {
753 if !parent.as_os_str().is_empty() {
754 dirs.push(parent.to_path_buf());
755 }
756 }
757}
758
759fn push_optional(dirs: &mut Vec<PathBuf>, path: Option<&PathBuf>) {
760 if let Some(path) = path {
761 dirs.push(path.clone());
762 }
763}
764
765fn sanitize_export_name(name: &str) -> String {
766 let mut out = String::new();
767 for ch in name.chars() {
768 if ch.is_ascii_alphanumeric() || ch == '-' || ch == '_' {
769 out.push(ch);
770 } else {
771 out.push('_');
772 }
773 }
774 if out.is_empty() {
775 "export".to_string()
776 } else {
777 out
778 }
779}
780
781#[cfg(test)]
782mod tests {
783 use super::*;
784
785 #[test]
786 fn derives_standard_sidecars_next_to_database() {
787 let path = Path::new("/var/lib/reddb/main.rdb");
788
789 assert_eq!(
790 support_dir_for(path),
791 PathBuf::from("/var/lib/reddb/main.rdb.red")
792 );
793 assert_eq!(
794 unified_wal_path(path),
795 PathBuf::from("/var/lib/reddb/main.rdb-uwal")
796 );
797 assert_eq!(
798 store_commit_coord_temp_wal_path(Path::new("/tmp"), "burst", 7, 99),
799 PathBuf::from("/tmp/rb_commit_coord_burst_7_99.wal")
800 );
801 assert_eq!(
802 group_commit_temp_wal_path(Path::new("/tmp"), "batch", 7, 99),
803 PathBuf::from("/tmp/rb_group_commit_batch_7_99.wal")
804 );
805 assert_eq!(
806 wal_component_temp_path(Path::new("/tmp"), "writer", "create", 7),
807 PathBuf::from("/tmp/rb_wal_writer_create_7.wal")
808 );
809 assert_eq!(
810 wal_component_temp_path(Path::new("/tmp"), "reader", "empty", 7),
811 PathBuf::from("/tmp/rb_wal_reader_empty_7.wal")
812 );
813 assert_eq!(
814 wal_component_unique_temp_path(Path::new("/tmp"), "coord", "single", 7, 99),
815 PathBuf::from("/tmp/rb_wal_coord_single_7_99.wal")
816 );
817 assert_eq!(
818 backup_temp_json_path(
819 Path::new("/tmp"),
820 "reddb-archived-change-records",
821 7,
822 99,
823 Some(10),
824 Some(20)
825 ),
826 PathBuf::from("/tmp/reddb-archived-change-records-7-10-20-99.json")
827 );
828 assert_eq!(
829 backup_temp_json_path(Path::new("/tmp"), "reddb-json-object", 7, 99, None, None),
830 PathBuf::from("/tmp/reddb-json-object-7-99.json")
831 );
832 assert_eq!(
833 logical_wal_path(path),
834 PathBuf::from("/var/lib/reddb/main.rdb.logical.wal")
835 );
836 assert_eq!(
837 logical_wal_temp_path(&logical_wal_path(path)),
838 PathBuf::from("/var/lib/reddb/main.rdb.logical.logical.wal.tmp")
839 );
840 assert_eq!(
841 temp_path(path),
842 PathBuf::from("/var/lib/reddb/main.rdb-tmp")
843 );
844 assert_eq!(
845 atomic_temp_path(&pager_meta_path(path)),
846 PathBuf::from("/var/lib/reddb/main.tmp")
847 );
848 assert_eq!(
849 result_cache_l2_path(path),
850 PathBuf::from("/var/lib/reddb/main.result-cache.l2")
851 );
852 assert_eq!(
853 engine_wal_path(path),
854 PathBuf::from("/var/lib/reddb/main.rdb-wal")
855 );
856 assert_eq!(
857 pager_legacy_wal_path(path),
858 PathBuf::from("/var/lib/reddb/main.wal")
859 );
860 assert_eq!(
861 pager_header_shadow_path(path),
862 PathBuf::from("/var/lib/reddb/main.rdb-hdr")
863 );
864 assert_eq!(
865 pager_meta_shadow_path(path),
866 PathBuf::from("/var/lib/reddb/main.rdb-meta")
867 );
868 assert_eq!(
869 pager_dwb_shadow_path(path),
870 PathBuf::from("/var/lib/reddb/main.rdb-dwb")
871 );
872 assert_eq!(shm_path(path), PathBuf::from("/var/lib/reddb/main.rdb-shm"));
873 assert_eq!(
874 physical_metadata_json_path(path),
875 PathBuf::from("/var/lib/reddb/main.rdb.meta.json")
876 );
877 assert_eq!(
878 physical_metadata_binary_path(path),
879 PathBuf::from("/var/lib/reddb/main.rdb.meta.rdbx")
880 );
881 assert_eq!(
882 physical_metadata_journal_path(path, 7),
883 PathBuf::from("/var/lib/reddb/main.rdb.meta.rdbx.seq-00000000000000000007")
884 );
885 assert_eq!(
886 physical_metadata_journal_prefix(path),
887 "main.rdb.meta.rdbx.seq-"
888 );
889 assert_eq!(
890 physical_export_data_path(path, "nightly backup"),
891 PathBuf::from("/var/lib/reddb/main.export.nightly_backup.rdb")
892 );
893 assert_eq!(
894 local_cas_lock_path(path),
895 PathBuf::from("/var/lib/reddb/.main.rdb.cas.lock")
896 );
897 assert_eq!(
898 local_upload_temp_path(path, 123, 7),
899 PathBuf::from("/var/lib/reddb/.main.rdb.tmp-123-7")
900 );
901 assert_eq!(
902 rebootstrap_staging_root(path),
903 PathBuf::from("/var/lib/reddb/main.rebootstrap.redbase")
904 );
905 assert_eq!(
906 rebootstrap_pending_path(path),
907 PathBuf::from("/var/lib/reddb/main.rebootstrap.pending.rdb")
908 );
909 assert_eq!(
910 rebootstrap_ready_marker_path(path),
911 PathBuf::from("/var/lib/reddb/main.rebootstrap.ready")
912 );
913 assert_eq!(
914 rebootstrap_intent_log_path(path),
915 PathBuf::from("/var/lib/reddb/main.rebootstrap.intent.jsonl")
916 );
917 assert_eq!(
918 rebootstrap_previous_path(path),
919 PathBuf::from("/var/lib/reddb/main.rebootstrap.previous.rdb")
920 );
921 assert_eq!(
922 primary_replica_root(path),
923 PathBuf::from("/var/lib/reddb/main.primary-replica")
924 );
925 assert_eq!(
926 legacy_logical_slots_path(path),
927 PathBuf::from("/var/lib/reddb/main.rdb.logical.slots.json")
928 );
929 assert_eq!(
930 legacy_logical_slots_temp_path(&legacy_logical_slots_path(path)),
931 PathBuf::from("/var/lib/reddb/main.rdb.logical.slots.logical.slots.tmp")
932 );
933 assert_eq!(
934 legacy_audit_log_path(path),
935 PathBuf::from("/var/lib/reddb/.audit.log")
936 );
937 assert_eq!(
938 audit_log_rotated_plain_path(&legacy_audit_log_path(path), 42),
939 PathBuf::from("/var/lib/reddb/.audit.log.42")
940 );
941 assert_eq!(
942 audit_log_rotated_compressed_path(&legacy_audit_log_path(path), 42),
943 PathBuf::from("/var/lib/reddb/.audit.log.42.zst")
944 );
945 assert_eq!(
946 parse_audit_log_rotated_timestamp(&legacy_audit_log_path(path), ".audit.log.42"),
947 Some(42)
948 );
949 assert_eq!(
950 parse_audit_log_rotated_timestamp(&legacy_audit_log_path(path), ".audit.log.42.zst"),
951 Some(42)
952 );
953 assert_eq!(
954 parse_audit_log_rotated_timestamp(&legacy_audit_log_path(path), "other.42.zst"),
955 None
956 );
957 assert_eq!(
958 legacy_slow_query_log_path(Path::new("/var/log/reddb")),
959 PathBuf::from("/var/log/reddb/red-slow.log")
960 );
961 assert_eq!(
962 serverless_root(path),
963 PathBuf::from("/var/lib/reddb/main.serverless")
964 );
965 assert_eq!(serverless_namespace(path), "main");
966 assert_eq!(
967 serverless_cache_root(&serverless_root(path), &serverless_namespace(path)),
968 PathBuf::from("/var/lib/reddb/main.serverless/main/cache")
969 );
970 }
971
972 #[test]
973 fn derives_dedicated_support_sidecars() {
974 let path = Path::new("/var/lib/reddb/main.rdb");
975 let support = support_dir_for(path);
976
977 assert_eq!(
978 unified_wal_path_in(&support, path),
979 PathBuf::from("/var/lib/reddb/main.rdb.red/wal/main.rdb-uwal")
980 );
981 assert_eq!(
982 logical_wal_path_in(&support, path),
983 PathBuf::from("/var/lib/reddb/main.rdb.red/wal/main.rdb.logical.wal")
984 );
985 assert_eq!(
986 temp_path_in(&support, path),
987 PathBuf::from("/var/lib/reddb/main.rdb.red/tmp/main.rdb-tmp")
988 );
989 }
990
991 #[test]
992 fn derives_primary_replica_segment_names() {
993 assert_eq!(
994 primary_wal_segment_file_name(2),
995 "00000000000000000002.redwal"
996 );
997 assert_eq!(
998 relay_segment_relative_path(10, 20),
999 PathBuf::from("relay-00000000000000000010-00000000000000000020.redwal")
1000 );
1001 }
1002}