1use 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
21pub struct ConfigEntry {
23 pub default_value: Value,
25 pub description: &'static str,
27 pub requires_restart: bool,
29 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#[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 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 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 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 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 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 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 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 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 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 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}