uqa_sql/expr/casting/
array.rs1use uqa_core::{ArrayValue, Value};
10
11use crate::error::{Result, SQLError};
12
13use super::cast_value_from;
14
15pub fn parse_pg_array_literal(text: &str) -> Result<ArrayValue> {
19 let mut parser = PgArrayLiteralParser::new(text);
20 let (declared_dimensions, items) = parser.parse()?;
21 if let Err(error) = array_shape(&items) {
22 return Err(SQLError::Routine {
23 sqlstate: "22P02".into(),
24 message: format!("malformed array literal: \"{text}\" ({})", error.message()),
25 });
26 }
27 let array = ArrayValue::try_new(items).ok_or_else(|| SQLError::Routine {
28 sqlstate: "22P02".into(),
29 message: format!("malformed array literal: \"{text}\""),
30 })?;
31 let Some(declared_dimensions) = declared_dimensions else {
32 return Ok(array);
33 };
34 let declared_lengths = declared_dimensions
35 .iter()
36 .map(|(_, length)| *length)
37 .collect::<Vec<_>>();
38 if declared_lengths != array.dimensions() {
39 return Err(SQLError::Routine {
40 sqlstate: "22P02".into(),
41 message: format!(
42 "malformed array literal: \"{text}\" (specified array dimensions do not match array contents)"
43 ),
44 });
45 }
46 let lower_bounds = declared_dimensions
47 .into_iter()
48 .map(|(lower, _)| lower)
49 .collect();
50 ArrayValue::with_lower_bounds(array.into_elements(), lower_bounds).ok_or_else(|| {
51 SQLError::Routine {
52 sqlstate: "22P02".into(),
53 message: format!("malformed array literal: \"{text}\""),
54 }
55 })
56}
57
58pub(super) fn cast_array_elements(
59 items: &[Value],
60 element_type: &str,
61 source_element_type: Option<&str>,
62) -> Result<Vec<Value>> {
63 items
64 .iter()
65 .map(|item| match item {
66 Value::List(nested) => {
67 cast_array_elements(nested, element_type, source_element_type).map(Value::List)
68 }
69 other => cast_value_from(other, element_type, source_element_type),
70 })
71 .collect()
72}
73
74pub(super) struct PgArrayLiteralParser<'a> {
75 source: &'a str,
76 chars: std::iter::Peekable<std::str::Chars<'a>>,
77}
78
79type ParsedArrayLiteral = (Option<Vec<(i32, usize)>>, Vec<Value>);
80
81impl<'a> PgArrayLiteralParser<'a> {
82 fn new(source: &'a str) -> Self {
83 Self {
84 source,
85 chars: source.chars().peekable(),
86 }
87 }
88
89 fn parse(&mut self) -> Result<ParsedArrayLiteral> {
90 self.skip_whitespace();
91 let dimensions = self.parse_dimension_declaration()?;
92 let items = self.parse_array()?;
93 self.skip_whitespace();
94 if self.chars.peek().is_some() {
95 return Err(self.error("unexpected content after closing brace"));
96 }
97 Ok((dimensions, items))
98 }
99
100 fn parse_dimension_declaration(&mut self) -> Result<Option<Vec<(i32, usize)>>> {
101 if self.chars.peek() != Some(&'[') {
102 return Ok(None);
103 }
104 let mut dimensions = Vec::new();
105 while self.chars.next_if_eq(&'[').is_some() {
106 self.skip_whitespace();
107 let lower = self.parse_dimension_bound()?;
108 self.skip_whitespace();
109 if self.chars.next() != Some(':') {
110 return Err(self.error("array dimension must contain `:`"));
111 }
112 self.skip_whitespace();
113 let upper = self.parse_dimension_bound()?;
114 self.skip_whitespace();
115 if self.chars.next() != Some(']') {
116 return Err(self.error("array dimension is missing a closing `]`"));
117 }
118 if upper == i32::MAX {
119 return Err(SQLError::Routine {
120 sqlstate: "54000".into(),
121 message: format!("array upper bound is too large: {upper}"),
122 });
123 }
124 if upper < lower {
125 return Err(SQLError::Routine {
126 sqlstate: "2202E".into(),
127 message: "upper bound cannot be less than lower bound".into(),
128 });
129 }
130 let length = i64::from(upper)
131 .checked_sub(i64::from(lower))
132 .and_then(|difference| difference.checked_add(1))
133 .and_then(|length| usize::try_from(length).ok())
134 .ok_or_else(|| self.error("array dimension is out of range"))?;
135 dimensions.push((lower, length));
136 self.skip_whitespace();
137 }
138 if self.chars.next() != Some('=') {
139 return Err(self.error("array dimensions must be followed by `=`"));
140 }
141 self.skip_whitespace();
142 Ok(Some(dimensions))
143 }
144
145 fn parse_dimension_bound(&mut self) -> Result<i32> {
146 let mut text = String::new();
147 if self
148 .chars
149 .peek()
150 .is_some_and(|character| matches!(character, '+' | '-'))
151 {
152 text.push(self.chars.next().expect("peeked array bound sign"));
153 }
154 while self.chars.peek().is_some_and(char::is_ascii_digit) {
155 text.push(self.chars.next().expect("peeked array bound digit"));
156 }
157 if text.is_empty() || matches!(text.as_str(), "+" | "-") {
158 return Err(self.error("array dimension bound must be an integer"));
159 }
160 text.parse()
161 .map_err(|_| self.error("array dimension bound is out of range"))
162 }
163
164 fn parse_array(&mut self) -> Result<Vec<Value>> {
165 if self.chars.next() != Some('{') {
166 return Err(self.error("array value must start with `{`"));
167 }
168 self.skip_whitespace();
169 if self.chars.next_if_eq(&'}').is_some() {
170 return Ok(Vec::new());
171 }
172
173 let mut items = Vec::new();
174 loop {
175 self.skip_whitespace();
176 items.push(self.parse_element()?);
177 self.skip_whitespace();
178 match self.chars.next() {
179 Some(',') => {
180 self.skip_whitespace();
181 if matches!(self.chars.peek(), None | Some('}')) {
182 return Err(self.error("array contains a missing element"));
183 }
184 }
185 Some('}') => break,
186 Some(_) => {
187 return Err(self.error("array elements must be separated by commas"));
188 }
189 None => return Err(self.error("array is missing a closing `}`")),
190 }
191 }
192 Ok(items)
193 }
194
195 fn parse_element(&mut self) -> Result<Value> {
196 match self.chars.peek() {
197 Some('{') => self.parse_array().map(Value::List),
198 Some('"') => self.parse_quoted_element().map(Value::Str),
199 Some(',') | Some('}') | None => Err(self.error("array contains a missing element")),
200 Some(_) => self.parse_unquoted_element(),
201 }
202 }
203
204 fn parse_quoted_element(&mut self) -> Result<String> {
205 let _opening_quote = self.chars.next();
206 let mut value = String::new();
207 loop {
208 match self.chars.next() {
209 Some('"') => return Ok(value),
210 Some('\\') => value.push(
211 self.chars
212 .next()
213 .ok_or_else(|| self.error("quoted element ends with an escape"))?,
214 ),
215 Some(character) => value.push(character),
216 None => return Err(self.error("array contains an unterminated quoted element")),
217 }
218 }
219 }
220
221 fn parse_unquoted_element(&mut self) -> Result<Value> {
222 let mut value = String::new();
223 let mut significant_len = 0;
224 let mut was_escaped = false;
225 while let Some(character) = self.chars.peek().copied() {
226 match character {
227 ',' | '}' => break,
228 '{' | '"' => {
229 return Err(self.error("array contains an unescaped special character"));
230 }
231 '\\' => {
232 let _escape = self.chars.next();
233 let escaped = self
234 .chars
235 .next()
236 .ok_or_else(|| self.error("array element ends with an escape"))?;
237 value.push(escaped);
238 significant_len = value.len();
239 was_escaped = true;
240 }
241 _ => {
242 let _character = self.chars.next();
243 value.push(character);
244 if !character.is_whitespace() {
245 significant_len = value.len();
246 }
247 }
248 }
249 }
250 value.truncate(significant_len);
251 if value.is_empty() {
252 return Err(self.error("array contains a missing element"));
253 }
254 if !was_escaped && value.eq_ignore_ascii_case("null") {
255 Ok(Value::Null)
256 } else {
257 Ok(Value::Str(value))
258 }
259 }
260
261 fn skip_whitespace(&mut self) {
262 while self
263 .chars
264 .next_if(|character| character.is_whitespace())
265 .is_some()
266 {}
267 }
268
269 fn error(&self, detail: &str) -> SQLError {
270 SQLError::Routine {
271 sqlstate: "22P02".into(),
272 message: format!("malformed array literal: \"{}\" ({detail})", self.source),
273 }
274 }
275}
276
277#[derive(Clone, Copy, Debug, PartialEq, Eq)]
278pub(super) enum ArrayShapeError {
279 MixedNesting,
280 MismatchedDimensions,
281}
282
283impl ArrayShapeError {
284 fn message(self) -> &'static str {
285 match self {
286 Self::MixedNesting => "cannot mix nested arrays and scalar elements",
287 Self::MismatchedDimensions => "multidimensional arrays must have matching dimensions",
288 }
289 }
290}
291
292pub(super) fn array_shape(items: &[Value]) -> std::result::Result<Vec<usize>, ArrayShapeError> {
293 let mut dimensions = vec![items.len()];
294 let mut nested_shape: Option<Vec<usize>> = None;
295 let mut has_scalar = false;
296 for item in items {
297 if let Value::List(nested) = item {
298 let shape = array_shape(nested)?;
299 if has_scalar {
300 return Err(ArrayShapeError::MixedNesting);
301 }
302 if nested_shape
303 .as_ref()
304 .is_some_and(|expected| *expected != shape)
305 {
306 return Err(ArrayShapeError::MismatchedDimensions);
307 }
308 nested_shape = Some(shape);
309 } else {
310 if nested_shape.is_some() {
311 return Err(ArrayShapeError::MixedNesting);
312 }
313 has_scalar = true;
314 }
315 }
316 if let Some(shape) = nested_shape {
317 dimensions.extend(shape);
318 }
319 Ok(dimensions)
320}
321
322pub fn array_dimensions(items: &[Value]) -> Result<Vec<usize>> {
327 array_shape(items).map_err(|error| SQLError::TypeMismatch(error.message().to_string()))
328}