use std::net::SocketAddr;
use nodedb_sql::parser::preprocess::lex::find_ascii_case_insensitive;
use super::store::SessionStore;
impl SessionStore {
pub fn set_parameter(&self, addr: &SocketAddr, key: String, value: String) {
self.write_session(addr, |session| {
session.parameters.insert(key, value);
});
}
pub fn reset_parameter(&self, addr: &SocketAddr, key: &str) {
let defaults = super::state::default_parameters();
let default = defaults
.into_iter()
.find(|(name, _)| name.eq_ignore_ascii_case(key));
self.write_session(addr, |session| {
session
.parameters
.retain(|name, _| !name.eq_ignore_ascii_case(key));
if let Some((name, value)) = default {
session.parameters.insert(name, value);
}
});
}
pub fn reset_all_parameters(&self, addr: &SocketAddr) {
self.write_session(addr, |session| {
session.parameters = super::state::default_parameters();
session.effective_tenant_id = None;
});
}
pub fn get_parameter(&self, addr: &SocketAddr, key: &str) -> Option<String> {
self.read_session(addr, |session| {
session.parameters.get(key).cloned().or_else(|| {
session
.parameters
.iter()
.find(|(name, _)| name.eq_ignore_ascii_case(key))
.map(|(_, value)| value.clone())
})
})?
}
pub fn all_parameters(&self, addr: &SocketAddr) -> Vec<(String, String)> {
self.read_session(addr, |s| {
let mut params: Vec<_> = s
.parameters
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
params.sort_by(|a, b| a.0.cmp(&b.0));
params
})
.unwrap_or_default()
}
}
pub fn parse_set_command(sql: &str) -> Option<(String, String)> {
let trimmed = sql.trim();
let upper = trimmed.to_uppercase();
let rest = if upper.starts_with("SET SESSION ") {
&trimmed[12..]
} else if upper.starts_with("SET LOCAL ") {
&trimmed[10..]
} else if upper.starts_with("SET ") {
&trimmed[4..]
} else {
return None;
};
let rest = rest.trim();
let (key, value) = if let Some(eq_pos) = rest.find('=') {
let k = rest[..eq_pos].trim();
let v = rest[eq_pos + 1..].trim();
(k, v)
} else {
let to_pos = find_ascii_case_insensitive(rest, " TO ")?;
let k = rest[..to_pos].trim();
let v = rest[to_pos + 4..].trim();
(k, v)
};
if key.is_empty() {
return None;
}
let value = value.trim_matches('\'').trim_matches('"').to_string();
Some((key.to_lowercase(), value))
}
pub const KNOWN_PG_RUNTIME_PARAMETERS: &[&str] = &[
"all",
"application_name",
"client_encoding",
"client_min_messages",
"cross_shard_txn",
"datestyle",
"default_read_consistency",
"default_transaction_isolation",
"default_transaction_read_only",
"extra_float_digits",
"integer_datetimes",
"intervalstyle",
"is_superuser",
"lc_collate",
"lc_ctype",
"lc_messages",
"lc_monetary",
"lc_numeric",
"lc_time",
"server_encoding",
"server_version",
"server_version_num",
"search_path",
"session_authorization",
"standard_conforming_strings",
"statement_timeout",
"timezone",
"time zone",
"transaction_isolation",
"transaction_read_only",
"nodedb.consistency",
"nodedb.tenant_id",
"nodedb.force_shuffle_join",
"nodedb.shuffle_num_parts",
"nodedb.force_shuffle_agg",
"nodedb.shuffle_agg_num_parts",
"nodedb.broadcast_threshold_bytes",
"nodedb.shuffle_agg_threshold",
"rounding_mode",
];
pub const SETTABLE_RUNTIME_PARAMETERS: &[&str] = &[
"application_name",
"client_encoding",
"client_min_messages",
"datestyle",
"default_transaction_isolation",
"default_transaction_read_only",
"extra_float_digits",
"intervalstyle",
"lc_collate",
"lc_ctype",
"lc_messages",
"lc_monetary",
"lc_numeric",
"lc_time",
"search_path",
"standard_conforming_strings",
"statement_timeout",
"timezone",
"time zone",
"transaction_isolation",
"transaction_read_only",
"rounding_mode",
"tenant",
"role",
"session_authorization",
"nodedb.consistency",
"default_read_consistency",
"cross_shard_txn",
"nodedb.tenant_id",
"nodedb.auth_session",
"nodedb.force_shuffle_join",
"nodedb.shuffle_num_parts",
"nodedb.force_shuffle_agg",
"nodedb.shuffle_agg_num_parts",
"nodedb.broadcast_threshold_bytes",
"nodedb.shuffle_agg_threshold",
"cross_shard_txn",
];
pub fn is_known_settable_runtime_parameter(name: &str) -> bool {
let lower = name.to_lowercase();
SETTABLE_RUNTIME_PARAMETERS
.iter()
.any(|p| p.eq_ignore_ascii_case(&lower))
}
pub fn is_known_pg_runtime_parameter(name: &str) -> bool {
let lower = name.to_lowercase();
KNOWN_PG_RUNTIME_PARAMETERS
.iter()
.any(|p| p.eq_ignore_ascii_case(&lower))
}
pub fn parse_show_command(sql: &str) -> Option<String> {
let trimmed = sql.trim();
let upper = trimmed.to_uppercase();
if !upper.starts_with("SHOW ") {
return None;
}
let param = trimmed[5..].trim().to_lowercase();
if param.is_empty() {
return None;
}
Some(param)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reset_parameter_restores_canonical_defaults() {
let store = SessionStore::new();
let addr = "127.0.0.1:5000".parse().expect("socket address");
store.ensure_session(addr);
store.set_parameter(&addr, "datestyle".into(), "SQL, DMY".into());
store.reset_parameter(&addr, "datestyle");
assert_eq!(
store.get_parameter(&addr, "datestyle"),
Some("ISO, MDY".into())
);
store.set_parameter(&addr, "default_read_consistency".into(), "eventual".into());
store.reset_parameter(&addr, "default_read_consistency");
assert_eq!(
store.get_parameter(&addr, "default_read_consistency"),
Some("strong".into())
);
store.set_parameter(
&addr,
"cross_shard_txn".into(),
"best_effort_non_atomic".into(),
);
store.reset_parameter(&addr, "cross_shard_txn");
assert_eq!(
store.get_parameter(&addr, "cross_shard_txn"),
Some("strict".into())
);
}
#[test]
fn reset_all_parameters_restores_defaults() {
let store = SessionStore::new();
let addr = "127.0.0.1:5000".parse().expect("socket address");
store.ensure_session(addr);
store.set_parameter(&addr, "application_name".into(), "worker".into());
store.set_parameter(&addr, "nodedb.consistency".into(), "eventual".into());
store.reset_all_parameters(&addr);
assert_eq!(
store.get_parameter(&addr, "application_name"),
Some(String::new())
);
assert_eq!(
store.get_parameter(&addr, "nodedb.consistency"),
Some("strong".into())
);
}
#[test]
fn parse_set_equals() {
let (k, v) = parse_set_command("SET client_encoding = 'UTF8'").unwrap();
assert_eq!(k, "client_encoding");
assert_eq!(v, "UTF8");
}
#[test]
fn parse_set_to() {
let (k, v) = parse_set_command("SET search_path TO public").unwrap();
assert_eq!(k, "search_path");
assert_eq!(v, "public");
}
#[test]
fn parse_set_to_after_unicode_key_preserves_original_offsets() {
let (k, v) = parse_set_command("SET custom.ffff TO enabled").unwrap();
assert_eq!(k, "custom.ffff");
assert_eq!(v, "enabled");
}
#[test]
fn parse_set_session() {
let (k, v) = parse_set_command("SET SESSION nodedb.consistency = 'eventual'").unwrap();
assert_eq!(k, "nodedb.consistency");
assert_eq!(v, "eventual");
}
#[test]
fn parse_set_nodedb_tenant() {
let (k, v) = parse_set_command("SET nodedb.tenant_id = 5").unwrap();
assert_eq!(k, "nodedb.tenant_id");
assert_eq!(v, "5");
}
#[test]
fn parse_show() {
assert_eq!(
parse_show_command("SHOW client_encoding"),
Some("client_encoding".into())
);
assert_eq!(parse_show_command("SHOW ALL"), Some("all".into()));
assert_eq!(parse_show_command("SHOW"), None);
}
}