pub(crate) mod dynamic;
use std::collections::HashMap;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::LazyLock;
use std::time::Duration;
use crate::iam::file::extract_allowed_paths;
pub const SERVER_NAME: &str = "SurrealDB";
pub const ID_CHARS: [char; 36] = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
];
pub const PROTECTED_PARAM_NAMES: &[&str] = &["access", "auth", "token", "session"];
pub const NOTIFICATIONS_CHANNEL_SIZE: usize = 15_000;
#[derive(Clone, Debug)]
pub struct ConfigMap {
values: HashMap<String, String>,
}
impl Default for ConfigMap {
fn default() -> Self {
Self::empty()
}
}
impl ConfigMap {
pub fn empty() -> Self {
ConfigMap {
values: HashMap::new(),
}
}
pub fn with_key_value<K, V>(mut self, key: K, value: V) -> Self
where
String: From<K>,
String: From<V>,
{
self.values.insert(key.into(), value.into());
self
}
pub fn from_env() -> Self {
Self::from_env_prefix("SURREAL_")
}
pub fn from_env_prefix(prefix: &str) -> Self {
let mut values = HashMap::new();
for (k, v) in std::env::vars() {
let Some(x) = k.strip_prefix(prefix) else {
continue;
};
let key_name = x.to_lowercase();
values.insert(key_name, v);
}
ConfigMap {
values,
}
}
pub fn map_keys<F: FnMut(String) -> String>(self, mut f: F) -> Self {
Self {
values: self.values.into_iter().map(|(k, v)| (f(k), v)).collect(),
}
}
pub fn from_config_string(s: &str) -> Self {
let values = s
.split('&')
.filter_map(|x| {
let (k, v) = x.split_once('=')?;
Some((k.to_lowercase(), v.to_string()))
})
.collect();
ConfigMap {
values,
}
}
pub fn join(mut self, other: ConfigMap) -> Self {
for (k, v) in other.values {
self.values.insert(k, v);
}
self
}
pub fn load<C: Config>(&self) -> C {
let mut def = C::default();
def.parse(self);
def
}
pub fn parse_key<S: FromStr>(&self, key: &str, value: &mut S) -> &Self {
self.parse_key_with(key, value, |x| S::from_str(x).ok())
}
pub fn parse_key_option<S: FromStr>(&self, key: &str, value: &mut Option<S>) -> &Self {
self.parse_key_with(key, value, |x| S::from_str(x).ok().map(Some))
}
pub fn parse_key_bool(&self, key: &str, value: &mut bool) -> &Self {
self.parse_key_with(key, value, |x| {
if x.eq_ignore_ascii_case("true") || x == "1" {
Some(true)
} else if x.eq_ignore_ascii_case("false") || x == "0" {
Some(false)
} else {
None
}
})
}
pub fn parse_key_with<R, F: FnOnce(&str) -> Option<R>>(
&self,
key: &str,
value: &mut R,
f: F,
) -> &Self {
let Some(v) = self.values.get(key) else {
return self;
};
let Some(v) = f(v) else {
warn!("Could not parse configuration value for key `{}`", key.to_uppercase());
return self;
};
*value = v;
self
}
pub fn has_key(&self, key: &str) -> bool {
self.values.contains_key(key)
}
}
pub trait Config: Default {
fn parse(&mut self, map: &ConfigMap);
}
#[derive(Debug)]
pub struct CommonConfig {
pub memory_threshold: usize,
pub max_concurrent_tasks: usize,
pub max_computation_depth: u32,
pub max_object_parsing_depth: u32,
pub max_query_parsing_depth: u32,
pub idiom_recursion_limit: u32,
pub regex_size_limit: usize,
pub regex_cache_size: usize,
pub transaction_cache_size: usize,
pub datastore_cache_size: usize,
pub export_batch_size: u32,
pub operator_buffer_size: usize,
pub scan_batch_size: usize,
pub max_order_limit_priority_queue_size: u32,
pub scripting_max_stack_size: usize,
pub scripting_max_memory_limit: usize,
pub scripting_max_time_limit: Duration,
pub max_http_redirects: usize,
pub max_http_idle_connections_per_host: usize,
pub max_http_idle_connections: usize,
pub http_idle_timeout_secs: u64,
pub http_connect_timeout_secs: u64,
pub insecure_forward_access_errors: bool,
pub external_sorting_buffer_limit: usize,
pub generation_allocation_limit: usize,
pub string_similarity_limit: usize,
pub file_allowlist: Vec<PathBuf>,
pub global_bucket: Option<String>,
pub global_bucket_enforced: bool,
pub surrealdb_user_agent: String,
pub hnsw_cache_size: u64,
pub diskann_cache_size: u64,
pub surrealism_cache_size: usize,
pub surrealism_max_memory: Option<usize>,
pub surrealism_max_execution_time: Option<u64>,
pub surrealism_max_kv_entries: Option<usize>,
pub surrealism_max_kv_value_bytes: Option<usize>,
pub surrealism_max_fs_bytes: u64,
pub surrealism_max_pool_size: usize,
pub surrealism_log_level: String,
}
impl Default for CommonConfig {
fn default() -> Self {
Self {
memory_threshold: 0,
#[cfg(not(target_family = "wasm"))]
max_concurrent_tasks: 64,
#[cfg(target_family = "wasm")]
max_concurrent_tasks: 1,
max_computation_depth: 120,
max_object_parsing_depth: 100,
max_query_parsing_depth: 20,
idiom_recursion_limit: 256,
regex_size_limit: 10 * 1024 * 1024,
regex_cache_size: 1_000,
transaction_cache_size: 512,
datastore_cache_size: 1_000,
export_batch_size: 1000,
operator_buffer_size: 2,
scan_batch_size: crate::exec::operators::scan::common::DEFAULT_SCAN_BATCH_SIZE,
max_order_limit_priority_queue_size: 1000,
scripting_max_stack_size: 256 * 1024,
scripting_max_memory_limit: 2 << 20,
scripting_max_time_limit: Duration::from_secs(5),
max_http_redirects: 10,
max_http_idle_connections_per_host: 128,
max_http_idle_connections: 1000,
http_idle_timeout_secs: 90,
http_connect_timeout_secs: 30,
insecure_forward_access_errors: false,
external_sorting_buffer_limit: 50_000,
generation_allocation_limit: 2 << 20,
string_similarity_limit: 16384,
file_allowlist: Vec::new(),
global_bucket: None,
global_bucket_enforced: false,
surrealdb_user_agent: "SurrealDB".to_string(),
hnsw_cache_size: 256 * 1024 * 1024,
diskann_cache_size: 256 * 1024 * 1024,
surrealism_cache_size: 100,
surrealism_max_memory: None,
surrealism_max_execution_time: None,
surrealism_max_kv_entries: None,
surrealism_max_kv_value_bytes: None,
surrealism_max_fs_bytes: 100 * 1024 * 1024,
surrealism_max_pool_size: 8,
surrealism_log_level: "debug".to_string(),
}
}
}
impl Config for CommonConfig {
fn parse(&mut self, map: &ConfigMap) {
map.parse_key_with("memory_threshold", &mut self.memory_threshold, |x| {
x.parse::<usize>().map(|x| x.max(1024 * 1024)).ok()
})
.parse_key("max_concurrent_tasks", &mut self.max_concurrent_tasks)
.parse_key("max_computation_depth", &mut self.max_computation_depth)
.parse_key("max_object_parsing_depth", &mut self.max_object_parsing_depth)
.parse_key("max_query_parsing_depth", &mut self.max_query_parsing_depth)
.parse_key("regex_size_limit", &mut self.regex_size_limit)
.parse_key("regex_cache_size", &mut self.regex_cache_size)
.parse_key("transaction_cache_size", &mut self.transaction_cache_size)
.parse_key("datastore_cache_size", &mut self.datastore_cache_size)
.parse_key("surrealism_cache_size", &mut self.surrealism_cache_size)
.parse_key("export_batch_size", &mut self.export_batch_size)
.parse_key("operator_buffer_size", &mut self.operator_buffer_size)
.parse_key("scan_batch_size", &mut self.scan_batch_size)
.parse_key(
"max_order_limit_priority_queue_size",
&mut self.max_order_limit_priority_queue_size,
)
.parse_key("scripting_max_stack_size", &mut self.scripting_max_stack_size)
.parse_key("scripting_max_memory_limit", &mut self.scripting_max_memory_limit)
.parse_key_with("scripting_max_time_limit", &mut self.scripting_max_time_limit, |x| {
x.parse().map(Duration::from_millis).ok()
})
.parse_key("max_http_redirects", &mut self.max_http_redirects)
.parse_key(
"max_http_idle_connections_per_host",
&mut self.max_http_idle_connections_per_host,
)
.parse_key("max_http_idle_connections", &mut self.max_http_idle_connections)
.parse_key("http_idle_timeout_secs", &mut self.http_idle_timeout_secs)
.parse_key("http_connect_timeout_secs", &mut self.http_connect_timeout_secs)
.parse_key("insecure_forward_access_errors", &mut self.insecure_forward_access_errors)
.parse_key("external_sorting_buffer_limit", &mut self.external_sorting_buffer_limit)
.parse_key_with("generation_allocation_limit", &mut self.generation_allocation_limit, |x| {
x.parse::<usize>().ok().map(|x| 2 << x.min(28))
})
.parse_key("string_similarity_limit", &mut self.string_similarity_limit)
.parse_key("hnsw_cache_size", &mut self.hnsw_cache_size)
.parse_key("diskann_cache_size", &mut self.diskann_cache_size)
.parse_key_with("file_allowlist", &mut self.file_allowlist, |x| {
Some(extract_allowed_paths(x, true, "file"))
})
.parse_key("surrealdb_user_agent", &mut self.surrealdb_user_agent)
.parse_key_option("surrealism_max_memory", &mut self.surrealism_max_memory)
.parse_key_option("surrealism_max_execution_time", &mut self.surrealism_max_execution_time)
.parse_key_option("surrealism_max_kv_entries", &mut self.surrealism_max_kv_entries)
.parse_key_option("surrealism_max_kv_value_bytes", &mut self.surrealism_max_kv_value_bytes)
.parse_key("surrealism_max_fs_bytes", &mut self.surrealism_max_fs_bytes)
.parse_key_with("surrealism_log_level", &mut self.surrealism_log_level, |s| {
Some(s.to_string())
});
}
}
pub static MEMORY_THRESHOLD: LazyLock<usize> = LazyLock::new(|| {
let n = std::env::var("SURREAL_MEMORY_THRESHOLD")
.map(|s| s.parse::<usize>().unwrap_or(0))
.unwrap_or(0);
match n {
default @ 0 => default,
specified => std::cmp::max(specified, 1024 * 1024),
}
});
pub static GENERATION_ALLOCATION_LIMIT: LazyLock<usize> = LazyLock::new(|| {
let n = std::env::var("SURREAL_GENERATION_ALLOCATION_LIMIT")
.map(|s| s.parse::<u32>().unwrap_or(20))
.unwrap_or(20);
2usize.pow(n.min(28))
});
pub static STRING_SIMILARITY_LIMIT: LazyLock<usize> =
lazy_env_parse!("SURREAL_STRING_SIMILARITY_LIMIT", usize, 16384);
pub static REGEX_SIZE_LIMIT: LazyLock<usize> =
lazy_env_parse!("SURREAL_REGEX_SIZE_LIMIT", usize, 10 * 1024 * 1024);
pub static REGEX_CACHE_SIZE: LazyLock<usize> =
lazy_env_parse!("SURREAL_REGEX_CACHE_SIZE", usize, 1_000);
pub static SURREALISM_MAX_POOL_SIZE: LazyLock<usize> =
lazy_env_parse!("SURREAL_SURREALISM_MAX_POOL_SIZE", usize, 8);
#[cfg(any(feature = "kv-mem", feature = "kv-rocksdb", feature = "kv-surrealkv"))]
#[cfg(not(target_family = "wasm"))]
pub static KVS_THREADPOOL_SIZE: LazyLock<usize> = LazyLock::new(|| {
let default = || {
let cores = num_cpus::get();
if cores >= 16 {
cores
} else {
16
}
};
const MINIMUM_OVERRIDE: usize = 4;
match std::env::var("SURREAL_KVS_THREADPOOL_SIZE") {
Err(_) => default(),
Ok(s) if s.is_empty() => default(),
Ok(s) => match s.parse::<usize>() {
Ok(n) if n >= MINIMUM_OVERRIDE => n,
Ok(n) => {
tracing::warn!(
target: "surrealdb::kvs::threadpool",
"SURREAL_KVS_THREADPOOL_SIZE={n} is below the minimum of {MINIMUM_OVERRIDE}; using default",
);
default()
}
Err(_) => {
tracing::warn!(
target: "surrealdb::kvs::threadpool",
"SURREAL_KVS_THREADPOOL_SIZE={s:?} is not a valid integer; using default",
);
default()
}
},
}
});