uqa_sql/semantics/parameters/
datestyle.rs1use super::{definition::ParameterDefinition, identifier_list::split_identifier_list};
10use crate::SQLError;
11use uqa_core::TemporalDateOrder;
12
13#[derive(Clone, Copy, Default)]
14struct DateStyle {
15 style: Style,
16 order: TemporalDateOrder,
17}
18
19#[derive(Clone, Copy, Default, PartialEq, Eq)]
20enum Style {
21 #[default]
22 Iso,
23 Sql,
24 Postgres,
25 German,
26}
27
28impl DateStyle {
29 fn canonical(setting: &str) -> Self {
30 let mut value = Self::default();
31 for token in setting.split(',').map(str::trim) {
32 match token {
33 "ISO" => value.style = Style::Iso,
34 "SQL" => value.style = Style::Sql,
35 "Postgres" => value.style = Style::Postgres,
36 "German" => value.style = Style::German,
37 "MDY" => value.order = TemporalDateOrder::MonthDayYear,
38 "DMY" => value.order = TemporalDateOrder::DayMonthYear,
39 "YMD" => value.order = TemporalDateOrder::YearMonthDay,
40 _ => {}
41 }
42 }
43 value
44 }
45
46 fn display(self) -> String {
47 let style = match self.style {
48 Style::Iso => "ISO",
49 Style::Sql => "SQL",
50 Style::Postgres => "Postgres",
51 Style::German => "German",
52 };
53 let order = match self.order {
54 TemporalDateOrder::MonthDayYear => "MDY",
55 TemporalDateOrder::DayMonthYear => "DMY",
56 TemporalDateOrder::YearMonthDay => "YMD",
57 };
58 format!("{style}, {order}")
59 }
60}
61
62#[must_use]
64pub fn date_order(setting: &str) -> TemporalDateOrder {
65 DateStyle::canonical(setting).order
66}
67
68pub(super) fn parse_setting(
69 definition: &ParameterDefinition,
70 raw: &str,
71 current: &str,
72 reset: &str,
73) -> Result<String, SQLError> {
74 let error = |detail: String| SQLError::Diagnostic {
75 sqlstate: "22023".into(),
76 message: super::value::invalid_value_message(definition, raw),
77 detail: Some(detail),
78 hint: None,
79 };
80 let tokens =
81 split_identifier_list(raw, b',').ok_or_else(|| error("List syntax is invalid.".into()))?;
82 let mut value = DateStyle::canonical(current);
83 let reset = DateStyle::canonical(reset);
84 let mut have_style = false;
85 let mut have_order = false;
86 let mut conflict = false;
87 for token in tokens {
88 let lowered = token.to_ascii_lowercase();
89 let style = match lowered.as_str() {
90 "iso" => Some(Style::Iso),
91 "sql" => Some(Style::Sql),
92 "german" => Some(Style::German),
93 token if token.starts_with("postgres") => Some(Style::Postgres),
94 _ => None,
95 };
96 if let Some(style) = style {
97 conflict |= have_style && value.style != style;
98 value.style = style;
99 have_style = true;
100 if style == Style::German && !have_order {
101 value.order = TemporalDateOrder::DayMonthYear;
102 }
103 continue;
104 }
105 let order = match lowered.as_str() {
106 "ymd" => Some(TemporalDateOrder::YearMonthDay),
107 "dmy" => Some(TemporalDateOrder::DayMonthYear),
108 "mdy" | "us" => Some(TemporalDateOrder::MonthDayYear),
109 token if token.starts_with("euro") => Some(TemporalDateOrder::DayMonthYear),
110 token if token.starts_with("noneuro") => Some(TemporalDateOrder::MonthDayYear),
111 _ => None,
112 };
113 if let Some(order) = order {
114 conflict |= have_order && value.order != order;
115 value.order = order;
116 have_order = true;
117 } else if lowered == "default" {
118 if !have_style {
119 value.style = reset.style;
120 }
121 if !have_order {
122 value.order = reset.order;
123 }
124 } else {
125 return Err(error(format!("Unrecognized key word: \"{token}\".")));
126 }
127 }
128 if conflict {
129 return Err(error("Conflicting \"DateStyle\" specifications.".into()));
130 }
131 Ok(value.display())
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn assignments_preserve_unspecified_fields_and_use_reset_defaults() {
140 let definition = super::super::catalog::find_parameter("DateStyle").unwrap();
141 for (raw, current, reset, expected) in [
142 ("ISO", "SQL, YMD", "ISO, MDY", "ISO, YMD"),
143 ("German", "ISO, MDY", "ISO, MDY", "German, DMY"),
144 ("US, German", "ISO, YMD", "ISO, MDY", "German, MDY"),
145 ("DEFAULT, YMD", "SQL, MDY", "German, DMY", "German, YMD"),
146 ("SQL, DEFAULT", "ISO, MDY", "German, DMY", "SQL, DMY"),
147 ("european", "ISO, MDY", "ISO, MDY", "ISO, DMY"),
148 ("", "ISO, YMD", "ISO, MDY", "ISO, YMD"),
149 ] {
150 assert_eq!(
151 parse_setting(definition, raw, current, reset).unwrap(),
152 expected
153 );
154 }
155 for (raw, detail) in [
156 ("MDY,DMY", "Conflicting \"DateStyle\" specifications."),
157 ("ISO,", "List syntax is invalid."),
158 ("bogus", "Unrecognized key word: \"bogus\"."),
159 ] {
160 let error = parse_setting(definition, raw, "ISO, MDY", "ISO, MDY").unwrap_err();
161 assert_eq!(error.sqlstate(), Some("22023"));
162 assert_eq!(error.detail(), Some(detail));
163 }
164 }
165}