appcore_sync_sqlite/
config.rs1use crate::{SqliteSyncError, SqliteSyncResult};
14use std::fmt;
15use std::path::{Path, PathBuf};
16
17const MIB: u64 = 1024 * 1024;
18
19#[derive(Clone, PartialEq, Eq)]
21pub struct SqliteSyncConfig {
22 pub(crate) path: PathBuf,
23 pub(crate) separate_outbox_path: Option<PathBuf>,
24 pub(crate) max_database_bytes: u64,
25 pub(crate) max_outbox_entries: usize,
26 pub(crate) max_checkpoints: usize,
27 pub(crate) max_outbox_record_bytes: usize,
28 pub(crate) max_read_records: usize,
29 pub(crate) max_read_bytes: usize,
30 pub(crate) max_tombstones: usize,
31 pub(crate) max_connections: usize,
32 pub(crate) busy_timeout_ms: u64,
33 pub(crate) backup_pages_per_step: i32,
34}
35
36impl SqliteSyncConfig {
37 pub fn new(path: impl Into<PathBuf>) -> Self {
39 Self {
40 path: path.into(),
41 separate_outbox_path: None,
42 max_database_bytes: 512 * MIB,
43 max_outbox_entries: 10_000,
44 max_checkpoints: 10_000,
45 max_outbox_record_bytes: 48 * MIB as usize,
46 max_read_records: 4_096,
47 max_read_bytes: 16 * MIB as usize,
48 max_tombstones: 100_000,
49 max_connections: 8,
50 busy_timeout_ms: 5_000,
51 backup_pages_per_step: 128,
52 }
53 }
54
55 pub fn with_separate_outbox_path(mut self, path: impl Into<PathBuf>) -> Self {
62 self.separate_outbox_path = Some(path.into());
63 self
64 }
65
66 pub fn with_max_database_bytes(mut self, value: u64) -> Self {
68 self.max_database_bytes = value;
69 self
70 }
71
72 pub fn with_max_outbox_entries(mut self, value: usize) -> Self {
74 self.max_outbox_entries = value;
75 self
76 }
77
78 pub fn with_max_checkpoints(mut self, value: usize) -> Self {
80 self.max_checkpoints = value;
81 self
82 }
83
84 pub fn with_max_outbox_record_bytes(mut self, value: usize) -> Self {
86 self.max_outbox_record_bytes = value;
87 self
88 }
89
90 pub fn with_max_read_records(mut self, value: usize) -> Self {
92 self.max_read_records = value;
93 self
94 }
95
96 pub fn with_max_read_bytes(mut self, value: usize) -> Self {
98 self.max_read_bytes = value;
99 self
100 }
101
102 pub fn with_max_tombstones(mut self, value: usize) -> Self {
104 self.max_tombstones = value;
105 self
106 }
107
108 pub fn with_max_connections(mut self, value: usize) -> Self {
110 self.max_connections = value;
111 self
112 }
113
114 pub fn with_busy_timeout_ms(mut self, value: u64) -> Self {
116 self.busy_timeout_ms = value;
117 self
118 }
119
120 pub fn with_backup_pages_per_step(mut self, value: i32) -> Self {
122 self.backup_pages_per_step = value;
123 self
124 }
125
126 pub fn path(&self) -> &Path {
128 &self.path
129 }
130
131 pub fn separate_outbox_path(&self) -> Option<&Path> {
133 self.separate_outbox_path.as_deref()
134 }
135
136 pub(crate) fn outbox_table(&self) -> &'static str {
137 if self.separate_outbox_path.is_some() {
138 "sync_queue.appcore_sync_outbox"
139 } else {
140 "main.appcore_sync_outbox"
141 }
142 }
143
144 pub(crate) fn outbox_database(&self) -> &'static str {
145 if self.separate_outbox_path.is_some() {
146 "sync_queue"
147 } else {
148 "main"
149 }
150 }
151
152 pub(crate) fn validate(&self) -> SqliteSyncResult<()> {
153 if self.path.as_os_str().is_empty() {
154 return Err(SqliteSyncError::InvalidConfiguration("empty path"));
155 }
156 if self
157 .separate_outbox_path
158 .as_ref()
159 .is_some_and(|path| path.as_os_str().is_empty())
160 {
161 return Err(SqliteSyncError::InvalidConfiguration("empty outbox path"));
162 }
163 if !(8 * MIB..=8 * 1024 * MIB).contains(&self.max_database_bytes) {
164 return Err(SqliteSyncError::InvalidConfiguration(
165 "database byte bound is outside 8 MiB..=8 GiB",
166 ));
167 }
168 if !(1..=100_000).contains(&self.max_outbox_entries) {
169 return Err(SqliteSyncError::InvalidConfiguration(
170 "outbox entry bound is outside 1..=100000",
171 ));
172 }
173 if !(1..=100_000).contains(&self.max_checkpoints) {
174 return Err(SqliteSyncError::InvalidConfiguration(
175 "checkpoint bound is outside 1..=100000",
176 ));
177 }
178 if !(MIB as usize..=48 * MIB as usize).contains(&self.max_outbox_record_bytes) {
179 return Err(SqliteSyncError::InvalidConfiguration(
180 "outbox record bound is outside 1 MiB..=48 MiB",
181 ));
182 }
183 if !(1..=10_000).contains(&self.max_read_records) {
184 return Err(SqliteSyncError::InvalidConfiguration(
185 "read record bound is outside 1..=10000",
186 ));
187 }
188 if !(MIB as usize..=64 * MIB as usize).contains(&self.max_read_bytes) {
189 return Err(SqliteSyncError::InvalidConfiguration(
190 "read byte bound is outside 1 MiB..=64 MiB",
191 ));
192 }
193 if !(1..=1_000_000).contains(&self.max_tombstones) {
194 return Err(SqliteSyncError::InvalidConfiguration(
195 "tombstone bound is outside 1..=1000000",
196 ));
197 }
198 if !(1..=32).contains(&self.max_connections) {
199 return Err(SqliteSyncError::InvalidConfiguration(
200 "connection bound is outside 1..=32",
201 ));
202 }
203 if !(1..=60_000).contains(&self.busy_timeout_ms) {
204 return Err(SqliteSyncError::InvalidConfiguration(
205 "busy timeout is outside 1..=60000 ms",
206 ));
207 }
208 if !(1..=4_096).contains(&self.backup_pages_per_step) {
209 return Err(SqliteSyncError::InvalidConfiguration(
210 "backup step is outside 1..=4096 pages",
211 ));
212 }
213 Ok(())
214 }
215}
216
217impl fmt::Debug for SqliteSyncConfig {
218 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
219 formatter
220 .debug_struct("SqliteSyncConfig")
221 .field("path_configured", &!self.path.as_os_str().is_empty())
222 .field("max_database_bytes", &self.max_database_bytes)
223 .field("max_outbox_entries", &self.max_outbox_entries)
224 .field("max_checkpoints", &self.max_checkpoints)
225 .field("max_outbox_record_bytes", &self.max_outbox_record_bytes)
226 .field("max_read_records", &self.max_read_records)
227 .field("max_read_bytes", &self.max_read_bytes)
228 .field("max_tombstones", &self.max_tombstones)
229 .field("max_connections", &self.max_connections)
230 .field("busy_timeout_ms", &self.busy_timeout_ms)
231 .field("backup_pages_per_step", &self.backup_pages_per_step)
232 .finish()
233 }
234}