1use std::format;
2
3use crate::errors::InvalidChunkFileName;
4use crate::num;
5use crate::ChunkId;
6
7#[derive(Clone, Debug, Default)]
15pub struct Config {
16 pub dir: String,
18
19 pub log_cache_max_items: Option<usize>,
21
22 pub log_cache_capacity: Option<usize>,
24
25 pub read_buffer_size: Option<usize>,
27
28 pub chunk_max_records: Option<usize>,
30
31 pub chunk_max_size: Option<usize>,
33
34 pub truncate_incomplete_record: Option<bool>,
39}
40
41impl Config {
42 pub fn new(dir: impl ToString) -> Self {
45 Self {
46 dir: dir.to_string(),
47 ..Default::default()
48 }
49 }
50
51 pub fn new_full(
53 dir: impl ToString,
54 log_cache_max_items: Option<usize>,
55 log_cache_capacity: Option<usize>,
56 read_buffer_size: Option<usize>,
57 chunk_max_records: Option<usize>,
58 chunk_max_size: Option<usize>,
59 ) -> Self {
60 Self {
61 dir: dir.to_string(),
62 log_cache_max_items,
63 log_cache_capacity,
64 read_buffer_size,
65 chunk_max_records,
66 chunk_max_size,
67 truncate_incomplete_record: None,
68 }
69 }
70
71 pub fn log_cache_max_items(&self) -> usize {
73 self.log_cache_max_items.unwrap_or(100_000)
74 }
75
76 pub fn log_cache_capacity(&self) -> usize {
78 self.log_cache_capacity.unwrap_or(1024 * 1024 * 1024)
79 }
80
81 pub fn read_buffer_size(&self) -> usize {
83 self.read_buffer_size.unwrap_or(64 * 1024 * 1024)
84 }
85
86 pub fn chunk_max_records(&self) -> usize {
88 self.chunk_max_records.unwrap_or(1024 * 1024)
89 }
90
91 pub fn chunk_max_size(&self) -> usize {
93 self.chunk_max_size.unwrap_or(1024 * 1024 * 1024)
94 }
95
96 pub fn truncate_incomplete_record(&self) -> bool {
98 self.truncate_incomplete_record.unwrap_or(true)
99 }
100
101 pub fn chunk_path(&self, chunk_id: ChunkId) -> String {
103 let file_name = Self::chunk_file_name(chunk_id);
104 format!("{}/{}", self.dir, file_name)
105 }
106
107 pub(crate) fn chunk_file_name(chunk_id: ChunkId) -> String {
111 let file_name = num::format_pad_u64(*chunk_id);
112 format!("r-{}.wal", file_name)
113 }
114
115 pub(crate) fn parse_chunk_file_name(
125 file_name: &str,
126 ) -> Result<u64, InvalidChunkFileName> {
127 let without_suffix =
129 file_name.strip_suffix(".wal").ok_or_else(|| {
130 InvalidChunkFileName::new(file_name, "has no '.wal' suffix")
131 })?;
132
133 let without_prefix =
135 without_suffix.strip_prefix("r-").ok_or_else(|| {
136 InvalidChunkFileName::new(file_name, "has no 'r-' prefix")
137 })?;
138
139 if without_prefix.len() != 26 {
140 return Err(InvalidChunkFileName::new(
141 file_name,
142 "does not have 26 digit after 'r-' prefix",
143 ));
144 }
145
146 let digits = without_prefix
147 .chars()
148 .filter(|c| c.is_ascii_digit())
149 .collect::<String>();
150
151 digits.parse::<u64>().map_err(|e| {
153 InvalidChunkFileName::new(
154 file_name,
155 format!("cannot parse as u64: {}", e),
156 )
157 })
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::Config;
164
165 #[test]
166 fn test_parse_chunk_file_name() {
167 assert_eq!(
168 Config::parse_chunk_file_name("r-10_100_000_000_001_200_000.wal"),
169 Ok(10_100_000_000_001_200_000)
170 );
171
172 assert!(Config::parse_chunk_file_name(
173 "r-10_100_000_000_001_200_000_1.wal"
174 )
175 .is_err());
176 assert!(Config::parse_chunk_file_name("r-1000000000.wal").is_err());
177 assert!(Config::parse_chunk_file_name(
178 "r-10_100_000_000_001_200_000.wall"
179 )
180 .is_err());
181 assert!(Config::parse_chunk_file_name(
182 "rrr-10_100_000_000_001_200_000.wal"
183 )
184 .is_err());
185 }
186}