uqa_sql/ast/
array_access.rs1use serde::{Deserialize, Serialize};
10
11use super::FunctionDispatch;
12use crate::SQLError;
13
14pub const MAX_ARRAY_SUBSCRIPT_DIMENSIONS: usize = 6;
16
17#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
19pub struct ArraySliceBounds {
20 omitted_lower: u8,
21 omitted_upper: u8,
22}
23
24impl ArraySliceBounds {
25 pub fn omit(&mut self, dimension: usize, lower: bool, upper: bool) {
26 if dimension < MAX_ARRAY_SUBSCRIPT_DIMENSIONS {
28 self.omitted_lower |= u8::from(lower) << dimension;
29 self.omitted_upper |= u8::from(upper) << dimension;
30 }
31 }
32
33 #[must_use]
34 pub fn is_omitted(self, dimension: usize, lower: bool) -> bool {
35 let bits = if lower {
36 self.omitted_lower
37 } else {
38 self.omitted_upper
39 };
40 dimension < MAX_ARRAY_SUBSCRIPT_DIMENSIONS && bits & (1 << dimension) != 0
41 }
42}
43
44#[derive(Debug, Clone, Copy)]
46pub struct ArrayAccess {
47 dimensions: usize,
48 bounds: Option<ArraySliceBounds>,
49}
50
51impl ArrayAccess {
52 pub fn from_dispatch(
54 dispatch: FunctionDispatch,
55 arguments: usize,
56 is_bare_null: impl Fn(usize) -> bool,
57 ) -> Option<Result<Self, SQLError>> {
58 let slice = match dispatch {
59 FunctionDispatch::ArraySubscripts => false,
60 FunctionDispatch::ArraySlices | FunctionDispatch::ArraySlicesWithBounds(_) => true,
61 _ => return None,
62 };
63 Some((|| {
64 let count = arguments
65 .checked_sub(1)
66 .filter(|count| *count > 0)
67 .ok_or_else(|| SQLError::Internal("array access has no index".into()))?;
68 if slice && !count.is_multiple_of(2) {
69 return Err(SQLError::Internal(
70 "array slice has an incomplete bound pair".into(),
71 ));
72 }
73 let dimensions = if slice { count / 2 } else { count };
74 let bounds = if slice {
75 let bounds = if let FunctionDispatch::ArraySlicesWithBounds(bounds) = dispatch {
76 bounds
77 } else {
78 let mut bounds = ArraySliceBounds::default();
79 for dimension in 0..dimensions.min(MAX_ARRAY_SUBSCRIPT_DIMENSIONS) {
80 bounds.omit(
81 dimension,
82 is_bare_null(1 + dimension * 2),
83 is_bare_null(2 + dimension * 2),
84 );
85 }
86 bounds
87 };
88 Some(bounds)
89 } else {
90 None
91 };
92 Ok(Self { dimensions, bounds })
93 })())
94 }
95
96 pub fn validate_dimensions(self) -> Result<(), SQLError> {
97 if self.dimensions > MAX_ARRAY_SUBSCRIPT_DIMENSIONS {
98 return Err(SQLError::Routine {
99 sqlstate: "54000".into(),
100 message: format!("number of array dimensions ({}) exceeds the maximum allowed ({MAX_ARRAY_SUBSCRIPT_DIMENSIONS})", self.dimensions),
101 });
102 }
103 Ok(())
104 }
105
106 #[must_use]
107 pub const fn slice_bounds(self) -> Option<ArraySliceBounds> {
108 self.bounds
109 }
110
111 #[must_use]
112 pub const fn bound_count(self) -> usize {
113 self.dimensions * if self.bounds.is_some() { 2 } else { 1 }
114 }
115
116 #[must_use]
117 pub fn is_omitted(self, argument: usize) -> bool {
118 argument > 0
119 && self
120 .bounds
121 .is_some_and(|bounds| bounds.is_omitted((argument - 1) / 2, argument % 2 == 1))
122 }
123
124 pub fn evaluation_order(self) -> impl Iterator<Item = usize> {
126 (0..self.bound_count())
127 .map(move |position| {
128 if self.bounds.is_none() {
129 position + 1
130 } else if position < self.dimensions {
131 position * 2 + 2
132 } else {
133 (position - self.dimensions) * 2 + 1
134 }
135 })
136 .filter(move |position| !self.is_omitted(*position))
137 }
138}