use alloc::string::String;
use spg_storage::ColumnSchema;
use crate::Engine;
use crate::eval::EvalContext;
pub(crate) const CURRENT_ROLE_KEY: &str = "__spg_current_role";
pub(crate) const SESSION_USER_KEY: &str = "__spg_session_user";
pub(crate) const SESSION_AUTHENTICATED_KEY: &str = "__spg_session_authenticated";
pub(crate) const LOGIN_ROLE: &str = "admin";
pub(crate) const BOOTSTRAP_ROLE: &str = "postgres";
pub(crate) fn guc_name_accepted_beyond_pg(key: &str) -> bool {
key.contains('.')
|| key.starts_with("__spg")
|| matches!(
key,
"sql_mode"
| "foreign_key_checks"
| "unique_checks"
| "autocommit"
| "names"
| "character_set_client"
| "character_set_connection"
| "character_set_results"
| "collation_connection"
| "sql_quote_show_create"
| "sql_notes"
| "time_zone"
| "sql_safe_updates"
| "innodb_strict_mode"
| "net_write_timeout"
| "net_read_timeout"
| "wait_timeout"
| "interactive_timeout"
| "max_allowed_packet"
| "group_concat_max_len"
| "old_alter_table"
| "sql_log_bin"
| "session_replication_role"
)
}
pub(crate) fn guc_name_known(key: &str) -> bool {
guc_name_accepted_beyond_pg(key) || crate::guc_catalog::guc_context(key).is_some()
}
impl Engine {
#[must_use]
pub(crate) fn session_user(&self) -> &str {
self.session_params
.get(SESSION_USER_KEY)
.map_or(LOGIN_ROLE, String::as_str)
}
pub fn set_session_user(&mut self, user: &str) {
self.session_params
.insert(String::from(SESSION_USER_KEY), String::from(user));
}
pub fn set_session_authenticated(&mut self) {
self.session_params
.insert(String::from(SESSION_AUTHENTICATED_KEY), String::from("1"));
}
#[must_use]
pub(crate) fn session_is_authenticated(&self) -> bool {
self.session_params.contains_key(SESSION_AUTHENTICATED_KEY)
}
#[must_use]
pub(crate) fn current_role(&self) -> &str {
self.session_params
.get(CURRENT_ROLE_KEY)
.map_or_else(|| self.session_user(), String::as_str)
}
#[must_use]
pub(crate) fn is_superuser(&self) -> bool {
match self.session_params.get(CURRENT_ROLE_KEY) {
Some(r) => self.role_is_superuser(r),
None if self.session_is_authenticated() => self.role_is_superuser(self.session_user()),
None => true,
}
}
pub(crate) fn role_is_superuser(&self, role: &str) -> bool {
role.eq_ignore_ascii_case(LOGIN_ROLE)
|| role.eq_ignore_ascii_case(BOOTSTRAP_ROLE)
|| self.users.get(role).is_some_and(|rec| rec.superuser)
}
pub(crate) fn reject_unsettable_guc(&self, name: &str) -> Option<alloc::string::String> {
let key = name.to_ascii_lowercase();
if guc_name_accepted_beyond_pg(&key) {
return None;
}
match crate::guc_catalog::guc_context(&key) {
None => Some(alloc::format!(
"unrecognized configuration parameter \"{key}\""
)),
Some("internal") => Some(alloc::format!("parameter \"{key}\" cannot be changed")),
Some("postmaster") => Some(alloc::format!(
"parameter \"{key}\" cannot be changed without restarting the server"
)),
Some("sighup") => Some(alloc::format!("parameter \"{key}\" cannot be changed now")),
Some(_) => None,
}
}
pub(crate) fn clear_session_param(&mut self, name: &str) {
let key = name.to_ascii_lowercase();
if key.contains('.') {
self.session_params
.insert(key, alloc::string::String::new());
} else {
self.session_params.remove(&key);
}
self.refresh_render_style();
}
pub(crate) fn set_session_param(&mut self, name: String, value: spg_sql::ast::SetValue) {
let normalised = match value {
spg_sql::ast::SetValue::String(s) => s,
spg_sql::ast::SetValue::Ident(s) => s,
spg_sql::ast::SetValue::Number(s) => s,
spg_sql::ast::SetValue::Default => {
self.clear_session_param(&name);
return;
}
};
let key = name.to_ascii_lowercase();
let value_off = matches!(
normalised.to_ascii_lowercase().as_str(),
"0" | "off" | "false"
);
let value_on = matches!(
normalised.to_ascii_lowercase().as_str(),
"1" | "on" | "true"
);
if key == "names" {
for k in [
"character_set_client",
"character_set_connection",
"character_set_results",
] {
self.session_params
.insert(String::from(k), normalised.clone());
}
if let Some(coll) = crate::collate::charset_default_collation(&normalised) {
self.session_params
.insert(String::from("collation_connection"), String::from(coll));
}
self.refresh_render_style();
return;
}
if key == "foreign_key_checks"
|| key == "session_replication_role" && normalised.eq_ignore_ascii_case("replica")
{
if value_off || key == "session_replication_role" {
self.foreign_key_checks = false;
} else if value_on
|| (key == "session_replication_role" && normalised.eq_ignore_ascii_case("origin"))
{
self.foreign_key_checks = true;
let _ = self.drain_pending_foreign_keys();
}
}
let new_escapes = if key == "sql_mode" {
Some(
!normalised
.to_ascii_uppercase()
.contains("NO_BACKSLASH_ESCAPES"),
)
} else if key == "standard_conforming_strings" {
Some(value_off)
} else {
None
};
if let Some(flag) = new_escapes
&& flag != self.backslash_escapes
{
self.backslash_escapes = flag;
self.plan_cache.clear();
}
if key == "sql_mode" {
let upper = normalised.to_ascii_uppercase();
self.mysql_strict =
upper.contains("STRICT_TRANS_TABLES") || upper.contains("STRICT_ALL_TABLES");
self.speaks_mysql = true;
self.refresh_name_folding();
self.mysql_only_full_group_by = upper.contains("ONLY_FULL_GROUP_BY");
let ansi = upper.contains("ANSI_QUOTES");
if ansi != self.mysql_ansi_quotes {
self.mysql_ansi_quotes = ansi;
self.plan_cache.clear();
}
}
let normalised = match guc_unit(key.as_str()) {
Some("ms") => match parse_pg_duration_ms(&normalised) {
Some(ms) => render_pg_duration_ms(ms),
None => normalised,
},
Some(_) => match parse_pg_mem_kb(&normalised) {
Some(kb) => render_pg_mem_kb(kb),
None => normalised,
},
None => normalised,
};
let normalised = if key == "datestyle" {
match parse_datestyle_parts(&normalised, self.render_style) {
Some((st, ord)) => String::from(datestyle_canonical(st, ord)),
None => normalised,
}
} else {
normalised
};
let is_render_guc = matches!(
key.as_str(),
"datestyle" | "intervalstyle" | "extra_float_digits" | "bytea_output"
);
self.session_params.insert(key, normalised);
if is_render_guc {
self.refresh_render_style();
}
}
pub(crate) fn refresh_render_style(&mut self) {
let mut style = crate::eval::RenderStyle::default();
if let Some(ds) = self.session_param("datestyle")
&& let Some((st, ord)) = parse_datestyle_parts(ds, style)
{
style.date_style = st;
style.date_order = ord;
}
if let Some(is) = self.session_param("intervalstyle")
&& let Some(k) = parse_intervalstyle(is)
{
style.interval_style = k;
}
if let Some(efd) = self.session_param("extra_float_digits")
&& let Ok(n) = efd.trim().parse::<i32>()
{
style.extra_float_digits = n;
}
if let Some(bo) = self.session_param("bytea_output") {
style.bytea_escape = bo.trim().eq_ignore_ascii_case("escape");
}
self.render_style = style;
}
pub(crate) fn canonicalize_timezone(&self, value: &str) -> Result<String, crate::EngineError> {
let v = value.trim();
if v.eq_ignore_ascii_case("utc") || v.eq_ignore_ascii_case("gmt") {
return Ok(v.to_ascii_uppercase());
}
if crate::eval::datetime_resolve_zone_offset(v).is_some() {
return Ok(String::from(v));
}
match self.tz_canon_fn {
Some(f) => match f(v) {
Some(canon) => Ok(canon),
None => Err(crate::EngineError::Unsupported(alloc::format!(
"invalid value for parameter \"TimeZone\": \"{v}\""
))),
},
None => Ok(String::from(v)),
}
}
#[must_use]
pub fn render_style(&self) -> crate::eval::RenderStyle {
self.render_style
}
pub fn apply_db_role_settings(&mut self, database: &str, role: &str) {
let scopes: alloc::vec::Vec<(alloc::string::String, alloc::string::String)> = alloc::vec![
(alloc::string::String::new(), alloc::string::String::new()),
(
alloc::string::String::from(database),
alloc::string::String::new()
),
(
alloc::string::String::new(),
alloc::string::String::from(role)
),
(
alloc::string::String::from(database),
alloc::string::String::from(role)
),
];
let mut apply: alloc::vec::Vec<(alloc::string::String, alloc::string::String)> =
alloc::vec::Vec::new();
for key in &scopes {
if let Some(params) = self.active_catalog().db_role_settings().get(key) {
for (k, v) in params {
apply.push((k.clone(), v.clone()));
}
}
}
for (k, v) in apply {
let _ = self.execute(&alloc::format!("SET {k} = '{v}'"));
}
}
#[must_use]
pub fn session_tz(&self) -> crate::SessionTz {
let Some(z) = self.session_param("timezone") else {
return crate::SessionTz::Utc;
};
if z.eq_ignore_ascii_case("utc") || z.eq_ignore_ascii_case("gmt") {
return crate::SessionTz::Utc;
}
if let Some(off) = crate::eval::datetime_resolve_zone_offset(z) {
return if off == 0 {
crate::SessionTz::Utc
} else {
crate::SessionTz::Fixed(off)
};
}
match (self.tz_offset_fn, self.tz_abbrev_fn) {
(Some(of), Some(af)) => crate::SessionTz::Named(String::from(z), of, af),
_ => crate::SessionTz::Utc,
}
}
#[must_use]
pub fn session_param(&self, name: &str) -> Option<&str> {
let lower = name.to_ascii_lowercase();
if lower == "transaction_isolation" {
return Some(self.current_isolation_level.as_pg_str());
}
self.session_params.get(&lower).map(String::as_str)
}
pub(crate) fn notice(&mut self, text: alloc::string::String) {
self.pending_notices.push(crate::Notice {
severity: crate::NoticeSeverity::Notice,
message: text,
});
}
pub(crate) fn reset_all_gucs(&mut self) {
let keep: alloc::vec::Vec<(String, String)> = [SESSION_USER_KEY, "spg.database"]
.iter()
.filter_map(|k| {
self.session_params
.get(*k)
.map(|v| (String::from(*k), v.clone()))
})
.collect();
self.session_params.clear();
for (k, v) in keep {
self.session_params.insert(k, v);
}
}
pub(crate) fn warning(&mut self, text: alloc::string::String) {
self.pending_notices.push(crate::Notice {
severity: crate::NoticeSeverity::Warning,
message: text,
});
}
pub(crate) fn drain_raise_sink(&mut self, sink: crate::triggers::NoticeSink) {
self.queue_raised(sink.into_inner());
}
pub(crate) fn queue_raised(
&mut self,
raised: alloc::vec::Vec<(crate::NoticeSeverity, alloc::string::String)>,
) {
for (severity, message) in raised {
if self.notice_severity_reaches_client(severity) {
self.pending_notices
.push(crate::Notice { severity, message });
}
}
}
#[must_use]
pub fn take_notices(&mut self) -> alloc::vec::Vec<crate::Notice> {
core::mem::take(&mut self.pending_notices)
}
#[must_use]
pub fn session_statement_timeout_ms(&self) -> Option<u64> {
let raw = self.session_param("statement_timeout")?;
parse_pg_duration_ms(raw).filter(|ms| *ms > 0)
}
#[must_use]
pub fn session_work_mem_bytes(&self) -> usize {
const DEFAULT_KB: usize = 4 * 1024;
let kb = self
.session_param("work_mem")
.and_then(parse_pg_mem_kb)
.and_then(|kb| usize::try_from(kb).ok())
.filter(|kb| *kb > 0)
.unwrap_or(DEFAULT_KB);
kb.saturating_mul(1024)
}
#[must_use]
pub fn notice_severity_reaches_client(&self, severity: crate::NoticeSeverity) -> bool {
fn rank(s: &str) -> u8 {
match s {
"debug5" => 0,
"debug4" => 1,
"debug3" => 2,
"debug2" => 3,
"debug1" => 4,
"log" => 5,
"notice" => 6,
"warning" => 7,
"error" => 8,
"fatal" => 9,
"panic" => 10,
_ => 6,
}
}
let setting = self
.session_param("client_min_messages")
.map_or(6, |v| rank(&v.trim().to_ascii_lowercase()));
let own = match severity {
crate::NoticeSeverity::Notice => 6,
crate::NoticeSeverity::Warning => 7,
crate::NoticeSeverity::Info => return true,
};
own >= setting
}
pub(crate) fn session_tz_offset_at(&self, utc_micros: i64) -> i64 {
let Some(zone) = self.session_params.get("timezone") else {
return 0;
};
if zone.eq_ignore_ascii_case("utc") || zone.eq_ignore_ascii_case("gmt") {
return 0;
}
if let Some(off) = crate::eval::resolve_zone_offset_pub(zone) {
return off;
}
self.tz_offset_fn
.and_then(|f| f(zone, utc_micros))
.unwrap_or(0)
}
pub(crate) fn dml_session(&self) -> crate::eval::DmlSession {
crate::eval::DmlSession {
gucs: self.session_params.clone(),
users: self.users.clone(),
render_style: self.render_style,
tz_offset_fn: self.tz_offset_fn,
tz_localize_fn: self.tz_localize_fn,
tz_abbrev_fn: self.tz_abbrev_fn,
}
}
pub(crate) fn session_coercion(&self) -> Option<crate::eval::SessionCoercion> {
let zone = self
.session_params
.get("timezone")
.filter(|z| !z.eq_ignore_ascii_case("utc") && !z.eq_ignore_ascii_case("gmt"))
.cloned();
let order = self.render_style.date_order;
if zone.is_none() && order == crate::eval::DateOrder::Mdy {
return None;
}
Some(crate::eval::SessionCoercion {
zone,
localize: self.tz_localize_fn,
order,
})
}
pub(crate) fn ev_ctx<'a>(
&'a self,
columns: &'a [ColumnSchema],
alias: Option<&'a str>,
) -> EvalContext<'a> {
EvalContext::new(columns, alias)
.with_render_style(self.render_style)
.with_tz_fns(self.tz_offset_fn, self.tz_localize_fn, self.tz_abbrev_fn)
.with_default_text_search_config(self.session_param("default_text_search_config"))
.with_session_gucs(&self.session_params)
.with_users(&self.users)
.with_engine(self)
.with_catalog(self.active_catalog())
.with_salt_fn(self.salt_fn)
.with_backend_pid_fn(self.backend_pid_fn)
.with_wal_lsn_fn(self.wal_lsn_fn)
.with_backend_signal_fn(self.backend_signal_fn)
.with_clock(self.clock)
.with_xact(self.xact_view())
}
pub(crate) fn xact_view(&self) -> crate::eval::XactView<'_> {
crate::eval::XactView {
current: self
.current_tx
.and_then(|t| self.tx_writer_versions.get(&t).copied())
.or(self.stmt_writer_version),
active: &self.active_writer_versions,
aborted: &self.aborted_versions,
}
}
}
pub(crate) fn parse_datestyle_parts(
value: &str,
current: crate::eval::RenderStyle,
) -> Option<(crate::eval::DateStyleKind, crate::eval::DateOrder)> {
use crate::eval::{DateOrder, DateStyleKind};
let mut st = current.date_style;
let mut ord = current.date_order;
let mut any = false;
for part in value.split(',') {
let p = part.trim().to_ascii_lowercase();
match p.as_str() {
"iso" => st = DateStyleKind::Iso,
"german" => {
st = DateStyleKind::German;
ord = DateOrder::Dmy;
}
"sql" => st = DateStyleKind::Sql,
"postgres" => st = DateStyleKind::Postgres,
"mdy" | "us" | "noneuro" | "noneuropean" => ord = DateOrder::Mdy,
"dmy" | "euro" | "european" => ord = DateOrder::Dmy,
"ymd" => ord = DateOrder::Ymd,
_ => return None,
}
any = true;
}
if any { Some((st, ord)) } else { None }
}
pub(crate) fn datestyle_canonical(
st: crate::eval::DateStyleKind,
ord: crate::eval::DateOrder,
) -> &'static str {
use crate::eval::{DateOrder, DateStyleKind};
match (st, ord) {
(DateStyleKind::Iso, DateOrder::Mdy) => "ISO, MDY",
(DateStyleKind::Iso, DateOrder::Dmy) => "ISO, DMY",
(DateStyleKind::Iso, DateOrder::Ymd) => "ISO, YMD",
(DateStyleKind::German, DateOrder::Mdy) => "German, MDY",
(DateStyleKind::German, DateOrder::Dmy) => "German, DMY",
(DateStyleKind::German, DateOrder::Ymd) => "German, YMD",
(DateStyleKind::Sql, DateOrder::Mdy) => "SQL, MDY",
(DateStyleKind::Sql, DateOrder::Dmy) => "SQL, DMY",
(DateStyleKind::Sql, DateOrder::Ymd) => "SQL, YMD",
(DateStyleKind::Postgres, DateOrder::Mdy) => "Postgres, MDY",
(DateStyleKind::Postgres, DateOrder::Dmy) => "Postgres, DMY",
(DateStyleKind::Postgres, DateOrder::Ymd) => "Postgres, YMD",
}
}
pub(crate) fn parse_intervalstyle(value: &str) -> Option<crate::eval::IntervalStyleKind> {
use crate::eval::IntervalStyleKind as K;
match value.trim().to_ascii_lowercase().as_str() {
"postgres" => Some(K::Postgres),
"sql_standard" => Some(K::SqlStandard),
"iso_8601" => Some(K::Iso8601),
"postgres_verbose" => Some(K::PostgresVerbose),
_ => None,
}
}
pub(crate) fn parse_pg_duration_ms(raw: &str) -> Option<u64> {
let s = raw.trim();
if s.is_empty() {
return None;
}
let lowered = s.to_ascii_lowercase();
let (num_part, multiplier_ms): (&str, u64) = if let Some(p) = lowered.strip_suffix("ms") {
(p, 1)
} else if let Some(p) = lowered.strip_suffix("min") {
(p, 60_000)
} else if let Some(p) = lowered.strip_suffix('s') {
(p, 1_000)
} else if let Some(p) = lowered.strip_suffix('h') {
(p, 3_600_000)
} else if let Some(p) = lowered.strip_suffix('d') {
(p, 86_400_000)
} else {
(lowered.as_str(), 1)
};
let n: u64 = num_part.trim().parse().ok()?;
n.checked_mul(multiplier_ms)
}
fn render_pg_duration_ms(ms: u64) -> String {
use alloc::format;
if ms == 0 {
return String::from("0");
}
if ms % 86_400_000 == 0 {
format!("{}d", ms / 86_400_000)
} else if ms % 3_600_000 == 0 {
format!("{}h", ms / 3_600_000)
} else if ms % 60_000 == 0 {
format!("{}min", ms / 60_000)
} else if ms % 1_000 == 0 {
format!("{}s", ms / 1_000)
} else {
format!("{ms}ms")
}
}
pub(crate) fn guc_unit(name: &str) -> Option<&'static str> {
match name {
"statement_timeout"
| "lock_timeout"
| "idle_in_transaction_session_timeout"
| "idle_session_timeout"
| "transaction_timeout" => Some("ms"),
"work_mem" | "maintenance_work_mem" => Some("kB"),
"shared_buffers" | "temp_buffers" | "effective_cache_size" | "wal_buffers" => Some("8kB"),
_ => None,
}
}
pub(crate) fn guc_raw_setting(name: &str, stored: &str) -> Option<String> {
match guc_unit(name)? {
"ms" => parse_pg_duration_ms(stored).map(|ms| alloc::format!("{ms}")),
"kB" => parse_pg_mem_kb(stored).map(|kb| alloc::format!("{kb}")),
"8kB" => parse_pg_mem_kb(stored).map(|kb| alloc::format!("{}", kb / 8)),
_ => None,
}
}
pub(crate) fn parse_pg_mem_kb(raw: &str) -> Option<u64> {
let s = raw.trim();
if s.is_empty() {
return None;
}
let lowered = s.to_ascii_lowercase();
let (num_part, mult_kb): (&str, u64) = if let Some(p) = lowered.strip_suffix("tb") {
(p, 1024 * 1024 * 1024)
} else if let Some(p) = lowered.strip_suffix("gb") {
(p, 1024 * 1024)
} else if let Some(p) = lowered.strip_suffix("mb") {
(p, 1024)
} else if let Some(p) = lowered.strip_suffix("kb") {
(p, 1)
} else if let Some(p) = lowered.strip_suffix('b') {
let n: u64 = p.trim().parse().ok()?;
return if n % 1024 == 0 { Some(n / 1024) } else { None };
} else {
(lowered.as_str(), 1)
};
let n: u64 = num_part.trim().parse().ok()?;
n.checked_mul(mult_kb)
}
fn render_pg_mem_kb(kb: u64) -> String {
use alloc::format;
if kb == 0 {
return String::from("0");
}
if kb % (1024 * 1024) == 0 {
format!("{}GB", kb / (1024 * 1024))
} else if kb % 1024 == 0 {
format!("{}MB", kb / 1024)
} else {
format!("{kb}kB")
}
}
#[cfg(test)]
mod tests {
use super::parse_pg_duration_ms;
use alloc::format;
#[test]
fn parse_bare_digits_treats_as_ms() {
assert_eq!(parse_pg_duration_ms("100"), Some(100));
assert_eq!(parse_pg_duration_ms("0"), Some(0));
assert_eq!(parse_pg_duration_ms("60000"), Some(60_000));
}
#[test]
fn parse_ms_suffix() {
assert_eq!(parse_pg_duration_ms("100ms"), Some(100));
assert_eq!(parse_pg_duration_ms("100 ms"), Some(100));
}
#[test]
fn parse_seconds() {
assert_eq!(parse_pg_duration_ms("1s"), Some(1_000));
assert_eq!(parse_pg_duration_ms("30s"), Some(30_000));
}
#[test]
fn parse_minutes_uses_three_letter_suffix() {
assert_eq!(parse_pg_duration_ms("5min"), Some(300_000));
assert_eq!(parse_pg_duration_ms("5m"), None);
}
#[test]
fn parse_invalid_returns_none() {
assert_eq!(parse_pg_duration_ms(""), None);
assert_eq!(parse_pg_duration_ms("abc"), None);
assert_eq!(parse_pg_duration_ms("100x"), None);
}
#[test]
fn parse_handles_whitespace() {
assert_eq!(parse_pg_duration_ms(" 100 "), Some(100));
}
#[test]
fn parse_overflow_returns_none() {
assert_eq!(parse_pg_duration_ms(&format!("{}s", u64::MAX)), None);
}
#[cfg(test)]
mod session_integration {
use crate::Engine;
use spg_sql::ast::SetValue;
#[test]
fn set_statement_timeout_round_trips_ms() {
let mut e = Engine::new();
e.set_session_param("statement_timeout".into(), SetValue::Number("250".into()));
assert_eq!(e.session_statement_timeout_ms(), Some(250));
}
#[test]
fn set_statement_timeout_zero_is_none() {
let mut e = Engine::new();
e.set_session_param("statement_timeout".into(), SetValue::Number("0".into()));
assert_eq!(e.session_statement_timeout_ms(), None);
}
#[test]
fn statement_timeout_unset_is_none() {
let e = Engine::new();
assert_eq!(e.session_statement_timeout_ms(), None);
}
#[test]
fn statement_timeout_accepts_ms_suffix_via_string_set() {
let mut e = Engine::new();
e.set_session_param(
"statement_timeout".into(),
SetValue::String("1500ms".into()),
);
assert_eq!(e.session_statement_timeout_ms(), Some(1500));
}
#[test]
fn client_min_messages_gates_by_pg_severity_order() {
use crate::NoticeSeverity::{Notice, Warning};
let mut e = Engine::new();
assert!(e.notice_severity_reaches_client(Notice));
assert!(e.notice_severity_reaches_client(Warning));
e.execute("SET client_min_messages = warning").unwrap();
assert!(!e.notice_severity_reaches_client(Notice));
assert!(e.notice_severity_reaches_client(Warning));
for above in ["error", "fatal", "panic"] {
e.execute(&alloc::format!("SET client_min_messages = {above}"))
.unwrap();
assert!(!e.notice_severity_reaches_client(Notice), "{above}");
assert!(!e.notice_severity_reaches_client(Warning), "{above}");
}
for below in ["notice", "log", "debug1", "debug5"] {
e.execute(&alloc::format!("SET client_min_messages = {below}"))
.unwrap();
assert!(e.notice_severity_reaches_client(Notice), "{below}");
assert!(e.notice_severity_reaches_client(Warning), "{below}");
}
e.execute("SET client_min_messages = WARNING").unwrap();
assert!(!e.notice_severity_reaches_client(Notice));
e.execute("RESET client_min_messages").unwrap();
assert!(e.notice_severity_reaches_client(Notice));
assert!(e.execute("SET client_min_messages = bogus_zz").is_err());
}
}
#[test]
fn work_mem_reads_back_as_bytes() {
let mut e = crate::Engine::new();
assert_eq!(
e.session_work_mem_bytes(),
4 * 1024 * 1024,
"an untouched session gets PG's 4MB default"
);
e.execute("SET work_mem = '64MB'").unwrap();
assert_eq!(e.session_work_mem_bytes(), 64 * 1024 * 1024);
e.execute("SET work_mem = '65536'").unwrap();
assert_eq!(
e.session_work_mem_bytes(),
64 * 1024 * 1024,
"'65536' and '64MB' are the same setting and must be the same bytes"
);
e.execute("SET work_mem = '1024kB'").unwrap();
assert_eq!(e.session_work_mem_bytes(), 1024 * 1024);
}
#[test]
fn work_mem_that_cannot_be_read_falls_back_to_the_default() {
let mut e = crate::Engine::new();
let _ = e.execute("SET work_mem = 'not_a_size'");
assert_eq!(e.session_work_mem_bytes(), 4 * 1024 * 1024);
assert!(e.session_work_mem_bytes() > 0);
}
}