use super::definition::{EnumOption, ParameterDefinition, ParameterFlags, ParameterKind};
use super::units::{parse_integer, show_integer};
use crate::SQLError;
const MAX_IDENTIFIER_BYTES: usize = 63;
pub fn parse_setting(definition: &ParameterDefinition, raw: &str) -> Result<String, SQLError> {
parse_setting_with_context(
definition,
raw,
&definition.boot_setting(),
&definition.boot_setting(),
)
}
pub fn parse_setting_with_context(
definition: &ParameterDefinition,
raw: &str,
current: &str,
reset: &str,
) -> Result<String, SQLError> {
if definition.name == "DateStyle" {
return super::datestyle::parse_setting(definition, raw, current, reset);
}
if definition.name == "TimeZone" {
return super::timezone::parse_setting(definition, raw);
}
match definition.kind {
ParameterKind::Bool { .. } => parse_bool(raw)
.map(|value| if value { "on" } else { "off" }.to_string())
.ok_or_else(|| SQLError::Routine {
sqlstate: "22023".into(),
message: format!("parameter \"{}\" requires a Boolean value", definition.name),
}),
ParameterKind::Integer { min, max, unit, .. } => {
let value = parse_integer(raw, unit).map_err(|hint| SQLError::Diagnostic {
sqlstate: "22023".into(),
message: invalid_value_message(definition, raw),
detail: None,
hint: hint.map(str::to_string),
})?;
if value < min || value > max {
let unit = unit.map_or(String::new(), |unit| format!(" {}", unit.name()));
return Err(SQLError::Routine {
sqlstate: "22023".into(),
message: format!(
"{value}{unit} is outside the valid range for parameter \"{}\" ({min}{unit} .. {max}{unit})",
definition.name
),
});
}
Ok(value.to_string())
}
ParameterKind::Enum { options, .. } => options
.iter()
.find(|option| option.name.eq_ignore_ascii_case(raw))
.map(|option| enum_display(options, option.value).to_string())
.ok_or_else(|| SQLError::Diagnostic {
sqlstate: "22023".into(),
message: invalid_value_message(definition, raw),
detail: None,
hint: Some(format!(
"Available values: {}.",
listed_enum_values(options).join(", ")
)),
}),
ParameterKind::String { .. } => Ok(if definition.has_flag(ParameterFlags::IS_NAME) {
truncate_identifier(raw).to_string()
} else {
raw.to_string()
}),
}
}
pub fn name_truncation_notice(definition: &ParameterDefinition, raw: &str) -> Option<String> {
let truncated = truncate_identifier(raw);
(matches!(definition.kind, ParameterKind::String { .. })
&& definition.has_flag(ParameterFlags::IS_NAME)
&& truncated.len() < raw.len())
.then(|| format!("identifier \"{raw}\" will be truncated to \"{truncated}\""))
}
pub fn invalid_value_message(definition: &ParameterDefinition, raw: &str) -> String {
format!(
"invalid value for parameter \"{}\": \"{raw}\"",
definition.name
)
}
pub fn show_setting(definition: &ParameterDefinition, setting: &str) -> String {
match definition.kind {
ParameterKind::Integer { unit, .. } => setting
.parse::<i64>()
.map_or_else(|_| setting.to_string(), |value| show_integer(value, unit)),
ParameterKind::Bool { .. } | ParameterKind::Enum { .. } | ParameterKind::String { .. } => {
setting.to_string()
}
}
}
pub fn listed_enum_values(options: &[EnumOption]) -> Vec<&'static str> {
options
.iter()
.filter(|option| !option.hidden)
.map(|option| option.name)
.collect()
}
pub(super) fn enum_display(options: &[EnumOption], value: u8) -> &'static str {
options
.iter()
.find(|option| option.value == value)
.map(|option| option.name)
.expect("enumerated value has a name")
}
fn parse_bool(raw: &str) -> Option<bool> {
let lower = raw.to_ascii_lowercase();
let prefix_of = |word: &str| !lower.is_empty() && word.starts_with(lower.as_str());
match lower.as_bytes().first()? {
b't' if prefix_of("true") => Some(true),
b'f' if prefix_of("false") => Some(false),
b'y' if prefix_of("yes") => Some(true),
b'n' if prefix_of("no") => Some(false),
b'o' if lower.len() >= 2 && prefix_of("on") => Some(true),
b'o' if lower.len() >= 2 && prefix_of("off") => Some(false),
b'1' if lower.len() == 1 => Some(true),
b'0' if lower.len() == 1 => Some(false),
_ => None,
}
}
fn truncate_identifier(raw: &str) -> &str {
if raw.len() <= MAX_IDENTIFIER_BYTES {
return raw;
}
let mut end = MAX_IDENTIFIER_BYTES;
while !raw.is_char_boundary(end) {
end -= 1;
}
&raw[..end]
}
#[cfg(test)]
mod tests;