1use std::cmp::Ordering;
10
11use uqa_core::{
12 memory::{Produced, ProductionControl},
13 TemporalValue, Value,
14};
15
16mod production;
17mod relationships;
18pub(super) use production::{
19 canonical_multirange_text_with_control, canonical_range_as_multirange_text_with_control,
20 canonical_range_text_with_control, multirange_from_produced_ranges,
21 parse_multirange_with_control, parse_range_with_control,
22};
23
24use crate::ast::RangeSubtype;
25use crate::error::Result;
26use crate::SQLError;
27
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct CanonicalRange {
30 subtype: RangeSubtype,
31 lower: Option<Value>,
32 upper: Option<Value>,
33 lower_inclusive: bool,
34 upper_inclusive: bool,
35 empty: bool,
36}
37
38impl CanonicalRange {
39 fn empty(subtype: RangeSubtype) -> Self {
40 Self {
41 subtype,
42 lower: None,
43 upper: None,
44 lower_inclusive: false,
45 upper_inclusive: false,
46 empty: true,
47 }
48 }
49
50 #[must_use]
51 pub const fn subtype(&self) -> RangeSubtype {
52 self.subtype
53 }
54
55 #[must_use]
56 pub const fn is_empty(&self) -> bool {
57 self.empty
58 }
59
60 #[must_use]
61 pub const fn lower_inclusive(&self) -> bool {
62 self.lower_inclusive
63 }
64
65 #[must_use]
66 pub const fn upper_inclusive(&self) -> bool {
67 self.upper_inclusive
68 }
69
70 #[must_use]
71 pub fn lower(&self) -> Option<&Value> {
72 self.lower.as_ref()
73 }
74
75 #[must_use]
76 pub fn upper(&self) -> Option<&Value> {
77 self.upper.as_ref()
78 }
79
80 #[must_use]
81 pub fn overlaps(&self, other: &Self) -> bool {
82 self.overlaps_with_control(other, &ProductionControl::uncontrolled())
83 .expect("ordinary range overlap")
84 }
85
86 #[must_use]
87 pub fn adjacent(&self, other: &Self) -> bool {
88 self.adjacent_with_control(other, &ProductionControl::uncontrolled())
89 .expect("ordinary range adjacency")
90 }
91
92 #[must_use]
93 pub fn contains_range(&self, other: &Self) -> bool {
94 self.contains_range_with_control(other, &ProductionControl::uncontrolled())
95 .expect("ordinary range containment")
96 }
97
98 #[must_use]
99 pub fn contains_value(&self, value: &Value) -> bool {
100 if self.empty {
101 return false;
102 }
103 let lower = self
104 .lower
105 .as_ref()
106 .is_none_or(|lower| match value.cmp(lower) {
107 Ordering::Greater => true,
108 Ordering::Equal => self.lower_inclusive,
109 Ordering::Less => false,
110 });
111 let upper = self
112 .upper
113 .as_ref()
114 .is_none_or(|upper| match value.cmp(upper) {
115 Ordering::Less => true,
116 Ordering::Equal => self.upper_inclusive,
117 Ordering::Greater => false,
118 });
119 lower && upper
120 }
121
122 #[must_use]
125 pub fn merge_cover(&self, other: &Self) -> Self {
126 self.merge_cover_with_control(other, &ProductionControl::uncontrolled())
127 .expect("ordinary range cover")
128 .into_uncontrolled()
129 .expect("ordinary range")
130 }
131
132 pub(super) fn to_text_with_control(
133 &self,
134 control: &ProductionControl<'_>,
135 ) -> Result<Produced<String>> {
136 production::range_text(self, control)
137 }
138
139 #[must_use]
140 pub fn to_text(&self) -> String {
141 production::range_text(self, &ProductionControl::uncontrolled())
142 .expect("ordinary range formatting")
143 .into_uncontrolled()
144 .expect("ordinary range text")
145 }
146}
147
148#[derive(Debug, Clone, PartialEq, Eq)]
149pub struct CanonicalMultirange {
150 subtype: RangeSubtype,
151 ranges: Vec<CanonicalRange>,
152}
153
154impl CanonicalMultirange {
155 #[must_use]
156 pub fn ranges(&self) -> &[CanonicalRange] {
157 &self.ranges
158 }
159
160 #[must_use]
161 pub fn is_empty(&self) -> bool {
162 self.ranges.is_empty()
163 }
164
165 #[must_use]
166 pub fn contains_range(&self, range: &CanonicalRange) -> bool {
167 range.subtype == self.subtype
168 && (range.empty || self.ranges.iter().any(|item| item.contains_range(range)))
169 }
170
171 #[must_use]
172 pub fn contains_multirange(&self, other: &Self) -> bool {
173 self.subtype == other.subtype && other.ranges.iter().all(|range| self.contains_range(range))
174 }
175
176 #[must_use]
177 pub fn overlaps_range(&self, range: &CanonicalRange) -> bool {
178 range.subtype == self.subtype && self.ranges.iter().any(|item| item.overlaps(range))
179 }
180
181 #[must_use]
182 pub fn overlaps_multirange(&self, other: &Self) -> bool {
183 self.subtype == other.subtype
184 && self
185 .ranges
186 .iter()
187 .any(|left| other.ranges.iter().any(|right| left.overlaps(right)))
188 }
189
190 #[must_use]
191 pub fn merge_cover(&self) -> CanonicalRange {
192 self.merge_cover_with_control(&ProductionControl::uncontrolled())
193 .expect("ordinary multirange cover")
194 .into_uncontrolled()
195 .expect("ordinary range")
196 }
197
198 pub(super) fn to_text_with_control(
199 &self,
200 control: &ProductionControl<'_>,
201 ) -> Result<Produced<String>> {
202 production::multirange_text(self, control)
203 }
204
205 #[must_use]
206 pub fn to_text(&self) -> String {
207 production::multirange_text(self, &ProductionControl::uncontrolled())
208 .expect("ordinary multirange formatting")
209 .into_uncontrolled()
210 .expect("ordinary multirange text")
211 }
212}
213
214pub fn parse_range(text: &str, subtype: RangeSubtype) -> Result<CanonicalRange> {
215 Ok(
216 production::parse_range_with_control(text, subtype, &ProductionControl::uncontrolled())?
217 .into_uncontrolled()
218 .expect("ordinary range"),
219 )
220}
221
222pub fn parse_multirange(text: &str, subtype: RangeSubtype) -> Result<CanonicalMultirange> {
223 Ok(production::parse_multirange_with_control(
224 text,
225 subtype,
226 &ProductionControl::uncontrolled(),
227 )?
228 .into_uncontrolled()
229 .expect("ordinary multirange"))
230}
231
232pub fn multirange_from_ranges(
233 subtype: RangeSubtype,
234 ranges: impl IntoIterator<Item = CanonicalRange>,
235) -> CanonicalMultirange {
236 production::normalize_ranges(ranges, subtype, &ProductionControl::uncontrolled(), None)
237 .expect("ordinary multirange normalization")
238 .into_uncontrolled()
239 .expect("ordinary multirange")
240}
241
242fn increment_discrete(value: &Value, subtype: RangeSubtype) -> Result<Value> {
243 match (subtype, value) {
244 (RangeSubtype::Integer, Value::Int(value)) => i32::try_from(*value)
245 .ok()
246 .and_then(|value| value.checked_add(1))
247 .map(|value| Value::Int(i64::from(value)))
248 .ok_or_else(|| range_overflow("integer")),
249 (RangeSubtype::BigInteger, Value::Int(value)) => value
250 .checked_add(1)
251 .map(Value::Int)
252 .ok_or_else(|| range_overflow("bigint")),
253 (RangeSubtype::Date, Value::Temporal(TemporalValue::Date { days })) => days
254 .checked_add(1)
255 .map(|days| Value::Temporal(TemporalValue::Date { days }))
256 .ok_or_else(|| range_overflow("date")),
257 _ => Err(SQLError::Internal(format!(
258 "range subtype {subtype:?} received incompatible bound {value:?}"
259 ))),
260 }
261}
262
263fn is_discrete(subtype: RangeSubtype) -> bool {
264 matches!(
265 subtype,
266 RangeSubtype::Integer | RangeSubtype::BigInteger | RangeSubtype::Date
267 )
268}
269
270fn invalid_range(text: &str, subtype: RangeSubtype) -> SQLError {
271 SQLError::Routine {
272 sqlstate: "22P02".into(),
273 message: format!(
274 "malformed range literal: \"{text}\" for type {}",
275 subtype.range_name()
276 ),
277 }
278}
279
280fn invalid_multirange(text: &str, subtype: RangeSubtype) -> SQLError {
281 SQLError::Routine {
282 sqlstate: "22P02".into(),
283 message: format!(
284 "malformed multirange literal: \"{text}\" for type {}",
285 subtype.multirange_name()
286 ),
287 }
288}
289
290fn range_subtype_error(text: &str, type_name: &str) -> SQLError {
291 SQLError::Routine {
292 sqlstate: "22P02".into(),
293 message: format!("invalid input syntax for type {type_name}: \"{text}\""),
294 }
295}
296
297fn range_overflow(type_name: &str) -> SQLError {
298 SQLError::Routine {
299 sqlstate: "22003".into(),
300 message: format!("{type_name} out of range"),
301 }
302}
303
304#[cfg(test)]
305mod tests {
306 use super::*;
307
308 #[test]
309 fn discrete_ranges_canonicalize_to_inclusive_exclusive_bounds() {
310 assert_eq!(
311 parse_range("(1,4]", RangeSubtype::Integer)
312 .unwrap()
313 .to_text(),
314 "[2,5)"
315 );
316 assert_eq!(
317 parse_range("[2024-01-01,2024-01-02]", RangeSubtype::Date)
318 .unwrap()
319 .to_text(),
320 "[2024-01-01,2024-01-03)"
321 );
322 }
323
324 #[test]
325 fn multiranges_merge_overlapping_and_adjacent_members() {
326 assert_eq!(
327 parse_multirange("{[10,12),[1,3),[3,5)}", RangeSubtype::Integer)
328 .unwrap()
329 .to_text(),
330 "{[1,5),[10,12)}"
331 );
332 }
333
334 #[test]
335 fn range_relationships_cover_temporal_constraint_checks() {
336 let left = parse_range("[1,3)", RangeSubtype::Integer).unwrap();
337 let right = parse_range("[3,5)", RangeSubtype::Integer).unwrap();
338 let coverage = multirange_from_ranges(RangeSubtype::Integer, [left, right]);
339 let child = parse_range("[2,4)", RangeSubtype::Integer).unwrap();
340 assert!(coverage.contains_range(&child));
341 }
342}