Skip to main content

reifydb_core/
config.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2// Copyright (c) 2025 ReifyDB
3
4//! In-memory registry for runtime database configuration.
5//!
6//! `ConfigRegistry` holds all registered configuration entries with their
7//! current values. Subsystems register their configs at startup, and the
8//! bootstrap process applies any persisted overrides from storage.
9
10use std::{
11	fmt,
12	fmt::{Debug, Formatter},
13	sync::Arc,
14};
15
16use crossbeam_skiplist::SkipMap;
17use reifydb_type::value::Value;
18
19use crate::{common::CommitVersion, interface::catalog::config::ConfigDef, util::multi::MultiVersionContainer};
20
21/// A single configuration entry in the registry.
22pub struct ConfigEntry {
23	/// Compile-time default value
24	pub default_value: Value,
25	/// Human-readable description
26	pub description: &'static str,
27	/// Whether a restart is required to apply this setting
28	pub requires_restart: bool,
29	/// Multi-version history of values for MVCC snapshot isolation.
30	pub versions: MultiVersionContainer<Value>,
31}
32
33fn is_valid_config_key(key: &str) -> bool {
34	!key.is_empty()
35		&& key.bytes().all(|b| b.is_ascii_uppercase() || b == b'_' || b.is_ascii_digit())
36		&& key.as_bytes()[0].is_ascii_uppercase()
37}
38
39struct SystemConfigInner {
40	entries: SkipMap<String, ConfigEntry>,
41}
42
43/// Registry of all runtime-tunable configuration entries.
44///
45/// Subsystems call `register()` at startup to declare their tunable parameters.
46/// The bootstrap process calls `apply_persisted()` to override defaults with
47/// values loaded from storage.
48///
49/// `SystemConfig` is cheaply cloneable — cloning increments an internal `Arc`
50/// reference count without copying any data.
51#[derive(Clone)]
52pub struct SystemConfig(Arc<SystemConfigInner>);
53
54impl Debug for SystemConfig {
55	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
56		f.debug_struct("SystemConfig").finish()
57	}
58}
59
60impl SystemConfig {
61	pub fn new() -> Self {
62		Self(Arc::new(SystemConfigInner {
63			entries: SkipMap::new(),
64		}))
65	}
66
67	/// Register a configuration key with its default value and metadata.
68	///
69	/// The default value is inserted at CommitVersion(0) so it is visible
70	/// to all transactions. If the key is already registered, this is a no-op.
71	pub fn register(&self, key: &str, default: Value, description: &'static str, requires_restart: bool) {
72		debug_assert!(is_valid_config_key(key), "config key must be SCREAMING_SNAKE_CASE, got: {key:?}");
73		if self.0.entries.contains_key(key) {
74			return;
75		}
76
77		let versions = MultiVersionContainer::new();
78		versions.insert(CommitVersion(0), default.clone());
79		self.0.entries.insert(
80			key.to_string(),
81			ConfigEntry {
82				default_value: default,
83				description,
84				requires_restart,
85				versions,
86			},
87		);
88	}
89
90	/// Apply a persisted value loaded from storage during bootstrap.
91	///
92	/// If the key is not registered, the value is silently ignored
93	/// (it may be from a removed config that no longer exists).
94	pub fn apply_persisted(&self, key: &str, version: CommitVersion, value: Value) {
95		if let Some(entry) = self.0.entries.get(key) {
96			entry.value().versions.insert(version, value);
97		}
98	}
99
100	/// Update a config value at the given commit version (called from post-commit interceptor).
101	///
102	/// Panics if the key is not registered — callers must verify via `get()`
103	/// before calling this.
104	pub fn update(&self, key: &str, version: CommitVersion, value: Value) {
105		match self.0.entries.get(key) {
106			Some(entry) => {
107				entry.value().versions.insert(version, value);
108			}
109			None => panic!("SystemConfig::update called with unregistered key: {key}"),
110		}
111	}
112
113	/// Get the latest value for a config key.
114	pub fn get(&self, key: &str) -> Option<Value> {
115		self.0.entries.get(key).and_then(|entry| entry.value().versions.get_latest())
116	}
117
118	/// Get the registered default value for a config key.
119	pub fn get_default(&self, key: &str) -> Option<Value> {
120		self.0.entries.get(key).map(|entry| entry.value().default_value.clone())
121	}
122
123	/// Get the value for a config key as of a specific snapshot version.
124	pub fn get_at(&self, key: &str, version: CommitVersion) -> Option<Value> {
125		self.0.entries.get(key).and_then(|entry| entry.value().versions.get(version))
126	}
127
128	/// Get the current value as a u64, or None if the key is not registered.
129	/// Panics if the key is registered but the value is not Value::Uint8.
130	pub fn get_uint8(&self, key: &str) -> Option<u64> {
131		self.0.entries.get(key).map(|entry| match entry.value().versions.get_latest() {
132			Some(Value::Uint8(v)) => v,
133			Some(v) => panic!("config key '{key}' expected Uint8, got {v:?}"),
134			None => panic!("config key '{key}' has no value"),
135		})
136	}
137
138	/// Get the current value as a u64, panicking if the key is not registered
139	/// or the value is not Value::Uint8.
140	pub fn require_uint8(&self, key: &str) -> u64 {
141		self.get_uint8(key).unwrap_or_else(|| panic!("config key '{key}' is not registered"))
142	}
143
144	/// List all registered configuration entries with their latest values.
145	pub fn list_all(&self) -> Vec<ConfigDef> {
146		self.0.entries
147			.iter()
148			.filter_map(|entry| {
149				entry.value().versions.get_latest().map(|current| ConfigDef {
150					key: entry.key().clone(),
151					value: current,
152					default_value: entry.value().default_value.clone(),
153					description: entry.value().description,
154					requires_restart: entry.value().requires_restart,
155				})
156			})
157			.collect()
158	}
159
160	/// List all registered configuration entries with values as of a specific snapshot version.
161	pub fn list_all_at(&self, version: CommitVersion) -> Vec<ConfigDef> {
162		self.0.entries
163			.iter()
164			.filter_map(|entry| {
165				entry.value().versions.get(version).map(|current| ConfigDef {
166					key: entry.key().clone(),
167					value: current,
168					default_value: entry.value().default_value.clone(),
169					description: entry.value().description,
170					requires_restart: entry.value().requires_restart,
171				})
172			})
173			.collect()
174	}
175}
176
177impl Default for SystemConfig {
178	fn default() -> Self {
179		Self::new()
180	}
181}