use std::{
fmt,
fmt::{Debug, Formatter},
sync::Arc,
};
use crossbeam_skiplist::SkipMap;
use reifydb_type::value::Value;
use crate::{common::CommitVersion, interface::catalog::config::Config, util::multi::MultiVersionContainer};
pub struct ConfigEntry {
pub default_value: Value,
pub description: &'static str,
pub requires_restart: bool,
pub versions: MultiVersionContainer<Value>,
}
fn is_valid_config_key(key: &str) -> bool {
!key.is_empty()
&& key.bytes().all(|b| b.is_ascii_uppercase() || b == b'_' || b.is_ascii_digit())
&& key.as_bytes()[0].is_ascii_uppercase()
}
struct SystemConfigInner {
entries: SkipMap<String, ConfigEntry>,
}
#[derive(Clone)]
pub struct SystemConfig(Arc<SystemConfigInner>);
impl Debug for SystemConfig {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("SystemConfig").finish()
}
}
impl SystemConfig {
pub fn new() -> Self {
Self(Arc::new(SystemConfigInner {
entries: SkipMap::new(),
}))
}
pub fn register(&self, key: &str, default: Value, description: &'static str, requires_restart: bool) {
debug_assert!(is_valid_config_key(key), "config key must be SCREAMING_SNAKE_CASE, got: {key:?}");
if self.0.entries.contains_key(key) {
return;
}
let versions = MultiVersionContainer::new();
versions.insert(CommitVersion(0), default.clone());
self.0.entries.insert(
key.to_string(),
ConfigEntry {
default_value: default,
description,
requires_restart,
versions,
},
);
}
pub fn apply_persisted(&self, key: &str, version: CommitVersion, value: Value) {
if let Some(entry) = self.0.entries.get(key) {
entry.value().versions.insert(version, value);
}
}
pub fn update(&self, key: &str, version: CommitVersion, value: Value) {
match self.0.entries.get(key) {
Some(entry) => {
entry.value().versions.insert(version, value);
}
None => panic!("SystemConfig::update called with unregistered key: {key}"),
}
}
pub fn get(&self, key: &str) -> Option<Value> {
self.0.entries.get(key).and_then(|entry| entry.value().versions.get_latest())
}
pub fn get_default(&self, key: &str) -> Option<Value> {
self.0.entries.get(key).map(|entry| entry.value().default_value.clone())
}
pub fn get_at(&self, key: &str, version: CommitVersion) -> Option<Value> {
self.0.entries.get(key).and_then(|entry| entry.value().versions.get(version))
}
pub fn get_uint8(&self, key: &str) -> Option<u64> {
self.0.entries.get(key).map(|entry| match entry.value().versions.get_latest() {
Some(Value::Uint8(v)) => v,
Some(v) => panic!("config key '{key}' expected Uint8, got {v:?}"),
None => panic!("config key '{key}' has no value"),
})
}
pub fn require_uint8(&self, key: &str) -> u64 {
self.get_uint8(key).unwrap_or_else(|| panic!("config key '{key}' is not registered"))
}
pub fn list_all(&self) -> Vec<Config> {
self.0.entries
.iter()
.filter_map(|entry| {
entry.value().versions.get_latest().map(|current| Config {
key: entry.key().clone(),
value: current,
default_value: entry.value().default_value.clone(),
description: entry.value().description,
requires_restart: entry.value().requires_restart,
})
})
.collect()
}
pub fn list_all_at(&self, version: CommitVersion) -> Vec<Config> {
self.0.entries
.iter()
.filter_map(|entry| {
entry.value().versions.get(version).map(|current| Config {
key: entry.key().clone(),
value: current,
default_value: entry.value().default_value.clone(),
description: entry.value().description,
requires_restart: entry.value().requires_restart,
})
})
.collect()
}
}
impl Default for SystemConfig {
fn default() -> Self {
Self::new()
}
}