1use uqa_core::{
10 memory::{Produced, ProductionControl, ProductionString, ProductionVec},
11 ArrayValue, Value, ValueRetentionError,
12};
13
14use crate::error::{Result, SQLError};
15
16use super::cast_value_from_with_control;
17
18pub(super) fn binary_compatible_elements(
20 source: Option<&str>,
21 target: &str,
22 control: &ProductionControl<'_>,
23) -> Result<bool> {
24 let Some(source) = source else {
25 return Ok(true);
26 };
27 let source = crate::ColumnType::from_sql_name_with_control(source, control)?;
28 let target = crate::ColumnType::from_sql_name_with_control(target, control)?;
29 Ok(*source == *target
30 || crate::type_resolution::cast_catalog_entry_with_control(&source, &target, control)?
31 .is_some_and(|entry| entry.method == crate::type_resolution::CastMethod::Binary))
32}
33
34type ArrayDimensions = Produced<Vec<(i32, usize)>>;
35
36pub fn parse_pg_array_literal(text: &str) -> Result<ArrayValue> {
40 parse_pg_array_literal_with_control(text, &ProductionControl::uncontrolled())?
41 .into_uncontrolled()
42 .map_err(|_| SQLError::Internal("ordinary array parse owner".into()))
43}
44
45pub fn parse_pg_array_literal_with_control(
46 text: &str,
47 control: &ProductionControl<'_>,
48) -> Result<Produced<ArrayValue>> {
49 let mut parser = PgArrayLiteralParser::new(text, *control);
50 let (declared_dimensions, items) = parser.parse()?;
51 match array_shape_with_control(&items, control) {
52 Ok(shape) => drop(shape),
53 Err(ShapeProductionError::Shape(error)) => return Err(parser.error(error.message())),
54 Err(ShapeProductionError::Control(error)) => return Err(error.into()),
55 }
56 let array = match &declared_dimensions {
57 Some(declared) => {
58 let mut bounds = ProductionVec::new(*control);
59 bounds.reserve(declared.len())?;
60 for (lower, _) in &**declared {
61 bounds.push_copy(*lower)?;
62 }
63 ArrayValue::with_lower_bounds_with_control(items, bounds.finish()?, control)?
64 }
65 None => ArrayValue::try_new_with_control(items, control)?,
66 }
67 .ok_or_else(|| parser.error("specified array dimensions do not match array contents"))?;
68 if let Some(declared) = declared_dimensions {
69 if !declared
70 .iter()
71 .map(|(_, length)| *length)
72 .eq(array.dimensions().iter().copied())
73 {
74 return Err(parser.error("specified array dimensions do not match array contents"));
75 }
76 }
77 Ok(array)
78}
79
80pub(super) fn cast_array_elements(
81 items: &[Value],
82 element_type: &str,
83 source_element_type: Option<&str>,
84 control: &ProductionControl<'_>,
85) -> Result<Produced<Vec<Value>>> {
86 let mut output = ProductionVec::new(*control);
87 output.reserve(items.len())?;
88 for item in items {
89 let value = match item {
90 Value::List(nested) => {
91 let (nested, memory) =
92 cast_array_elements(nested, element_type, source_element_type, control)?
93 .into_parts();
94 control.finish(Value::List(nested), memory)?
95 }
96 other => {
97 cast_value_from_with_control(other, element_type, source_element_type, control)?
98 }
99 };
100 output.push_produced(value)?;
101 }
102 Ok(output.finish()?)
103}
104
105pub(super) struct PgArrayLiteralParser<'a, 'control> {
106 control: ProductionControl<'control>,
107 source: &'a str,
108 chars: std::iter::Peekable<std::str::Chars<'a>>,
109}
110
111type ParsedArrayLiteral = (Option<ArrayDimensions>, Produced<Vec<Value>>);
112
113impl<'a, 'control> PgArrayLiteralParser<'a, 'control> {
114 fn new(source: &'a str, control: ProductionControl<'control>) -> Self {
115 Self {
116 control,
117 source,
118 chars: source.chars().peekable(),
119 }
120 }
121
122 fn parse(&mut self) -> Result<ParsedArrayLiteral> {
123 self.skip_whitespace()?;
124 let dimensions = self.parse_dimension_declaration()?;
125 let items = self.parse_array()?;
126 self.skip_whitespace()?;
127 if self.chars.peek().is_some() {
128 return Err(self.error("unexpected content after closing brace"));
129 }
130 Ok((dimensions, items))
131 }
132
133 fn parse_dimension_declaration(&mut self) -> Result<Option<ArrayDimensions>> {
134 if self.chars.peek() != Some(&'[') {
135 return Ok(None);
136 }
137 let mut dimensions = ProductionVec::new(self.control);
138 while self.chars.next_if_eq(&'[').is_some() {
139 self.skip_whitespace()?;
140 let lower = self.parse_dimension_bound()?;
141 self.skip_whitespace()?;
142 if self.chars.next() != Some(':') {
143 return Err(self.error("array dimension must contain `:`"));
144 }
145 self.skip_whitespace()?;
146 let upper = self.parse_dimension_bound()?;
147 self.skip_whitespace()?;
148 if self.chars.next() != Some(']') {
149 return Err(self.error("array dimension is missing a closing `]`"));
150 }
151 if upper == i32::MAX {
152 return Err(SQLError::Routine {
153 sqlstate: "54000".into(),
154 message: format!("array upper bound is too large: {upper}"),
155 });
156 }
157 if upper < lower {
158 return Err(SQLError::Routine {
159 sqlstate: "2202E".into(),
160 message: "upper bound cannot be less than lower bound".into(),
161 });
162 }
163 let length = i64::from(upper)
164 .checked_sub(i64::from(lower))
165 .and_then(|difference| difference.checked_add(1))
166 .and_then(|length| usize::try_from(length).ok())
167 .ok_or_else(|| self.error("array dimension is out of range"))?;
168 dimensions.push_copy((lower, length))?;
169 self.skip_whitespace()?;
170 }
171 if self.chars.next() != Some('=') {
172 return Err(self.error("array dimensions must be followed by `=`"));
173 }
174 self.skip_whitespace()?;
175 Ok(Some(dimensions.finish()?))
176 }
177
178 fn parse_dimension_bound(&mut self) -> Result<i32> {
179 let mut text = ProductionString::new(self.control);
180 if self
181 .chars
182 .peek()
183 .is_some_and(|character| matches!(character, '+' | '-'))
184 {
185 text.push(self.chars.next().expect("peeked array bound sign"))?;
186 }
187 while self.chars.peek().is_some_and(char::is_ascii_digit) {
188 text.push(self.chars.next().expect("peeked array bound digit"))?;
189 }
190 if text.is_empty() || matches!(&*text, "+" | "-") {
191 return Err(self.error("array dimension bound must be an integer"));
192 }
193 text.parse()
194 .map_err(|_| self.error("array dimension bound is out of range"))
195 }
196
197 fn parse_array(&mut self) -> Result<Produced<Vec<Value>>> {
198 if self.chars.next() != Some('{') {
199 return Err(self.error("array value must start with `{`"));
200 }
201 self.skip_whitespace()?;
202 if self.chars.next_if_eq(&'}').is_some() {
203 return Ok(ProductionVec::new(self.control).finish()?);
204 }
205
206 let mut items = ProductionVec::new(self.control);
207 loop {
208 self.skip_whitespace()?;
209 items.push_produced(self.parse_element()?)?;
210 self.skip_whitespace()?;
211 match self.chars.next() {
212 Some(',') => {
213 self.skip_whitespace()?;
214 if matches!(self.chars.peek(), None | Some('}')) {
215 return Err(self.error("array contains a missing element"));
216 }
217 }
218 Some('}') => break,
219 Some(_) => {
220 return Err(self.error("array elements must be separated by commas"));
221 }
222 None => return Err(self.error("array is missing a closing `}`")),
223 }
224 }
225 Ok(items.finish()?)
226 }
227
228 fn parse_element(&mut self) -> Result<Produced<Value>> {
229 match self.chars.peek() {
230 Some('{') => {
231 let (value, memory) = self.parse_array()?.into_parts();
232 Ok(self.control.finish(Value::List(value), memory)?)
233 }
234 Some('"') => {
235 let (value, memory) = self.parse_quoted_element()?.into_parts();
236 Ok(self.control.finish(Value::Str(value), memory)?)
237 }
238 Some(',') | Some('}') | None => Err(self.error("array contains a missing element")),
239 Some(_) => self.parse_unquoted_element(),
240 }
241 }
242
243 fn parse_quoted_element(&mut self) -> Result<Produced<String>> {
244 let _opening_quote = self.chars.next();
245 let mut value = ProductionString::new(self.control);
246 loop {
247 match self.chars.next() {
248 Some('"') => return Ok(value.finish()?),
249 Some('\\') => value.push(
250 self.chars
251 .next()
252 .ok_or_else(|| self.error("quoted element ends with an escape"))?,
253 )?,
254 Some(character) => value.push(character)?,
255 None => return Err(self.error("array contains an unterminated quoted element")),
256 }
257 }
258 }
259
260 fn parse_unquoted_element(&mut self) -> Result<Produced<Value>> {
261 let mut value = ProductionString::new(self.control);
262 let mut significant_len = 0;
263 let mut was_escaped = false;
264 while let Some(character) = self.chars.peek().copied() {
265 match character {
266 ',' | '}' => break,
267 '{' | '"' => {
268 return Err(self.error("array contains an unescaped special character"));
269 }
270 '\\' => {
271 let _escape = self.chars.next();
272 let escaped = self
273 .chars
274 .next()
275 .ok_or_else(|| self.error("array element ends with an escape"))?;
276 value.push(escaped)?;
277 significant_len = value.len();
278 was_escaped = true;
279 }
280 _ => {
281 let _character = self.chars.next();
282 value.push(character)?;
283 if !character.is_whitespace() {
284 significant_len = value.len();
285 }
286 }
287 }
288 }
289 let value = value.finish()?;
290 let significant = &value[..significant_len];
291 if significant.is_empty() {
292 return Err(self.error("array contains a missing element"));
293 }
294 if !was_escaped && significant.eq_ignore_ascii_case("null") {
295 Ok(self
296 .control
297 .finish(Value::Null, self.control.empty_reservation())?)
298 } else {
299 let (mut value, memory) = value.into_parts();
300 value.truncate(significant_len);
301 Ok(self.control.finish(Value::Str(value), memory)?)
302 }
303 }
304
305 fn skip_whitespace(&mut self) -> Result<()> {
306 self.control.check()?;
307 while self
308 .chars
309 .next_if(|character| character.is_whitespace())
310 .is_some()
311 {
312 self.control.check()?;
313 }
314 Ok(())
315 }
316
317 fn error(&self, detail: &str) -> SQLError {
318 SQLError::Routine {
319 sqlstate: "22P02".into(),
320 message: format!("malformed array literal: \"{}\" ({detail})", self.source),
321 }
322 }
323}
324
325#[derive(Clone, Copy, Debug, PartialEq, Eq)]
326pub(super) enum ArrayShapeError {
327 MixedNesting,
328 MismatchedDimensions,
329}
330
331impl ArrayShapeError {
332 fn message(self) -> &'static str {
333 match self {
334 Self::MixedNesting => "cannot mix nested arrays and scalar elements",
335 Self::MismatchedDimensions => "multidimensional arrays must have matching dimensions",
336 }
337 }
338}
339
340pub(super) fn array_shape(items: &[Value]) -> std::result::Result<Vec<usize>, ArrayShapeError> {
341 match array_shape_with_control(items, &ProductionControl::uncontrolled()) {
342 Ok(shape) => Ok(shape.into_uncontrolled().expect("ordinary array shape")),
343 Err(ShapeProductionError::Shape(error)) => Err(error),
344 Err(ShapeProductionError::Control(_)) => unreachable!("ordinary shape production"),
345 }
346}
347
348enum ShapeProductionError {
349 Shape(ArrayShapeError),
350 Control(ValueRetentionError),
351}
352impl From<ValueRetentionError> for ShapeProductionError {
353 fn from(error: ValueRetentionError) -> Self {
354 Self::Control(error)
355 }
356}
357
358fn array_shape_with_control(
359 items: &[Value],
360 control: &ProductionControl<'_>,
361) -> std::result::Result<Produced<Vec<usize>>, ShapeProductionError> {
362 let mut dimensions = ProductionVec::new(*control);
363 dimensions.push_copy(items.len())?;
364 let mut nested_shape: Option<Produced<Vec<usize>>> = None;
365 let mut has_scalar = false;
366 for item in items {
367 control.check()?;
368 if let Value::List(nested) = item {
369 let shape = array_shape_with_control(nested, control)?;
370 if has_scalar {
371 return Err(ShapeProductionError::Shape(ArrayShapeError::MixedNesting));
372 }
373 if nested_shape
374 .as_ref()
375 .is_some_and(|expected| **expected != *shape)
376 {
377 return Err(ShapeProductionError::Shape(
378 ArrayShapeError::MismatchedDimensions,
379 ));
380 }
381 nested_shape = Some(shape);
382 } else {
383 if nested_shape.is_some() {
384 return Err(ShapeProductionError::Shape(ArrayShapeError::MixedNesting));
385 }
386 has_scalar = true;
387 }
388 }
389 if let Some(shape) = nested_shape {
390 for length in &*shape {
391 dimensions.push_copy(*length)?;
392 }
393 }
394 Ok(dimensions.finish()?)
395}
396
397pub fn array_dimensions(items: &[Value]) -> Result<Vec<usize>> {
402 array_shape(items).map_err(|error| SQLError::TypeMismatch(error.message().to_string()))
403}