1use std::{fmt, str::FromStr};
5
6use reifydb_value::value::{Value, duration::Duration, value_type::ValueType};
7
8use crate::common::CommitVersion;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum AcceptError {
12 TypeMismatch {
13 expected: Vec<ValueType>,
14 actual: ValueType,
15 },
16
17 InvalidValue(String),
18}
19
20impl fmt::Display for AcceptError {
21 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22 match self {
23 Self::TypeMismatch {
24 expected,
25 actual,
26 } => {
27 write!(f, "expected one of {:?}, got {:?}", expected, actual)
28 }
29 Self::InvalidValue(reason) => write!(f, "{reason}"),
30 }
31 }
32}
33
34#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
35pub enum ConfigKey {
36 OracleWindowSize,
37 OracleWaterMark,
38 QueryRowBatchSize,
39 RowTtlScanBatchSize,
40 RowTtlScanInterval,
41 OperatorTtlScanBatchSize,
42 OperatorTtlScanInterval,
43 VersionEpochSampleInterval,
44 HistoricalGcBatchSize,
45 HistoricalGcInterval,
46 CdcTtlDuration,
47 CdcTtlScanInterval,
48 CdcTtlScanBatchSize,
49 CdcTtlScanMaxBatchesPerTick,
50 CdcCompactInterval,
51 CdcCompactBlockSize,
52 CdcCompactSafetyLag,
53 CdcCompactMaxBlocksPerTick,
54 CdcCompactBlockCacheCapacity,
55 CdcCompactZstdLevel,
56 CdcRecentCacheCapacity,
57 CdcWalAutocheckpoint,
58 MultiReadBufferPages,
59 MultiReadBufferPageSize,
60 MultiFlushInterval,
61 MultiWalAutocheckpoint,
62 FlowTick,
63 CdcWatermarkWaitTimeout,
64 CdcConsumeWaitTimeout,
65 FlowJoinProbeBlockSize,
66 ThreadsAsync,
67 ThreadsCoordination,
68 ThreadsFlow,
69 ThreadsTask,
70 ThreadsCompute,
71 SubscriptionWorkerThreads,
72 RuntimeMetricsInterval,
73 MetricFlushInterval,
74 MetricsRuntimeRetention,
75 MetricsProfilerRetention,
76 MetricsProfilerSnapshotInterval,
77}
78
79impl ConfigKey {
80 pub fn all() -> &'static [Self] {
81 &[
82 Self::OracleWindowSize,
83 Self::OracleWaterMark,
84 Self::QueryRowBatchSize,
85 Self::RowTtlScanBatchSize,
86 Self::RowTtlScanInterval,
87 Self::OperatorTtlScanBatchSize,
88 Self::OperatorTtlScanInterval,
89 Self::VersionEpochSampleInterval,
90 Self::HistoricalGcBatchSize,
91 Self::HistoricalGcInterval,
92 Self::CdcTtlDuration,
93 Self::CdcTtlScanInterval,
94 Self::CdcTtlScanBatchSize,
95 Self::CdcTtlScanMaxBatchesPerTick,
96 Self::CdcCompactInterval,
97 Self::CdcCompactBlockSize,
98 Self::CdcCompactSafetyLag,
99 Self::CdcCompactMaxBlocksPerTick,
100 Self::CdcCompactBlockCacheCapacity,
101 Self::CdcCompactZstdLevel,
102 Self::CdcRecentCacheCapacity,
103 Self::CdcWalAutocheckpoint,
104 Self::MultiReadBufferPages,
105 Self::MultiReadBufferPageSize,
106 Self::MultiFlushInterval,
107 Self::MultiWalAutocheckpoint,
108 Self::FlowTick,
109 Self::CdcWatermarkWaitTimeout,
110 Self::CdcConsumeWaitTimeout,
111 Self::FlowJoinProbeBlockSize,
112 Self::ThreadsAsync,
113 Self::ThreadsCoordination,
114 Self::ThreadsFlow,
115 Self::ThreadsTask,
116 Self::ThreadsCompute,
117 Self::SubscriptionWorkerThreads,
118 Self::RuntimeMetricsInterval,
119 Self::MetricFlushInterval,
120 Self::MetricsRuntimeRetention,
121 Self::MetricsProfilerRetention,
122 Self::MetricsProfilerSnapshotInterval,
123 ]
124 }
125
126 pub fn default_value(&self) -> Value {
127 match self {
128 Self::OracleWindowSize => Value::Uint8(500),
129 Self::OracleWaterMark => Value::Uint8(20),
130 Self::QueryRowBatchSize => Value::Uint2(32),
131 Self::RowTtlScanBatchSize => Value::Uint8(10000),
132 Self::RowTtlScanInterval => Value::duration_seconds(60),
133 Self::OperatorTtlScanBatchSize => Value::Uint8(10000),
134 Self::OperatorTtlScanInterval => Value::duration_seconds(60),
135 Self::VersionEpochSampleInterval => Value::duration_seconds(1),
136 Self::HistoricalGcBatchSize => Value::Uint8(50_000),
137 Self::HistoricalGcInterval => Value::duration_seconds(30),
138 Self::CdcTtlDuration => Value::None {
139 inner: ValueType::Duration,
140 },
141 Self::CdcTtlScanInterval => Value::duration_seconds(30),
142 Self::CdcTtlScanBatchSize => Value::Uint8(8192),
143 Self::CdcTtlScanMaxBatchesPerTick => Value::Uint8(32),
144 Self::CdcCompactInterval => Value::duration_seconds(60),
145 Self::CdcCompactBlockSize => Value::Uint8(1024),
146 Self::CdcCompactSafetyLag => Value::Uint8(1024),
147 Self::CdcCompactMaxBlocksPerTick => Value::Uint8(16),
148 Self::CdcCompactBlockCacheCapacity => Value::Uint8(8),
149 Self::CdcCompactZstdLevel => Value::Uint1(2),
150 Self::CdcRecentCacheCapacity => Value::Uint8(128),
151 Self::CdcWalAutocheckpoint => Value::Uint8(10000),
152 Self::MultiReadBufferPages => Value::Uint8(1024),
153 Self::MultiReadBufferPageSize => Value::Uint8(65536),
154 Self::MultiFlushInterval => Value::duration_seconds(5),
155 Self::MultiWalAutocheckpoint => Value::Uint8(10000),
156 Self::FlowTick => Value::duration_seconds(1),
157 Self::CdcWatermarkWaitTimeout => Value::duration_seconds(1),
158 Self::CdcConsumeWaitTimeout => Value::duration_seconds(30),
159 Self::FlowJoinProbeBlockSize => Value::Uint8(1024),
160 Self::ThreadsAsync => Value::Uint2(1),
161 Self::ThreadsCoordination => Value::Uint2(2),
162 Self::ThreadsFlow => Value::Uint2(2),
163 Self::ThreadsTask => Value::Uint2(2),
164 Self::ThreadsCompute => Value::Uint2(2),
165 Self::SubscriptionWorkerThreads => Value::Uint2(0),
166 Self::RuntimeMetricsInterval => Value::duration_seconds(5),
167 Self::MetricFlushInterval => Value::duration_seconds(10),
168 Self::MetricsRuntimeRetention => Value::duration_seconds(7 * 24 * 3600),
169 Self::MetricsProfilerRetention => Value::duration_seconds(3600),
170 Self::MetricsProfilerSnapshotInterval => Value::None {
171 inner: ValueType::Duration,
172 },
173 }
174 }
175
176 pub fn description(&self) -> &'static str {
177 match self {
178 Self::OracleWindowSize => "Number of transactions per conflict-detection window.",
179 Self::OracleWaterMark => "Number of conflict windows retained before cleanup is triggered.",
180 Self::QueryRowBatchSize => {
181 "Number of rows produced per batch by query / DML pipeline operators."
182 }
183 Self::RowTtlScanBatchSize => "Max rows to examine per batch during a row TTL scan.",
184 Self::RowTtlScanInterval => "How often the row TTL actor should scan for expired rows.",
185 Self::OperatorTtlScanBatchSize => {
186 "Max rows to examine per batch during an operator-state TTL scan."
187 }
188 Self::OperatorTtlScanInterval => {
189 "How often the operator-state TTL actor should scan for expired rows."
190 }
191 Self::VersionEpochSampleInterval => {
192 "How often the version-epoch sampler records a (wall-clock, commit version) sample used to map a TTL duration to a cutoff version."
193 }
194 Self::HistoricalGcBatchSize => {
195 "Max historical (key, version) pairs scanned per shape per historical GC tick."
196 }
197 Self::HistoricalGcInterval => {
198 "How often the historical-version GC actor sweeps __historical for versions older than the oracle read watermark."
199 }
200 Self::CdcTtlDuration => {
201 "Maximum age of CDC entries before eviction. When unset, CDC is retained forever; \
202 when set, must be > 0 and entries older than this duration are evicted regardless \
203 of consumer state."
204 }
205 Self::CdcTtlScanInterval => {
206 "How often the CDC producer actor scans for and evicts expired CDC entries."
207 }
208 Self::CdcTtlScanBatchSize => {
209 "Max CDC entries deleted per transaction during a CDC TTL eviction tick."
210 }
211 Self::CdcTtlScanMaxBatchesPerTick => {
212 "Upper bound on delete transactions per CDC TTL eviction tick. Caps how long one tick can run when draining a backlog; remaining work continues on the next tick."
213 }
214 Self::CdcCompactInterval => "How often the CDC compaction actor runs.",
215 Self::CdcCompactBlockSize => "Number of CDC entries packed into one compressed block.",
216 Self::CdcCompactSafetyLag => "Versions newer than (max_version - lag) are never compacted.",
217 Self::CdcCompactMaxBlocksPerTick => {
218 "Upper bound on consecutive blocks produced per actor tick."
219 }
220 Self::CdcCompactBlockCacheCapacity => {
221 "Number of decompressed CDC blocks held in the in-memory LRU cache."
222 }
223 Self::CdcCompactZstdLevel => {
224 "Zstd compression level for CDC blocks. Range 1-22; higher means smaller blocks but \
225 slower compression. Decompression cost is independent of level."
226 }
227 Self::CdcRecentCacheCapacity => {
228 "Number of most-recent decoded CDC entries held in memory so a caught-up consumer \
229 is served without re-reading and re-deserializing from the backend."
230 }
231 Self::CdcWalAutocheckpoint => {
232 "WAL frame threshold (SQLite wal_autocheckpoint PRAGMA) for the CDC log's SQLite tier. \
233 CDC has no explicit checkpoint of its own, so this is the sole control over how often \
234 cdc.db's WAL is checkpointed into the main file. Higher values checkpoint less often with \
235 a larger WAL; since CDC is written on the commit path, this also bounds how often a commit \
236 pays an inline auto-checkpoint. Read once at boot; changing it requires a restart."
237 }
238 Self::MultiReadBufferPages => {
239 "Number of pages (contiguous row-number buckets) the multi-version read cache keeps \
240 resident before eviction. Raising it trades RAM for fewer persistent-tier reads."
241 }
242 Self::MultiReadBufferPageSize => {
243 "Number of rows per cached page (bucket) in the multi-version read cache. Must be a \
244 power of two; sets the granularity of whole-page read-ahead and completeness tracking."
245 }
246 Self::MultiFlushInterval => {
247 "How often the persistent-flush actor drains the in-memory commit buffer into the multi \
248 store's SQLite tier and checkpoints its WAL. Longer intervals coalesce more writes per \
249 flush - fewer, larger WAL checkpoints and a larger WAL - at the cost of more resident \
250 commit-buffer memory and a longer window before data is materialized in the persistent \
251 file. Read once at boot; changing it requires a restart."
252 }
253 Self::MultiWalAutocheckpoint => {
254 "WAL frame threshold for the multi store's SQLite tier: sets both the SQLite \
255 wal_autocheckpoint PRAGMA and the frame count above which the flush actor forces a \
256 blocking RESTART checkpoint. Higher values checkpoint less often with a larger WAL, \
257 reducing checkpoint I/O and blocking-checkpoint frequency; lower values keep the WAL \
258 small at the cost of more frequent checkpoints. Read once at boot; changing it requires \
259 a restart."
260 }
261 Self::FlowTick => {
262 "How often the deferred and transactional flow tick coordinators wake up to dispatch \
263 due flows."
264 }
265 Self::CdcWatermarkWaitTimeout => {
266 "Backstop timeout for the CDC consumer's wait for the transaction watermark to reach the \
267 latest commit before consuming; catch-up is event-driven, so this only bounds a missed \
268 wakeup. Must be > 0."
269 }
270 Self::CdcConsumeWaitTimeout => {
271 "Backstop timeout for the CDC consumer's wait for a consume reply from the downstream \
272 consumer. A lost reply would otherwise wedge the poll loop forever; on timeout the batch \
273 is re-dispatched without advancing the checkpoint. Must be > 0."
274 }
275 Self::FlowJoinProbeBlockSize => {
276 "Number of opposite-side rows a streaming join pulls per block when probing its stored \
277 state. Bounds resident probe memory without dropping matches; smaller trades fewer \
278 resident rows for more scan round-trips."
279 }
280 Self::ThreadsAsync => {
281 "Number of worker threads for the async runtime. Must be >= 1. \
282 Read at boot before the runtime starts; changes require restart."
283 }
284 Self::ThreadsCoordination => {
285 "Number of worker threads for the coordination group (long-lived actors with \
286 tiny high-frequency handlers and periodic background actors); pinned dispatch. \
287 Must be >= 1. Changes require restart."
288 }
289 Self::ThreadsFlow => {
290 "Number of worker threads for the flow group (long-lived heavy-handler actors: \
291 materialized-view flow execution); pinned dispatch. \
292 Must be >= 1. Changes require restart."
293 }
294 Self::ThreadsTask => {
295 "Number of worker threads for the task pool (short-lived work: per-request \
296 actors and one-shot jobs). Must be >= 1. Changes require restart."
297 }
298 Self::ThreadsCompute => {
299 "Number of worker threads for the compute pool (data-parallel work via install(), \
300 never actors). Must be >= 1. Changes require restart."
301 }
302 Self::SubscriptionWorkerThreads => {
303 "Number of subscription worker actors that fan out CDC changes to ephemeral \
304 subscriptions in parallel. 0 means auto (size to the system thread pool). Higher values \
305 raise fan-out parallelism for many concurrent subscriptions. Changes require restart."
306 }
307 Self::RuntimeMetricsInterval => {
308 "How often the runtime-metrics sampler records a memory snapshot into \
309 system::metrics::runtime::memory::snapshots. When unset, the history sampler is \
310 dormant and only the live ::current view is available; when set, must be > 0."
311 }
312 Self::MetricFlushInterval => {
313 "How often the metric collector flushes accumulated storage and CDC stats into the \
314 system::metrics views. Must be > 0."
315 }
316 Self::MetricsRuntimeRetention => {
317 "Row TTL applied to the system::metrics::runtime::* snapshot series so old samples are \
318 evicted. Seeded onto each runtime series at bootstrap only when it has no row settings \
319 yet; changing it affects series created after the change, not already-seeded ones. \
320 Must be > 0."
321 }
322 Self::MetricsProfilerRetention => {
323 "Row TTL applied to the system::metrics::profiler::*::snapshots series so old samples are \
324 evicted. Seeded onto each profiler series at bootstrap only when it has no row settings \
325 yet; changing it affects series created after the change, not already-seeded ones. \
326 Must be > 0."
327 }
328 Self::MetricsProfilerSnapshotInterval => {
329 "How often the profiler snapshot actor flushes in-memory aggregates into \
330 system::metrics::profiler::*::snapshots. Defaults to none, which disables snapshot \
331 persistence entirely (the actor is never spawned) and leaves only the live ::current \
332 view available; when set, must be > 0. Read once at subsystem construction, so \
333 changing it requires a restart."
334 }
335 }
336 }
337
338 pub fn requires_restart(&self) -> bool {
339 match self {
340 Self::OracleWindowSize => false,
341 Self::OracleWaterMark => false,
342 Self::QueryRowBatchSize => false,
343 Self::RowTtlScanBatchSize => false,
344 Self::RowTtlScanInterval => false,
345 Self::OperatorTtlScanBatchSize => false,
346 Self::OperatorTtlScanInterval => false,
347 Self::VersionEpochSampleInterval => false,
348 Self::HistoricalGcBatchSize => false,
349 Self::HistoricalGcInterval => false,
350 Self::CdcTtlDuration => false,
351 Self::CdcTtlScanInterval => true,
352 Self::CdcTtlScanBatchSize => false,
353 Self::CdcTtlScanMaxBatchesPerTick => false,
354 Self::CdcCompactInterval => false,
355 Self::CdcCompactBlockSize => false,
356 Self::CdcCompactSafetyLag => false,
357 Self::CdcCompactMaxBlocksPerTick => false,
358 Self::CdcCompactBlockCacheCapacity => true,
359 Self::CdcCompactZstdLevel => false,
360 Self::CdcRecentCacheCapacity => true,
361 Self::CdcWalAutocheckpoint => true,
362 Self::MultiReadBufferPages => true,
363 Self::MultiReadBufferPageSize => true,
364 Self::MultiFlushInterval => true,
365 Self::MultiWalAutocheckpoint => true,
366 Self::FlowTick => false,
367 Self::CdcWatermarkWaitTimeout => false,
368 Self::CdcConsumeWaitTimeout => false,
369 Self::FlowJoinProbeBlockSize => false,
370 Self::ThreadsAsync => true,
371 Self::ThreadsCoordination => true,
372 Self::ThreadsFlow => true,
373 Self::ThreadsTask => true,
374 Self::ThreadsCompute => true,
375 Self::SubscriptionWorkerThreads => true,
376 Self::RuntimeMetricsInterval => false,
377 Self::MetricFlushInterval => false,
378 Self::MetricsRuntimeRetention => true,
379 Self::MetricsProfilerRetention => true,
380 Self::MetricsProfilerSnapshotInterval => true,
381 }
382 }
383
384 pub fn expected_types(&self) -> &'static [ValueType] {
385 match self {
386 Self::OracleWindowSize => &[ValueType::Uint8],
387 Self::OracleWaterMark => &[ValueType::Uint8],
388 Self::QueryRowBatchSize => &[ValueType::Uint2],
389 Self::RowTtlScanBatchSize => &[ValueType::Uint8],
390 Self::RowTtlScanInterval => &[ValueType::Duration],
391 Self::OperatorTtlScanBatchSize => &[ValueType::Uint8],
392 Self::OperatorTtlScanInterval => &[ValueType::Duration],
393 Self::VersionEpochSampleInterval => &[ValueType::Duration],
394 Self::HistoricalGcBatchSize => &[ValueType::Uint8],
395 Self::HistoricalGcInterval => &[ValueType::Duration],
396 Self::CdcTtlDuration => &[ValueType::Duration],
397 Self::CdcTtlScanInterval => &[ValueType::Duration],
398 Self::CdcTtlScanBatchSize => &[ValueType::Uint8],
399 Self::CdcTtlScanMaxBatchesPerTick => &[ValueType::Uint8],
400 Self::CdcCompactInterval => &[ValueType::Duration],
401 Self::CdcCompactBlockSize => &[ValueType::Uint8],
402 Self::CdcCompactSafetyLag => &[ValueType::Uint8],
403 Self::CdcCompactMaxBlocksPerTick => &[ValueType::Uint8],
404 Self::CdcCompactBlockCacheCapacity => &[ValueType::Uint8],
405 Self::CdcCompactZstdLevel => &[ValueType::Uint1],
406 Self::CdcRecentCacheCapacity => &[ValueType::Uint8],
407 Self::CdcWalAutocheckpoint => &[ValueType::Uint8],
408 Self::MultiReadBufferPages => &[ValueType::Uint8],
409 Self::MultiReadBufferPageSize => &[ValueType::Uint8],
410 Self::MultiFlushInterval => &[ValueType::Duration],
411 Self::MultiWalAutocheckpoint => &[ValueType::Uint8],
412 Self::FlowTick => &[ValueType::Duration],
413 Self::CdcWatermarkWaitTimeout => &[ValueType::Duration],
414 Self::CdcConsumeWaitTimeout => &[ValueType::Duration],
415 Self::FlowJoinProbeBlockSize => &[ValueType::Uint8],
416 Self::ThreadsAsync => &[ValueType::Uint2],
417 Self::ThreadsCoordination => &[ValueType::Uint2],
418 Self::ThreadsFlow => &[ValueType::Uint2],
419 Self::ThreadsTask => &[ValueType::Uint2],
420 Self::ThreadsCompute => &[ValueType::Uint2],
421 Self::SubscriptionWorkerThreads => &[ValueType::Uint2],
422 Self::RuntimeMetricsInterval => &[ValueType::Duration],
423 Self::MetricFlushInterval => &[ValueType::Duration],
424 Self::MetricsRuntimeRetention => &[ValueType::Duration],
425 Self::MetricsProfilerRetention => &[ValueType::Duration],
426 Self::MetricsProfilerSnapshotInterval => &[ValueType::Duration],
427 }
428 }
429
430 pub fn is_optional(&self) -> bool {
431 match self {
432 Self::OracleWindowSize => false,
433 Self::OracleWaterMark => false,
434 Self::QueryRowBatchSize => false,
435 Self::RowTtlScanBatchSize => false,
436 Self::RowTtlScanInterval => false,
437 Self::OperatorTtlScanBatchSize => false,
438 Self::OperatorTtlScanInterval => false,
439 Self::VersionEpochSampleInterval => false,
440 Self::HistoricalGcBatchSize => false,
441 Self::HistoricalGcInterval => false,
442 Self::CdcTtlDuration => true,
443 Self::CdcTtlScanInterval => false,
444 Self::CdcTtlScanBatchSize => false,
445 Self::CdcTtlScanMaxBatchesPerTick => false,
446 Self::CdcCompactInterval => false,
447 Self::CdcCompactBlockSize => false,
448 Self::CdcCompactSafetyLag => false,
449 Self::CdcCompactMaxBlocksPerTick => false,
450 Self::CdcCompactBlockCacheCapacity => false,
451 Self::CdcCompactZstdLevel => false,
452 Self::CdcRecentCacheCapacity => false,
453 Self::CdcWalAutocheckpoint => false,
454 Self::MultiReadBufferPages => false,
455 Self::MultiReadBufferPageSize => false,
456 Self::MultiFlushInterval => false,
457 Self::MultiWalAutocheckpoint => false,
458 Self::FlowTick => false,
459 Self::CdcWatermarkWaitTimeout => false,
460 Self::CdcConsumeWaitTimeout => false,
461 Self::FlowJoinProbeBlockSize => false,
462 Self::ThreadsAsync => false,
463 Self::ThreadsCoordination => false,
464 Self::ThreadsFlow => false,
465 Self::ThreadsTask => false,
466 Self::ThreadsCompute => false,
467 Self::SubscriptionWorkerThreads => false,
468 Self::RuntimeMetricsInterval => true,
469 Self::MetricFlushInterval => false,
470 Self::MetricsRuntimeRetention => false,
471 Self::MetricsProfilerRetention => false,
472 Self::MetricsProfilerSnapshotInterval => true,
473 }
474 }
475
476 fn validate_canonical(&self, value: &Value) -> Result<(), String> {
477 match self {
478 Self::CdcTtlDuration => match value {
479 Value::None {
480 ..
481 } => Ok(()),
482 Value::Duration(d) => {
483 if d.is_positive() {
484 Ok(())
485 } else {
486 Err("CDC_TTL_DURATION must be greater than zero".to_string())
487 }
488 }
489 _ => Ok(()),
490 },
491 Self::CdcCompactInterval => match value {
492 Value::Duration(d) => {
493 if d.is_positive() {
494 Ok(())
495 } else {
496 Err("CDC_COMPACT_INTERVAL must be greater than zero".to_string())
497 }
498 }
499 _ => Ok(()),
500 },
501 Self::CdcCompactBlockSize => match value {
502 Value::Uint8(0) => Err("CDC_COMPACT_BLOCK_SIZE must be greater than zero".to_string()),
503 _ => Ok(()),
504 },
505 Self::QueryRowBatchSize => match value {
506 Value::Uint2(0) => Err("QUERY_ROW_BATCH_SIZE must be greater than zero".to_string()),
507 _ => Ok(()),
508 },
509 Self::CdcCompactBlockCacheCapacity => match value {
510 Value::Uint8(0) => {
511 Err("CDC_COMPACT_BLOCK_CACHE_CAPACITY must be greater than zero".to_string())
512 }
513 _ => Ok(()),
514 },
515 Self::MultiReadBufferPages => match value {
516 Value::Uint8(0) => Err("MULTI_READ_BUFFER_PAGES must be greater than zero".to_string()),
517 _ => Ok(()),
518 },
519 Self::MultiReadBufferPageSize => match value {
520 Value::Uint8(v) if v.is_power_of_two() => Ok(()),
521 Value::Uint8(_) => {
522 Err("MULTI_READ_BUFFER_PAGE_SIZE must be a power of two".to_string())
523 }
524 _ => Ok(()),
525 },
526 Self::MultiFlushInterval => match value {
527 Value::Duration(d) if d.is_positive() => Ok(()),
528 Value::Duration(_) => Err("MULTI_FLUSH_INTERVAL must be greater than zero".to_string()),
529 _ => Ok(()),
530 },
531 Self::MultiWalAutocheckpoint => match value {
532 Value::Uint8(0) => {
533 Err("MULTI_WAL_AUTOCHECKPOINT must be greater than zero".to_string())
534 }
535 _ => Ok(()),
536 },
537 Self::CdcWalAutocheckpoint => match value {
538 Value::Uint8(0) => Err("CDC_WAL_AUTOCHECKPOINT must be greater than zero".to_string()),
539 _ => Ok(()),
540 },
541 Self::CdcCompactZstdLevel => match value {
542 Value::Uint1(v) if (1..=22).contains(v) => Ok(()),
543 Value::Uint1(_) => Err("CDC_COMPACT_ZSTD_LEVEL must be in [1, 22]".to_string()),
544 _ => Ok(()),
545 },
546 Self::HistoricalGcBatchSize => match value {
547 Value::Uint8(0) => {
548 Err("HISTORICAL_GC_BATCH_SIZE must be greater than zero".to_string())
549 }
550 _ => Ok(()),
551 },
552 Self::HistoricalGcInterval => match value {
553 Value::Duration(d) => {
554 if d.is_positive() {
555 Ok(())
556 } else {
557 Err("HISTORICAL_GC_INTERVAL must be greater than zero".to_string())
558 }
559 }
560 _ => Ok(()),
561 },
562 Self::FlowTick => match value {
563 Value::Duration(d) => {
564 if d.is_positive() {
565 Ok(())
566 } else {
567 Err("FLOW_TICK must be greater than zero".to_string())
568 }
569 }
570 _ => Ok(()),
571 },
572 Self::CdcWatermarkWaitTimeout => match value {
573 Value::Duration(d) => {
574 if d.is_positive() {
575 Ok(())
576 } else {
577 Err("CDC_WATERMARK_WAIT_TIMEOUT must be greater than zero".to_string())
578 }
579 }
580 _ => Ok(()),
581 },
582 Self::CdcConsumeWaitTimeout => match value {
583 Value::Duration(d) => {
584 if d.is_positive() {
585 Ok(())
586 } else {
587 Err("CDC_CONSUME_WAIT_TIMEOUT must be greater than zero".to_string())
588 }
589 }
590 _ => Ok(()),
591 },
592 Self::FlowJoinProbeBlockSize => match value {
593 Value::Uint8(0) => {
594 Err("FLOW_JOIN_PROBE_BLOCK_SIZE must be greater than zero".to_string())
595 }
596 _ => Ok(()),
597 },
598 Self::ThreadsAsync => match value {
599 Value::Uint2(0) => Err("THREADS_ASYNC must be greater than zero".to_string()),
600 _ => Ok(()),
601 },
602 Self::ThreadsCoordination => match value {
603 Value::Uint2(0) => Err("THREADS_COORDINATION must be greater than zero".to_string()),
604 _ => Ok(()),
605 },
606 Self::ThreadsFlow => match value {
607 Value::Uint2(0) => Err("THREADS_FLOW must be greater than zero".to_string()),
608 _ => Ok(()),
609 },
610 Self::ThreadsTask => match value {
611 Value::Uint2(0) => Err("THREADS_TASK must be greater than zero".to_string()),
612 _ => Ok(()),
613 },
614 Self::ThreadsCompute => match value {
615 Value::Uint2(0) => Err("THREADS_COMPUTE must be greater than zero".to_string()),
616 _ => Ok(()),
617 },
618 Self::SubscriptionWorkerThreads => Ok(()),
619 Self::RuntimeMetricsInterval => match value {
620 Value::None {
621 ..
622 } => Ok(()),
623 Value::Duration(d) => {
624 if d.is_positive() {
625 Ok(())
626 } else {
627 Err("RUNTIME_METRICS_INTERVAL must be greater than zero".to_string())
628 }
629 }
630 _ => Ok(()),
631 },
632 Self::MetricFlushInterval => match value {
633 Value::Duration(d) => {
634 if d.is_positive() {
635 Ok(())
636 } else {
637 Err("METRIC_FLUSH_INTERVAL must be greater than zero".to_string())
638 }
639 }
640 _ => Ok(()),
641 },
642 Self::MetricsRuntimeRetention => match value {
643 Value::Duration(d) => {
644 if d.is_positive() {
645 Ok(())
646 } else {
647 Err("METRICS_RUNTIME_RETENTION must be greater than zero".to_string())
648 }
649 }
650 _ => Ok(()),
651 },
652 Self::MetricsProfilerRetention => match value {
653 Value::Duration(d) => {
654 if d.is_positive() {
655 Ok(())
656 } else {
657 Err("METRICS_PROFILER_RETENTION must be greater than zero".to_string())
658 }
659 }
660 _ => Ok(()),
661 },
662 Self::MetricsProfilerSnapshotInterval => match value {
663 Value::None {
664 ..
665 } => Ok(()),
666 Value::Duration(d) => {
667 if d.is_positive() {
668 Ok(())
669 } else {
670 Err("METRICS_PROFILER_SNAPSHOT_INTERVAL must be greater than zero"
671 .to_string())
672 }
673 }
674 _ => Ok(()),
675 },
676 _ => Ok(()),
677 }
678 }
679
680 pub fn accept(&self, value: Value) -> Result<Value, AcceptError> {
681 if let Value::None {
682 inner,
683 } = &value
684 {
685 if self.is_optional() && self.expected_types().contains(inner) {
686 return Ok(value);
687 }
688 return Err(AcceptError::TypeMismatch {
689 expected: self.expected_types().to_vec(),
690 actual: value.get_type(),
691 });
692 }
693
694 if !self.expected_types().contains(&value.get_type()) {
695 return Err(AcceptError::TypeMismatch {
696 expected: self.expected_types().to_vec(),
697 actual: value.get_type(),
698 });
699 }
700
701 self.validate_canonical(&value).map_err(AcceptError::InvalidValue)?;
702 Ok(value)
703 }
704}
705
706impl fmt::Display for ConfigKey {
707 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
708 match self {
709 Self::OracleWindowSize => write!(f, "ORACLE_WINDOW_SIZE"),
710 Self::OracleWaterMark => write!(f, "ORACLE_WATER_MARK"),
711 Self::QueryRowBatchSize => write!(f, "QUERY_ROW_BATCH_SIZE"),
712 Self::RowTtlScanBatchSize => write!(f, "ROW_TTL_SCAN_BATCH_SIZE"),
713 Self::RowTtlScanInterval => write!(f, "ROW_TTL_SCAN_INTERVAL"),
714 Self::OperatorTtlScanBatchSize => write!(f, "OPERATOR_TTL_SCAN_BATCH_SIZE"),
715 Self::OperatorTtlScanInterval => write!(f, "OPERATOR_TTL_SCAN_INTERVAL"),
716 Self::VersionEpochSampleInterval => write!(f, "VERSION_EPOCH_SAMPLE_INTERVAL"),
717 Self::HistoricalGcBatchSize => write!(f, "HISTORICAL_GC_BATCH_SIZE"),
718 Self::HistoricalGcInterval => write!(f, "HISTORICAL_GC_INTERVAL"),
719 Self::CdcTtlDuration => write!(f, "CDC_TTL_DURATION"),
720 Self::CdcTtlScanInterval => write!(f, "CDC_TTL_SCAN_INTERVAL"),
721 Self::CdcTtlScanBatchSize => write!(f, "CDC_TTL_SCAN_BATCH_SIZE"),
722 Self::CdcTtlScanMaxBatchesPerTick => write!(f, "CDC_TTL_SCAN_MAX_BATCHES_PER_TICK"),
723 Self::CdcCompactInterval => write!(f, "CDC_COMPACT_INTERVAL"),
724 Self::CdcCompactBlockSize => write!(f, "CDC_COMPACT_BLOCK_SIZE"),
725 Self::CdcCompactSafetyLag => write!(f, "CDC_COMPACT_SAFETY_LAG"),
726 Self::CdcCompactMaxBlocksPerTick => write!(f, "CDC_COMPACT_MAX_BLOCKS_PER_TICK"),
727 Self::CdcCompactBlockCacheCapacity => write!(f, "CDC_COMPACT_BLOCK_CACHE_CAPACITY"),
728 Self::CdcCompactZstdLevel => write!(f, "CDC_COMPACT_ZSTD_LEVEL"),
729 Self::CdcRecentCacheCapacity => write!(f, "CDC_RECENT_CACHE_CAPACITY"),
730 Self::CdcWalAutocheckpoint => write!(f, "CDC_WAL_AUTOCHECKPOINT"),
731 Self::MultiReadBufferPages => write!(f, "MULTI_READ_BUFFER_PAGES"),
732 Self::MultiReadBufferPageSize => write!(f, "MULTI_READ_BUFFER_PAGE_SIZE"),
733 Self::MultiFlushInterval => write!(f, "MULTI_FLUSH_INTERVAL"),
734 Self::MultiWalAutocheckpoint => write!(f, "MULTI_WAL_AUTOCHECKPOINT"),
735 Self::FlowTick => write!(f, "FLOW_TICK"),
736 Self::CdcWatermarkWaitTimeout => write!(f, "CDC_WATERMARK_WAIT_TIMEOUT"),
737 Self::CdcConsumeWaitTimeout => write!(f, "CDC_CONSUME_WAIT_TIMEOUT"),
738 Self::FlowJoinProbeBlockSize => write!(f, "FLOW_JOIN_PROBE_BLOCK_SIZE"),
739 Self::ThreadsAsync => write!(f, "THREADS_ASYNC"),
740 Self::ThreadsCoordination => write!(f, "THREADS_COORDINATION"),
741 Self::ThreadsFlow => write!(f, "THREADS_FLOW"),
742 Self::ThreadsTask => write!(f, "THREADS_TASK"),
743 Self::ThreadsCompute => write!(f, "THREADS_COMPUTE"),
744 Self::SubscriptionWorkerThreads => write!(f, "SUBSCRIPTION_WORKER_THREADS"),
745 Self::RuntimeMetricsInterval => write!(f, "RUNTIME_METRICS_INTERVAL"),
746 Self::MetricFlushInterval => write!(f, "METRIC_FLUSH_INTERVAL"),
747 Self::MetricsRuntimeRetention => write!(f, "METRICS_RUNTIME_RETENTION"),
748 Self::MetricsProfilerRetention => write!(f, "METRICS_PROFILER_RETENTION"),
749 Self::MetricsProfilerSnapshotInterval => write!(f, "METRICS_PROFILER_SNAPSHOT_INTERVAL"),
750 }
751 }
752}
753
754impl FromStr for ConfigKey {
755 type Err = String;
756
757 fn from_str(s: &str) -> Result<Self, Self::Err> {
758 match s {
759 "ORACLE_WINDOW_SIZE" => Ok(Self::OracleWindowSize),
760 "ORACLE_WATER_MARK" => Ok(Self::OracleWaterMark),
761 "QUERY_ROW_BATCH_SIZE" => Ok(Self::QueryRowBatchSize),
762 "ROW_TTL_SCAN_BATCH_SIZE" => Ok(Self::RowTtlScanBatchSize),
763 "ROW_TTL_SCAN_INTERVAL" => Ok(Self::RowTtlScanInterval),
764 "OPERATOR_TTL_SCAN_BATCH_SIZE" => Ok(Self::OperatorTtlScanBatchSize),
765 "OPERATOR_TTL_SCAN_INTERVAL" => Ok(Self::OperatorTtlScanInterval),
766 "VERSION_EPOCH_SAMPLE_INTERVAL" => Ok(Self::VersionEpochSampleInterval),
767 "HISTORICAL_GC_BATCH_SIZE" => Ok(Self::HistoricalGcBatchSize),
768 "HISTORICAL_GC_INTERVAL" => Ok(Self::HistoricalGcInterval),
769 "CDC_TTL_DURATION" => Ok(Self::CdcTtlDuration),
770 "CDC_TTL_SCAN_INTERVAL" => Ok(Self::CdcTtlScanInterval),
771 "CDC_TTL_SCAN_BATCH_SIZE" => Ok(Self::CdcTtlScanBatchSize),
772 "CDC_TTL_SCAN_MAX_BATCHES_PER_TICK" => Ok(Self::CdcTtlScanMaxBatchesPerTick),
773 "CDC_COMPACT_INTERVAL" => Ok(Self::CdcCompactInterval),
774 "CDC_COMPACT_BLOCK_SIZE" => Ok(Self::CdcCompactBlockSize),
775 "CDC_COMPACT_SAFETY_LAG" => Ok(Self::CdcCompactSafetyLag),
776 "CDC_COMPACT_MAX_BLOCKS_PER_TICK" => Ok(Self::CdcCompactMaxBlocksPerTick),
777 "CDC_COMPACT_BLOCK_CACHE_CAPACITY" => Ok(Self::CdcCompactBlockCacheCapacity),
778 "CDC_COMPACT_ZSTD_LEVEL" => Ok(Self::CdcCompactZstdLevel),
779 "CDC_RECENT_CACHE_CAPACITY" => Ok(Self::CdcRecentCacheCapacity),
780 "CDC_WAL_AUTOCHECKPOINT" => Ok(Self::CdcWalAutocheckpoint),
781 "MULTI_READ_BUFFER_PAGES" => Ok(Self::MultiReadBufferPages),
782 "MULTI_READ_BUFFER_PAGE_SIZE" => Ok(Self::MultiReadBufferPageSize),
783 "MULTI_FLUSH_INTERVAL" => Ok(Self::MultiFlushInterval),
784 "MULTI_WAL_AUTOCHECKPOINT" => Ok(Self::MultiWalAutocheckpoint),
785 "FLOW_TICK" => Ok(Self::FlowTick),
786 "CDC_WATERMARK_WAIT_TIMEOUT" => Ok(Self::CdcWatermarkWaitTimeout),
787 "CDC_CONSUME_WAIT_TIMEOUT" => Ok(Self::CdcConsumeWaitTimeout),
788 "FLOW_JOIN_PROBE_BLOCK_SIZE" => Ok(Self::FlowJoinProbeBlockSize),
789 "THREADS_ASYNC" => Ok(Self::ThreadsAsync),
790 "THREADS_COORDINATION" => Ok(Self::ThreadsCoordination),
791 "THREADS_FLOW" => Ok(Self::ThreadsFlow),
792 "THREADS_TASK" => Ok(Self::ThreadsTask),
793 "THREADS_COMPUTE" => Ok(Self::ThreadsCompute),
794 "SUBSCRIPTION_WORKER_THREADS" => Ok(Self::SubscriptionWorkerThreads),
795 "RUNTIME_METRICS_INTERVAL" => Ok(Self::RuntimeMetricsInterval),
796 "METRIC_FLUSH_INTERVAL" => Ok(Self::MetricFlushInterval),
797 "METRICS_RUNTIME_RETENTION" => Ok(Self::MetricsRuntimeRetention),
798 "METRICS_PROFILER_RETENTION" => Ok(Self::MetricsProfilerRetention),
799 "METRICS_PROFILER_SNAPSHOT_INTERVAL" => Ok(Self::MetricsProfilerSnapshotInterval),
800 _ => Err(format!("Unknown system configuration key: {}", s)),
801 }
802 }
803}
804
805#[derive(Debug, Clone)]
806pub struct Config {
807 pub key: ConfigKey,
808
809 pub value: Value,
810
811 pub default_value: Value,
812
813 pub description: &'static str,
814
815 pub requires_restart: bool,
816}
817
818pub trait GetConfig: Send + Sync {
819 fn get_config(&self, key: ConfigKey) -> Value;
820
821 fn get_config_at(&self, key: ConfigKey, version: CommitVersion) -> Value;
822
823 fn get_config_uint8(&self, key: ConfigKey) -> u64 {
824 let val = self.get_config(key);
825 match val {
826 Value::Uint8(v) => v,
827 v => panic!("config key '{}' expected Uint8, got {:?}", key, v),
828 }
829 }
830
831 fn get_config_uint1(&self, key: ConfigKey) -> u8 {
832 let val = self.get_config(key);
833 match val {
834 Value::Uint1(v) => v,
835 v => panic!("config key '{}' expected Uint1, got {:?}", key, v),
836 }
837 }
838
839 fn get_config_uint2(&self, key: ConfigKey) -> u16 {
840 let val = self.get_config(key);
841 match val {
842 Value::Uint2(v) => v,
843 v => panic!("config key '{}' expected Uint2, got {:?}", key, v),
844 }
845 }
846
847 fn get_config_duration(&self, key: ConfigKey) -> Duration {
848 let val = self.get_config(key);
849 match val {
850 Value::Duration(v) => v,
851 v => panic!("config key '{}' expected Duration, got {:?}", key, v),
852 }
853 }
854
855 fn get_config_duration_opt(&self, key: ConfigKey) -> Option<Duration> {
856 match self.get_config(key) {
857 Value::None {
858 ..
859 } => None,
860 Value::Duration(v) => Some(v),
861 v => panic!("config key '{}' expected Duration or None, got {:?}", key, v),
862 }
863 }
864}
865
866#[cfg(test)]
867mod tests {
868 use super::*;
869
870 #[test]
871 fn test_cdc_ttl_default_is_typed_null() {
872 let default = ConfigKey::CdcTtlDuration.default_value();
874 assert!(matches!(
875 default,
876 Value::None {
877 inner: ValueType::Duration
878 }
879 ));
880 }
881
882 #[test]
883 fn test_cdc_ttl_accept_passes_typed_null() {
884 let none = Value::None {
885 inner: ValueType::Duration,
886 };
887 let v = ConfigKey::CdcTtlDuration.accept(none.clone()).unwrap();
888 assert_eq!(v, none);
889 }
890
891 #[test]
892 fn test_cdc_ttl_accept_passes_positive_duration() {
893 let one_sec = Value::duration_seconds(1);
894 assert_eq!(ConfigKey::CdcTtlDuration.accept(one_sec.clone()).unwrap(), one_sec);
895
896 let one_hour = Value::duration_seconds(3600);
897 assert_eq!(ConfigKey::CdcTtlDuration.accept(one_hour.clone()).unwrap(), one_hour);
898 }
899
900 #[test]
901 fn test_cdc_ttl_accept_rejects_zero() {
902 let zero = Value::duration_seconds(0);
903 match ConfigKey::CdcTtlDuration.accept(zero).unwrap_err() {
904 AcceptError::InvalidValue(reason) => {
905 assert!(reason.contains("greater than zero"), "unexpected reason: {reason}");
906 }
907 other => panic!("expected InvalidValue, got {other:?}"),
908 }
909 }
910
911 #[test]
912 fn test_cdc_ttl_accept_rejects_negative() {
913 let negative = Value::duration_seconds(-5);
914 assert!(matches!(ConfigKey::CdcTtlDuration.accept(negative), Err(AcceptError::InvalidValue(_))));
915 }
916
917 #[test]
918 fn test_other_keys_accept_in_type_values() {
919 assert!(ConfigKey::OracleWindowSize.accept(Value::Uint8(0)).is_ok());
921 assert!(ConfigKey::RowTtlScanInterval.accept(Value::duration_seconds(0)).is_ok());
922 }
923
924 #[test]
925 fn test_cdc_ttl_round_trips_through_display_and_from_str() {
926 let key: ConfigKey = "CDC_TTL_DURATION".parse().unwrap();
927 assert_eq!(key, ConfigKey::CdcTtlDuration);
928 assert_eq!(format!("{}", ConfigKey::CdcTtlDuration), "CDC_TTL_DURATION");
929 }
930
931 #[test]
932 fn test_cdc_ttl_in_all() {
933 assert!(ConfigKey::all().contains(&ConfigKey::CdcTtlDuration));
934 }
935
936 #[test]
937 fn test_all_contains_every_compact_key_and_has_expected_len() {
938 let all = ConfigKey::all();
939 assert_eq!(all.len(), 41);
940 assert!(all.contains(&ConfigKey::MultiFlushInterval));
941 assert!(all.contains(&ConfigKey::MultiWalAutocheckpoint));
942 assert!(all.contains(&ConfigKey::CdcWalAutocheckpoint));
943 assert!(all.contains(&ConfigKey::MetricsRuntimeRetention));
944 assert!(all.contains(&ConfigKey::MetricsProfilerRetention));
945 assert!(all.contains(&ConfigKey::MetricsProfilerSnapshotInterval));
946 assert!(all.contains(&ConfigKey::VersionEpochSampleInterval));
947 assert!(all.contains(&ConfigKey::CdcWatermarkWaitTimeout));
948 assert!(all.contains(&ConfigKey::CdcConsumeWaitTimeout));
949 assert!(all.contains(&ConfigKey::FlowJoinProbeBlockSize));
950 assert!(all.contains(&ConfigKey::CdcTtlScanInterval));
951 assert!(all.contains(&ConfigKey::CdcTtlScanBatchSize));
952 assert!(all.contains(&ConfigKey::CdcTtlScanMaxBatchesPerTick));
953 assert!(all.contains(&ConfigKey::CdcCompactInterval));
954 assert!(all.contains(&ConfigKey::CdcCompactBlockSize));
955 assert!(all.contains(&ConfigKey::CdcCompactSafetyLag));
956 assert!(all.contains(&ConfigKey::CdcCompactMaxBlocksPerTick));
957 assert!(all.contains(&ConfigKey::CdcCompactBlockCacheCapacity));
958 assert!(all.contains(&ConfigKey::CdcCompactZstdLevel));
959 assert!(all.contains(&ConfigKey::CdcRecentCacheCapacity));
960 assert!(all.contains(&ConfigKey::MultiReadBufferPages));
961 assert!(all.contains(&ConfigKey::MultiReadBufferPageSize));
962 assert!(all.contains(&ConfigKey::QueryRowBatchSize));
963 assert!(all.contains(&ConfigKey::ThreadsAsync));
964 assert!(all.contains(&ConfigKey::ThreadsCoordination));
965 assert!(all.contains(&ConfigKey::ThreadsFlow));
966 assert!(all.contains(&ConfigKey::ThreadsTask));
967 assert!(all.contains(&ConfigKey::ThreadsCompute));
968 assert!(all.contains(&ConfigKey::RuntimeMetricsInterval));
969 assert!(all.contains(&ConfigKey::MetricFlushInterval));
970 assert!(all.contains(&ConfigKey::SubscriptionWorkerThreads));
971 }
972
973 #[test]
974 fn test_runtime_metrics_interval_metadata() {
975 assert_eq!(ConfigKey::RuntimeMetricsInterval.default_value(), Value::duration_seconds(5));
977 assert_eq!(ConfigKey::RuntimeMetricsInterval.expected_types(), &[ValueType::Duration]);
978 assert!(ConfigKey::RuntimeMetricsInterval.is_optional());
979 }
980
981 #[test]
982 fn test_runtime_metrics_interval_round_trip() {
983 assert_eq!("RUNTIME_METRICS_INTERVAL".parse::<ConfigKey>().unwrap(), ConfigKey::RuntimeMetricsInterval);
984 assert_eq!(format!("{}", ConfigKey::RuntimeMetricsInterval), "RUNTIME_METRICS_INTERVAL");
985 }
986
987 #[test]
988 fn test_runtime_metrics_interval_accepts_none_and_positive_rejects_zero() {
989 let none = Value::None {
990 inner: ValueType::Duration,
991 };
992 assert_eq!(ConfigKey::RuntimeMetricsInterval.accept(none.clone()).unwrap(), none);
993
994 let five = Value::duration_seconds(5);
995 assert_eq!(ConfigKey::RuntimeMetricsInterval.accept(five.clone()).unwrap(), five);
996
997 let zero = Value::duration_seconds(0);
998 assert!(matches!(ConfigKey::RuntimeMetricsInterval.accept(zero), Err(AcceptError::InvalidValue(_))));
999 }
1000
1001 #[test]
1002 fn test_metric_flush_interval_metadata() {
1003 assert_eq!(ConfigKey::MetricFlushInterval.default_value(), Value::duration_seconds(10));
1005 assert_eq!(ConfigKey::MetricFlushInterval.expected_types(), &[ValueType::Duration]);
1006 assert!(!ConfigKey::MetricFlushInterval.is_optional());
1007 assert!(!ConfigKey::MetricFlushInterval.requires_restart());
1008 }
1009
1010 #[test]
1011 fn test_metric_flush_interval_round_trip() {
1012 assert_eq!("METRIC_FLUSH_INTERVAL".parse::<ConfigKey>().unwrap(), ConfigKey::MetricFlushInterval);
1013 assert_eq!(format!("{}", ConfigKey::MetricFlushInterval), "METRIC_FLUSH_INTERVAL");
1014 }
1015
1016 #[test]
1017 fn test_metric_flush_interval_accepts_positive_rejects_zero() {
1018 let ten = Value::duration_seconds(10);
1019 assert_eq!(ConfigKey::MetricFlushInterval.accept(ten.clone()).unwrap(), ten);
1020
1021 let zero = Value::duration_seconds(0);
1022 assert!(matches!(ConfigKey::MetricFlushInterval.accept(zero), Err(AcceptError::InvalidValue(_))));
1023 }
1024
1025 #[test]
1026 fn test_cdc_recent_cache_capacity_round_trip() {
1027 assert_eq!(
1028 "CDC_RECENT_CACHE_CAPACITY".parse::<ConfigKey>().unwrap(),
1029 ConfigKey::CdcRecentCacheCapacity
1030 );
1031 assert_eq!(format!("{}", ConfigKey::CdcRecentCacheCapacity), "CDC_RECENT_CACHE_CAPACITY");
1032 }
1033
1034 #[test]
1035 fn test_cdc_recent_cache_capacity_metadata() {
1036 assert_eq!(ConfigKey::CdcRecentCacheCapacity.default_value(), Value::Uint8(128));
1037 assert_eq!(ConfigKey::CdcRecentCacheCapacity.expected_types(), &[ValueType::Uint8]);
1038 assert!(ConfigKey::CdcRecentCacheCapacity.requires_restart());
1039 assert!(!ConfigKey::CdcRecentCacheCapacity.is_optional());
1040 }
1041
1042 #[test]
1043 fn test_multi_read_buffer_pages_round_trip() {
1044 assert_eq!("MULTI_READ_BUFFER_PAGES".parse::<ConfigKey>().unwrap(), ConfigKey::MultiReadBufferPages);
1045 assert_eq!(format!("{}", ConfigKey::MultiReadBufferPages), "MULTI_READ_BUFFER_PAGES");
1046 }
1047
1048 #[test]
1049 fn test_multi_read_buffer_pages_metadata_and_rejects_zero() {
1050 assert_eq!(ConfigKey::MultiReadBufferPages.default_value(), Value::Uint8(1024));
1051 assert_eq!(ConfigKey::MultiReadBufferPages.expected_types(), &[ValueType::Uint8]);
1052 assert!(ConfigKey::MultiReadBufferPages.requires_restart());
1053 assert!(!ConfigKey::MultiReadBufferPages.is_optional());
1054 match ConfigKey::MultiReadBufferPages.accept(Value::Uint8(0)).unwrap_err() {
1055 AcceptError::InvalidValue(reason) => {
1056 assert!(reason.contains("greater than zero"), "unexpected reason: {reason}");
1057 }
1058 other => panic!("expected InvalidValue, got {other:?}"),
1059 }
1060 }
1061
1062 #[test]
1063 fn test_multi_read_buffer_page_size_round_trip() {
1064 assert_eq!(
1065 "MULTI_READ_BUFFER_PAGE_SIZE".parse::<ConfigKey>().unwrap(),
1066 ConfigKey::MultiReadBufferPageSize
1067 );
1068 assert_eq!(format!("{}", ConfigKey::MultiReadBufferPageSize), "MULTI_READ_BUFFER_PAGE_SIZE");
1069 }
1070
1071 #[test]
1072 fn test_multi_read_buffer_page_size_metadata_and_rejects_non_power_of_two() {
1073 assert_eq!(ConfigKey::MultiReadBufferPageSize.default_value(), Value::Uint8(65536));
1076 assert_eq!(ConfigKey::MultiReadBufferPageSize.expected_types(), &[ValueType::Uint8]);
1077 assert!(ConfigKey::MultiReadBufferPageSize.requires_restart());
1078 assert!(!ConfigKey::MultiReadBufferPageSize.is_optional());
1079 assert_eq!(
1080 ConfigKey::MultiReadBufferPageSize.accept(Value::Uint8(4096)).unwrap(),
1081 Value::Uint8(4096),
1082 "a power-of-two page size is accepted"
1083 );
1084 match ConfigKey::MultiReadBufferPageSize.accept(Value::Uint8(1000)).unwrap_err() {
1085 AcceptError::InvalidValue(reason) => {
1086 assert!(reason.contains("power of two"), "unexpected reason: {reason}");
1087 }
1088 other => panic!("expected InvalidValue, got {other:?}"),
1089 }
1090 }
1091
1092 #[test]
1093 fn test_threads_keys_round_trip() {
1094 assert_eq!("THREADS_ASYNC".parse::<ConfigKey>().unwrap(), ConfigKey::ThreadsAsync);
1095 assert_eq!("THREADS_COORDINATION".parse::<ConfigKey>().unwrap(), ConfigKey::ThreadsCoordination);
1096 assert_eq!("THREADS_FLOW".parse::<ConfigKey>().unwrap(), ConfigKey::ThreadsFlow);
1097 assert_eq!("THREADS_TASK".parse::<ConfigKey>().unwrap(), ConfigKey::ThreadsTask);
1098 assert_eq!("THREADS_COMPUTE".parse::<ConfigKey>().unwrap(), ConfigKey::ThreadsCompute);
1099 assert_eq!(format!("{}", ConfigKey::ThreadsAsync), "THREADS_ASYNC");
1100 assert_eq!(format!("{}", ConfigKey::ThreadsCoordination), "THREADS_COORDINATION");
1101 assert_eq!(format!("{}", ConfigKey::ThreadsFlow), "THREADS_FLOW");
1102 assert_eq!(format!("{}", ConfigKey::ThreadsTask), "THREADS_TASK");
1103 assert_eq!(format!("{}", ConfigKey::ThreadsCompute), "THREADS_COMPUTE");
1104 }
1105
1106 #[test]
1107 fn test_threads_defaults() {
1108 assert_eq!(ConfigKey::ThreadsAsync.default_value(), Value::Uint2(1));
1109 assert_eq!(ConfigKey::ThreadsCoordination.default_value(), Value::Uint2(2));
1110 assert_eq!(ConfigKey::ThreadsFlow.default_value(), Value::Uint2(2));
1111 assert_eq!(ConfigKey::ThreadsTask.default_value(), Value::Uint2(2));
1112 assert_eq!(ConfigKey::ThreadsCompute.default_value(), Value::Uint2(2));
1113 }
1114
1115 #[test]
1116 fn test_threads_reject_zero() {
1117 for key in [
1118 ConfigKey::ThreadsAsync,
1119 ConfigKey::ThreadsCoordination,
1120 ConfigKey::ThreadsFlow,
1121 ConfigKey::ThreadsTask,
1122 ConfigKey::ThreadsCompute,
1123 ] {
1124 match key.accept(Value::Uint2(0)).unwrap_err() {
1125 AcceptError::InvalidValue(reason) => {
1126 assert!(
1127 reason.contains("greater than zero"),
1128 "{key}: unexpected reason: {reason}"
1129 );
1130 }
1131 other => panic!("{key}: expected InvalidValue, got {other:?}"),
1132 }
1133 }
1134 }
1135
1136 #[test]
1137 fn test_threads_accept_positive() {
1138 assert_eq!(ConfigKey::ThreadsAsync.accept(Value::Uint2(4)).unwrap(), Value::Uint2(4));
1139 assert_eq!(ConfigKey::ThreadsCoordination.accept(Value::Uint2(8)).unwrap(), Value::Uint2(8));
1140 assert_eq!(ConfigKey::ThreadsFlow.accept(Value::Uint2(16)).unwrap(), Value::Uint2(16));
1141 assert_eq!(ConfigKey::ThreadsTask.accept(Value::Uint2(4)).unwrap(), Value::Uint2(4));
1142 assert_eq!(ConfigKey::ThreadsCompute.accept(Value::Uint2(2)).unwrap(), Value::Uint2(2));
1143 }
1144
1145 #[test]
1146 fn test_threads_reject_int4_for_uint2_key() {
1147 assert!(matches!(ConfigKey::ThreadsTask.accept(Value::Int4(8)), Err(AcceptError::TypeMismatch { .. })));
1149 }
1150
1151 #[test]
1152 fn test_threads_require_restart() {
1153 assert!(ConfigKey::ThreadsAsync.requires_restart());
1154 assert!(ConfigKey::ThreadsCoordination.requires_restart());
1155 assert!(ConfigKey::ThreadsFlow.requires_restart());
1156 assert!(ConfigKey::ThreadsTask.requires_restart());
1157 assert!(ConfigKey::ThreadsCompute.requires_restart());
1158 }
1159
1160 #[test]
1161 fn test_query_row_batch_size_default_is_uint2_32() {
1162 assert_eq!(ConfigKey::QueryRowBatchSize.default_value(), Value::Uint2(32));
1163 }
1164
1165 #[test]
1166 fn test_query_row_batch_size_round_trips_through_display_and_from_str() {
1167 let key: ConfigKey = "QUERY_ROW_BATCH_SIZE".parse().unwrap();
1168 assert_eq!(key, ConfigKey::QueryRowBatchSize);
1169 assert_eq!(format!("{}", ConfigKey::QueryRowBatchSize), "QUERY_ROW_BATCH_SIZE");
1170 }
1171
1172 #[test]
1173 fn test_query_row_batch_size_accept_rejects_zero() {
1174 match ConfigKey::QueryRowBatchSize.accept(Value::Uint2(0)).unwrap_err() {
1175 AcceptError::InvalidValue(reason) => {
1176 assert!(reason.contains("greater than zero"), "unexpected reason: {reason}");
1177 }
1178 other => panic!("expected InvalidValue, got {other:?}"),
1179 }
1180 }
1181
1182 #[test]
1183 fn test_query_row_batch_size_accept_passes_positive() {
1184 assert_eq!(ConfigKey::QueryRowBatchSize.accept(Value::Uint2(1)).unwrap(), Value::Uint2(1));
1185 assert_eq!(ConfigKey::QueryRowBatchSize.accept(Value::Uint2(1024)).unwrap(), Value::Uint2(1024));
1186 }
1187
1188 #[test]
1189 fn test_query_row_batch_size_rejects_mismatched_type() {
1190 assert!(matches!(
1192 ConfigKey::QueryRowBatchSize.accept(Value::Int4(64)),
1193 Err(AcceptError::TypeMismatch { .. })
1194 ));
1195 assert!(matches!(
1196 ConfigKey::QueryRowBatchSize.accept(Value::Int4(0)),
1197 Err(AcceptError::TypeMismatch { .. })
1198 ));
1199 }
1200
1201 #[test]
1202 fn test_cdc_compact_interval_round_trips_through_display_and_from_str() {
1203 let key: ConfigKey = "CDC_COMPACT_INTERVAL".parse().unwrap();
1204 assert_eq!(key, ConfigKey::CdcCompactInterval);
1205 assert_eq!(format!("{}", ConfigKey::CdcCompactInterval), "CDC_COMPACT_INTERVAL");
1206 }
1207
1208 #[test]
1209 fn test_cdc_compact_block_size_round_trips_through_display_and_from_str() {
1210 let key: ConfigKey = "CDC_COMPACT_BLOCK_SIZE".parse().unwrap();
1211 assert_eq!(key, ConfigKey::CdcCompactBlockSize);
1212 assert_eq!(format!("{}", ConfigKey::CdcCompactBlockSize), "CDC_COMPACT_BLOCK_SIZE");
1213 }
1214
1215 #[test]
1216 fn test_cdc_compact_safety_lag_round_trips_through_display_and_from_str() {
1217 let key: ConfigKey = "CDC_COMPACT_SAFETY_LAG".parse().unwrap();
1218 assert_eq!(key, ConfigKey::CdcCompactSafetyLag);
1219 assert_eq!(format!("{}", ConfigKey::CdcCompactSafetyLag), "CDC_COMPACT_SAFETY_LAG");
1220 }
1221
1222 #[test]
1223 fn test_cdc_compact_max_blocks_per_tick_round_trips_through_display_and_from_str() {
1224 let key: ConfigKey = "CDC_COMPACT_MAX_BLOCKS_PER_TICK".parse().unwrap();
1225 assert_eq!(key, ConfigKey::CdcCompactMaxBlocksPerTick);
1226 assert_eq!(format!("{}", ConfigKey::CdcCompactMaxBlocksPerTick), "CDC_COMPACT_MAX_BLOCKS_PER_TICK");
1227 }
1228
1229 #[test]
1230 fn test_cdc_compact_interval_default_is_duration() {
1231 assert!(matches!(ConfigKey::CdcCompactInterval.default_value(), Value::Duration(_)));
1232 }
1233
1234 #[test]
1235 fn test_cdc_compact_block_size_default_is_uint8_1024() {
1236 assert_eq!(ConfigKey::CdcCompactBlockSize.default_value(), Value::Uint8(1024));
1237 }
1238
1239 #[test]
1240 fn test_cdc_compact_safety_lag_default_is_uint8_1024() {
1241 assert_eq!(ConfigKey::CdcCompactSafetyLag.default_value(), Value::Uint8(1024));
1242 }
1243
1244 #[test]
1245 fn test_cdc_compact_max_blocks_per_tick_default_is_uint8_16() {
1246 assert_eq!(ConfigKey::CdcCompactMaxBlocksPerTick.default_value(), Value::Uint8(16));
1247 }
1248
1249 #[test]
1250 fn test_cdc_compact_interval_accept_passes_positive_duration() {
1251 let one_sec = Value::duration_seconds(1);
1252 assert_eq!(ConfigKey::CdcCompactInterval.accept(one_sec.clone()).unwrap(), one_sec);
1253 }
1254
1255 #[test]
1256 fn test_cdc_compact_interval_accept_rejects_zero() {
1257 let zero = Value::duration_seconds(0);
1258 match ConfigKey::CdcCompactInterval.accept(zero).unwrap_err() {
1259 AcceptError::InvalidValue(reason) => {
1260 assert!(reason.contains("greater than zero"), "unexpected reason: {reason}");
1261 }
1262 other => panic!("expected InvalidValue, got {other:?}"),
1263 }
1264 }
1265
1266 #[test]
1267 fn test_cdc_compact_interval_accept_rejects_negative() {
1268 let negative = Value::duration_seconds(-5);
1269 assert!(matches!(ConfigKey::CdcCompactInterval.accept(negative), Err(AcceptError::InvalidValue(_))));
1270 }
1271
1272 #[test]
1273 fn test_cdc_compact_block_size_accept_rejects_zero() {
1274 match ConfigKey::CdcCompactBlockSize.accept(Value::Uint8(0)).unwrap_err() {
1275 AcceptError::InvalidValue(reason) => {
1276 assert!(reason.contains("greater than zero"), "unexpected reason: {reason}");
1277 }
1278 other => panic!("expected InvalidValue, got {other:?}"),
1279 }
1280 }
1281
1282 #[test]
1283 fn test_cdc_compact_block_size_accept_passes_positive() {
1284 assert_eq!(ConfigKey::CdcCompactBlockSize.accept(Value::Uint8(1)).unwrap(), Value::Uint8(1));
1285 assert_eq!(ConfigKey::CdcCompactBlockSize.accept(Value::Uint8(1024)).unwrap(), Value::Uint8(1024));
1286 }
1287
1288 #[test]
1289 fn test_cdc_compact_safety_lag_and_max_blocks_accept_zero() {
1290 assert_eq!(ConfigKey::CdcCompactSafetyLag.accept(Value::Uint8(0)).unwrap(), Value::Uint8(0));
1291 assert_eq!(ConfigKey::CdcCompactMaxBlocksPerTick.accept(Value::Uint8(0)).unwrap(), Value::Uint8(0));
1292 }
1293
1294 #[test]
1295 fn test_accept_rejects_int4_for_uint8_block_size() {
1296 assert!(matches!(
1298 ConfigKey::CdcCompactBlockSize.accept(Value::Int4(1024)),
1299 Err(AcceptError::TypeMismatch { .. })
1300 ));
1301 assert!(matches!(
1302 ConfigKey::CdcCompactBlockSize.accept(Value::Int8(2048)),
1303 Err(AcceptError::TypeMismatch { .. })
1304 ));
1305 }
1306
1307 #[test]
1308 fn test_accept_rejects_zero_of_canonical_type() {
1309 match ConfigKey::CdcCompactBlockSize.accept(Value::Uint8(0)).unwrap_err() {
1310 AcceptError::InvalidValue(reason) => {
1311 assert!(reason.contains("greater than zero"));
1312 }
1313 other => panic!("expected InvalidValue, got {other:?}"),
1314 }
1315 }
1316
1317 #[test]
1318 fn test_accept_rejects_negative_int_for_uint8_key() {
1319 assert!(matches!(
1321 ConfigKey::CdcCompactBlockSize.accept(Value::Int4(-1)),
1322 Err(AcceptError::TypeMismatch { .. })
1323 ));
1324 }
1325
1326 #[test]
1327 fn test_accept_rejects_int_for_duration_key() {
1328 assert!(matches!(
1331 ConfigKey::CdcCompactInterval.accept(Value::Int4(60)),
1332 Err(AcceptError::TypeMismatch { .. })
1333 ));
1334 }
1335
1336 #[test]
1337 fn test_accept_idempotent_on_canonical_uint8() {
1338 let canonical = Value::Uint8(42);
1339 assert_eq!(ConfigKey::OracleWindowSize.accept(canonical.clone()).unwrap(), canonical);
1340 }
1341
1342 #[test]
1343 fn test_accept_idempotent_on_canonical_duration() {
1344 let canonical = Value::duration_seconds(5);
1345 assert_eq!(ConfigKey::CdcCompactInterval.accept(canonical.clone()).unwrap(), canonical);
1346 }
1347
1348 #[test]
1349 fn test_accept_rejects_typed_null_for_non_optional_key() {
1350 let err = ConfigKey::CdcCompactBlockSize
1351 .accept(Value::None {
1352 inner: ValueType::Uint8,
1353 })
1354 .unwrap_err();
1355 assert!(matches!(err, AcceptError::TypeMismatch { .. }));
1356 }
1357
1358 #[test]
1359 fn test_accept_passes_typed_null_for_optional_key() {
1360 let none = Value::None {
1361 inner: ValueType::Duration,
1362 };
1363 assert_eq!(ConfigKey::CdcTtlDuration.accept(none.clone()).unwrap(), none);
1364 }
1365
1366 #[test]
1367 fn test_accept_rejects_wrong_inner_type_typed_null_for_optional_key() {
1368 let err = ConfigKey::CdcTtlDuration
1370 .accept(Value::None {
1371 inner: ValueType::Uint8,
1372 })
1373 .unwrap_err();
1374 assert!(matches!(err, AcceptError::TypeMismatch { .. }));
1375 }
1376
1377 #[test]
1378 fn test_metrics_retention_round_trip() {
1379 assert_eq!(
1380 "METRICS_RUNTIME_RETENTION".parse::<ConfigKey>().unwrap(),
1381 ConfigKey::MetricsRuntimeRetention
1382 );
1383 assert_eq!(
1384 "METRICS_PROFILER_RETENTION".parse::<ConfigKey>().unwrap(),
1385 ConfigKey::MetricsProfilerRetention
1386 );
1387 assert_eq!(format!("{}", ConfigKey::MetricsRuntimeRetention), "METRICS_RUNTIME_RETENTION");
1388 assert_eq!(format!("{}", ConfigKey::MetricsProfilerRetention), "METRICS_PROFILER_RETENTION");
1389 }
1390
1391 #[test]
1392 fn test_metrics_retention_defaults_are_7d_and_1h() {
1393 assert_eq!(ConfigKey::MetricsRuntimeRetention.default_value(), Value::duration_seconds(7 * 24 * 3600));
1396 assert_eq!(ConfigKey::MetricsProfilerRetention.default_value(), Value::duration_seconds(3600));
1397 }
1398
1399 #[test]
1400 fn test_metrics_retention_metadata() {
1401 for key in [ConfigKey::MetricsRuntimeRetention, ConfigKey::MetricsProfilerRetention] {
1402 assert_eq!(key.expected_types(), &[ValueType::Duration], "{key}");
1403 assert!(!key.is_optional(), "{key} is always defaulted, never unset");
1404 }
1405 }
1406
1407 #[test]
1408 fn test_metrics_retention_rejects_zero() {
1409 for key in [ConfigKey::MetricsRuntimeRetention, ConfigKey::MetricsProfilerRetention] {
1412 match key.accept(Value::duration_seconds(0)).unwrap_err() {
1413 AcceptError::InvalidValue(reason) => {
1414 assert!(
1415 reason.contains("greater than zero"),
1416 "{key}: unexpected reason: {reason}"
1417 );
1418 }
1419 other => panic!("{key}: expected InvalidValue, got {other:?}"),
1420 }
1421 }
1422 }
1423
1424 #[test]
1425 fn test_metrics_profiler_snapshot_interval_default_is_none() {
1426 assert_eq!(
1430 ConfigKey::MetricsProfilerSnapshotInterval.default_value(),
1431 Value::None {
1432 inner: ValueType::Duration,
1433 }
1434 );
1435 }
1436
1437 #[test]
1438 fn test_metrics_profiler_snapshot_interval_accepts_none_to_disable_persistence() {
1439 let none = Value::None {
1444 inner: ValueType::Duration,
1445 };
1446 assert_eq!(ConfigKey::MetricsProfilerSnapshotInterval.accept(none.clone()).unwrap(), none);
1447 }
1448
1449 #[test]
1450 fn test_metrics_profiler_snapshot_interval_rejects_zero_and_negative() {
1451 match ConfigKey::MetricsProfilerSnapshotInterval.accept(Value::duration_seconds(0)).unwrap_err() {
1455 AcceptError::InvalidValue(reason) => {
1456 assert!(reason.contains("greater than zero"), "unexpected reason: {reason}");
1457 }
1458 other => panic!("expected InvalidValue, got {other:?}"),
1459 }
1460 assert!(matches!(
1461 ConfigKey::MetricsProfilerSnapshotInterval.accept(Value::duration_seconds(-5)),
1462 Err(AcceptError::InvalidValue(_))
1463 ));
1464 }
1465
1466 #[test]
1467 fn test_metrics_profiler_snapshot_interval_requires_restart() {
1468 assert!(ConfigKey::MetricsProfilerSnapshotInterval.requires_restart());
1473 }
1474
1475 #[test]
1476 fn test_metrics_profiler_snapshot_interval_round_trips_through_display_and_from_str() {
1477 assert_eq!(
1478 "METRICS_PROFILER_SNAPSHOT_INTERVAL".parse::<ConfigKey>().unwrap(),
1479 ConfigKey::MetricsProfilerSnapshotInterval
1480 );
1481 assert_eq!(
1482 format!("{}", ConfigKey::MetricsProfilerSnapshotInterval),
1483 "METRICS_PROFILER_SNAPSHOT_INTERVAL"
1484 );
1485 }
1486
1487 #[test]
1488 fn test_metrics_profiler_snapshot_interval_in_all() {
1489 assert!(ConfigKey::all().contains(&ConfigKey::MetricsProfilerSnapshotInterval));
1490 }
1491
1492 #[test]
1493 fn test_historical_gc_keys_round_trip() {
1494 assert_eq!("HISTORICAL_GC_BATCH_SIZE".parse::<ConfigKey>().unwrap(), ConfigKey::HistoricalGcBatchSize);
1495 assert_eq!("HISTORICAL_GC_INTERVAL".parse::<ConfigKey>().unwrap(), ConfigKey::HistoricalGcInterval);
1496 assert_eq!(format!("{}", ConfigKey::HistoricalGcBatchSize), "HISTORICAL_GC_BATCH_SIZE");
1497 assert_eq!(format!("{}", ConfigKey::HistoricalGcInterval), "HISTORICAL_GC_INTERVAL");
1498 }
1499
1500 #[test]
1501 fn test_historical_gc_defaults() {
1502 assert_eq!(ConfigKey::HistoricalGcBatchSize.default_value(), Value::Uint8(50_000));
1503 assert!(matches!(ConfigKey::HistoricalGcInterval.default_value(), Value::Duration(_)));
1504 }
1505
1506 #[test]
1507 fn test_historical_gc_batch_size_rejects_zero() {
1508 match ConfigKey::HistoricalGcBatchSize.accept(Value::Uint8(0)).unwrap_err() {
1509 AcceptError::InvalidValue(reason) => {
1510 assert!(reason.contains("greater than zero"), "unexpected reason: {reason}");
1511 }
1512 other => panic!("expected InvalidValue, got {other:?}"),
1513 }
1514 }
1515
1516 #[test]
1517 fn test_historical_gc_interval_rejects_zero() {
1518 let zero = Value::duration_seconds(0);
1519 match ConfigKey::HistoricalGcInterval.accept(zero).unwrap_err() {
1520 AcceptError::InvalidValue(reason) => {
1521 assert!(reason.contains("greater than zero"), "unexpected reason: {reason}");
1522 }
1523 other => panic!("expected InvalidValue, got {other:?}"),
1524 }
1525 }
1526}