uqa_sql/semantics/parameters/
value.rs1use super::definition::{EnumOption, ParameterDefinition, ParameterFlags, ParameterKind};
10use super::units::{parse_integer, show_integer};
11use crate::SQLError;
12
13const MAX_IDENTIFIER_BYTES: usize = 63;
15
16pub fn parse_setting(definition: &ParameterDefinition, raw: &str) -> Result<String, SQLError> {
18 parse_setting_with_context(
19 definition,
20 raw,
21 &definition.boot_setting(),
22 &definition.boot_setting(),
23 )
24}
25
26pub fn parse_setting_with_context(
28 definition: &ParameterDefinition,
29 raw: &str,
30 current: &str,
31 reset: &str,
32) -> Result<String, SQLError> {
33 if definition.name == "DateStyle" {
34 return super::datestyle::parse_setting(definition, raw, current, reset);
35 }
36 if definition.name == "TimeZone" {
37 return super::timezone::parse_setting(definition, raw);
38 }
39 match definition.kind {
40 ParameterKind::Bool { .. } => parse_bool(raw)
41 .map(|value| if value { "on" } else { "off" }.to_string())
42 .ok_or_else(|| SQLError::Routine {
43 sqlstate: "22023".into(),
44 message: format!("parameter \"{}\" requires a Boolean value", definition.name),
45 }),
46 ParameterKind::Integer { min, max, unit, .. } => {
47 let value = parse_integer(raw, unit).map_err(|hint| SQLError::Diagnostic {
48 sqlstate: "22023".into(),
49 message: invalid_value_message(definition, raw),
50 detail: None,
51 hint: hint.map(str::to_string),
52 })?;
53 if value < min || value > max {
54 let unit = unit.map_or(String::new(), |unit| format!(" {}", unit.name()));
55 return Err(SQLError::Routine {
56 sqlstate: "22023".into(),
57 message: format!(
58 "{value}{unit} is outside the valid range for parameter \"{}\" ({min}{unit} .. {max}{unit})",
59 definition.name
60 ),
61 });
62 }
63 Ok(value.to_string())
64 }
65 ParameterKind::Enum { options, .. } => options
66 .iter()
67 .find(|option| option.name.eq_ignore_ascii_case(raw))
68 .map(|option| enum_display(options, option.value).to_string())
69 .ok_or_else(|| SQLError::Diagnostic {
70 sqlstate: "22023".into(),
71 message: invalid_value_message(definition, raw),
72 detail: None,
73 hint: Some(format!(
74 "Available values: {}.",
75 listed_enum_values(options).join(", ")
76 )),
77 }),
78 ParameterKind::String { .. } => Ok(if definition.has_flag(ParameterFlags::IS_NAME) {
79 truncate_identifier(raw).to_string()
80 } else {
81 raw.to_string()
82 }),
83 }
84}
85
86pub fn name_truncation_notice(definition: &ParameterDefinition, raw: &str) -> Option<String> {
88 let truncated = truncate_identifier(raw);
89 (matches!(definition.kind, ParameterKind::String { .. })
90 && definition.has_flag(ParameterFlags::IS_NAME)
91 && truncated.len() < raw.len())
92 .then(|| format!("identifier \"{raw}\" will be truncated to \"{truncated}\""))
93}
94
95pub fn invalid_value_message(definition: &ParameterDefinition, raw: &str) -> String {
97 format!(
98 "invalid value for parameter \"{}\": \"{raw}\"",
99 definition.name
100 )
101}
102
103pub fn show_setting(definition: &ParameterDefinition, setting: &str) -> String {
105 match definition.kind {
106 ParameterKind::Integer { unit, .. } => setting
107 .parse::<i64>()
108 .map_or_else(|_| setting.to_string(), |value| show_integer(value, unit)),
109 ParameterKind::Bool { .. } | ParameterKind::Enum { .. } | ParameterKind::String { .. } => {
110 setting.to_string()
111 }
112 }
113}
114
115pub fn listed_enum_values(options: &[EnumOption]) -> Vec<&'static str> {
117 options
118 .iter()
119 .filter(|option| !option.hidden)
120 .map(|option| option.name)
121 .collect()
122}
123
124pub(super) fn enum_display(options: &[EnumOption], value: u8) -> &'static str {
126 options
127 .iter()
128 .find(|option| option.value == value)
129 .map(|option| option.name)
130 .expect("enumerated value has a name")
131}
132
133fn parse_bool(raw: &str) -> Option<bool> {
135 let lower = raw.to_ascii_lowercase();
136 let prefix_of = |word: &str| !lower.is_empty() && word.starts_with(lower.as_str());
137 match lower.as_bytes().first()? {
138 b't' if prefix_of("true") => Some(true),
139 b'f' if prefix_of("false") => Some(false),
140 b'y' if prefix_of("yes") => Some(true),
141 b'n' if prefix_of("no") => Some(false),
142 b'o' if lower.len() >= 2 && prefix_of("on") => Some(true),
143 b'o' if lower.len() >= 2 && prefix_of("off") => Some(false),
144 b'1' if lower.len() == 1 => Some(true),
145 b'0' if lower.len() == 1 => Some(false),
146 _ => None,
147 }
148}
149
150fn truncate_identifier(raw: &str) -> &str {
152 if raw.len() <= MAX_IDENTIFIER_BYTES {
153 return raw;
154 }
155 let mut end = MAX_IDENTIFIER_BYTES;
156 while !raw.is_char_boundary(end) {
157 end -= 1;
158 }
159 &raw[..end]
160}
161
162#[cfg(test)]
163mod tests;