use std::sync::Arc;
use pgwire::api::results::{DataRowEncoder, QueryResponse, Response};
use pgwire::error::{ErrorInfo, PgWireError, PgWireResult};
use super::super::types::text_field;
use super::core::NodeDbPgHandler;
pub fn parse_current_setting(sql: &str) -> Option<(String, bool)> {
let trimmed = sql.trim().trim_end_matches(';').trim();
let trimmed_upper = trimmed.to_uppercase();
if !trimmed_upper.starts_with("SELECT ") {
return None;
}
let rest = trimmed[7..].trim();
let rest_upper = rest.to_uppercase();
if !rest_upper.starts_with("CURRENT_SETTING(") {
return None;
}
let paren = rest.find('(')?;
let close = rest.rfind(')')?;
if close <= paren + 1 {
return None;
}
let args = &rest[paren + 1..close];
let parts: Vec<&str> = args.split(',').map(str::trim).collect();
let (name_arg, missing_ok_arg) = match parts.as_slice() {
[name] => (*name, None),
[name, missing_ok] => (*name, Some(*missing_ok)),
_ => return None,
};
let name = strip_quotes(name_arg)?;
if name.is_empty() {
return None;
}
let missing_ok = match missing_ok_arg {
Some(v) => v.eq_ignore_ascii_case("true"),
None => false,
};
Some((name.to_lowercase(), missing_ok))
}
fn strip_quotes(s: &str) -> Option<String> {
let s = s.trim();
if s.len() >= 2 {
let first = s.as_bytes()[0];
let last = s.as_bytes()[s.len() - 1];
if first == b'\'' && last == b'\'' {
let inner = &s[1..s.len() - 1];
if !inner.contains('\'') {
return Some(inner.to_string());
}
}
}
None
}
impl NodeDbPgHandler {
pub(super) fn handle_current_setting(
&self,
addr: &std::net::SocketAddr,
sql: &str,
) -> PgWireResult<Vec<Response>> {
let (name, missing_ok) = match parse_current_setting(sql) {
Some(parsed) => parsed,
None => {
return Err(PgWireError::UserError(Box::new(ErrorInfo::new(
"ERROR".to_owned(),
"42601".to_owned(),
"syntax error: current_setting('name' [, missing_ok])".to_owned(),
))));
}
};
let schema = Arc::new(vec![text_field("current_setting")]);
let mut encoder = DataRowEncoder::new(schema.clone());
match self.resolve_guc(addr, &name) {
Ok(value) => {
encoder.encode_field(&value)?;
}
Err(e) => {
if missing_ok {
encoder.encode_field(&Option::<String>::None)?;
} else {
return Err(e);
}
}
}
let row = encoder.take_row();
Ok(vec![Response::Query(QueryResponse::new(
schema,
futures::stream::iter(vec![Ok(row)]),
))])
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_single_arg_form() {
assert_eq!(
parse_current_setting("SELECT current_setting('server_version_num')"),
Some(("server_version_num".to_string(), false))
);
}
#[test]
fn parses_missing_ok_true() {
assert_eq!(
parse_current_setting("SELECT current_setting('nodedb.foo', true)"),
Some(("nodedb.foo".to_string(), true))
);
}
#[test]
fn parses_missing_ok_false() {
assert_eq!(
parse_current_setting("SELECT current_setting('nodedb.foo', false)"),
Some(("nodedb.foo".to_string(), false))
);
}
#[test]
fn lowercases_setting_name() {
assert_eq!(
parse_current_setting("SELECT current_setting('SERVER_VERSION')"),
Some(("server_version".to_string(), false))
);
}
#[test]
fn is_case_insensitive_on_keyword() {
assert_eq!(
parse_current_setting("select CURRENT_SETTING('server_version')"),
Some(("server_version".to_string(), false))
);
}
#[test]
fn accepts_trailing_semicolon() {
assert_eq!(
parse_current_setting("SELECT current_setting('server_version');"),
Some(("server_version".to_string(), false))
);
}
#[test]
fn unclear_second_arg_defaults_to_false() {
assert_eq!(
parse_current_setting("SELECT current_setting('server_version', maybe)"),
Some(("server_version".to_string(), false))
);
}
#[test]
fn rejects_unrelated_select() {
assert_eq!(parse_current_setting("SELECT 1"), None);
assert_eq!(parse_current_setting("SELECT version()"), None);
}
#[test]
fn rejects_empty_arg() {
assert_eq!(parse_current_setting("SELECT current_setting()"), None);
assert_eq!(parse_current_setting("SELECT current_setting('')"), None);
}
}