1use std::cmp::Ordering;
10
11use uqa_core::{DecimalValue, TemporalValue, Value};
12
13use crate::ast::RangeSubtype;
14use crate::error::Result;
15use crate::SQLError;
16
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct CanonicalRange {
19 subtype: RangeSubtype,
20 lower: Option<Value>,
21 upper: Option<Value>,
22 lower_inclusive: bool,
23 upper_inclusive: bool,
24 empty: bool,
25}
26
27impl CanonicalRange {
28 fn empty(subtype: RangeSubtype) -> Self {
29 Self {
30 subtype,
31 lower: None,
32 upper: None,
33 lower_inclusive: false,
34 upper_inclusive: false,
35 empty: true,
36 }
37 }
38
39 #[must_use]
40 pub const fn subtype(&self) -> RangeSubtype {
41 self.subtype
42 }
43
44 #[must_use]
45 pub const fn is_empty(&self) -> bool {
46 self.empty
47 }
48
49 #[must_use]
50 pub const fn lower_inclusive(&self) -> bool {
51 self.lower_inclusive
52 }
53
54 #[must_use]
55 pub const fn upper_inclusive(&self) -> bool {
56 self.upper_inclusive
57 }
58
59 #[must_use]
60 pub fn lower(&self) -> Option<&Value> {
61 self.lower.as_ref()
62 }
63
64 #[must_use]
65 pub fn upper(&self) -> Option<&Value> {
66 self.upper.as_ref()
67 }
68
69 #[must_use]
70 pub fn overlaps(&self, other: &Self) -> bool {
71 self.subtype == other.subtype
72 && !self.empty
73 && !other.empty
74 && !upper_before_lower(self, other)
75 && !upper_before_lower(other, self)
76 }
77
78 #[must_use]
79 pub fn adjacent(&self, other: &Self) -> bool {
80 if self.subtype != other.subtype || self.empty || other.empty || self.overlaps(other) {
81 return false;
82 }
83 touching_bounds(
84 self.upper(),
85 self.upper_inclusive,
86 other.lower(),
87 other.lower_inclusive,
88 ) || touching_bounds(
89 other.upper(),
90 other.upper_inclusive,
91 self.lower(),
92 self.lower_inclusive,
93 )
94 }
95
96 #[must_use]
97 pub fn contains_range(&self, other: &Self) -> bool {
98 if self.subtype != other.subtype || self.empty {
99 return false;
100 }
101 if other.empty {
102 return true;
103 }
104 lower_contains(self, other) && upper_contains(self, other)
105 }
106
107 #[must_use]
108 pub fn contains_value(&self, value: &Value) -> bool {
109 if self.empty {
110 return false;
111 }
112 let lower = self
113 .lower
114 .as_ref()
115 .is_none_or(|lower| match value.cmp(lower) {
116 Ordering::Greater => true,
117 Ordering::Equal => self.lower_inclusive,
118 Ordering::Less => false,
119 });
120 let upper = self
121 .upper
122 .as_ref()
123 .is_none_or(|upper| match value.cmp(upper) {
124 Ordering::Less => true,
125 Ordering::Equal => self.upper_inclusive,
126 Ordering::Greater => false,
127 });
128 lower && upper
129 }
130
131 fn merge(&self, other: &Self) -> Self {
132 debug_assert!(self.overlaps(other) || self.adjacent(other));
133 let (lower, lower_inclusive) = minimum_lower(self, other);
134 let (upper, upper_inclusive) = maximum_upper(self, other);
135 Self {
136 subtype: self.subtype,
137 lower,
138 upper,
139 lower_inclusive,
140 upper_inclusive,
141 empty: false,
142 }
143 }
144
145 #[must_use]
148 pub fn merge_cover(&self, other: &Self) -> Self {
149 if self.empty {
150 return other.clone();
151 }
152 if other.empty {
153 return self.clone();
154 }
155 let (lower, lower_inclusive) = minimum_lower(self, other);
156 let (upper, upper_inclusive) = maximum_upper(self, other);
157 Self {
158 subtype: self.subtype,
159 lower,
160 upper,
161 lower_inclusive,
162 upper_inclusive,
163 empty: false,
164 }
165 }
166
167 #[must_use]
168 pub fn to_text(&self) -> String {
169 if self.empty {
170 return "empty".into();
171 }
172 let mut text = String::new();
173 text.push(if self.lower_inclusive { '[' } else { '(' });
174 if let Some(lower) = &self.lower {
175 text.push_str(&format_bound(lower));
176 }
177 text.push(',');
178 if let Some(upper) = &self.upper {
179 text.push_str(&format_bound(upper));
180 }
181 text.push(if self.upper_inclusive { ']' } else { ')' });
182 text
183 }
184}
185
186#[derive(Debug, Clone, PartialEq, Eq)]
187pub struct CanonicalMultirange {
188 subtype: RangeSubtype,
189 ranges: Vec<CanonicalRange>,
190}
191
192impl CanonicalMultirange {
193 #[must_use]
194 pub fn ranges(&self) -> &[CanonicalRange] {
195 &self.ranges
196 }
197
198 #[must_use]
199 pub fn is_empty(&self) -> bool {
200 self.ranges.is_empty()
201 }
202
203 #[must_use]
204 pub fn contains_range(&self, range: &CanonicalRange) -> bool {
205 range.subtype == self.subtype
206 && (range.empty || self.ranges.iter().any(|item| item.contains_range(range)))
207 }
208
209 #[must_use]
210 pub fn contains_multirange(&self, other: &Self) -> bool {
211 self.subtype == other.subtype && other.ranges.iter().all(|range| self.contains_range(range))
212 }
213
214 #[must_use]
215 pub fn overlaps_range(&self, range: &CanonicalRange) -> bool {
216 range.subtype == self.subtype && self.ranges.iter().any(|item| item.overlaps(range))
217 }
218
219 #[must_use]
220 pub fn overlaps_multirange(&self, other: &Self) -> bool {
221 self.subtype == other.subtype
222 && self
223 .ranges
224 .iter()
225 .any(|left| other.ranges.iter().any(|right| left.overlaps(right)))
226 }
227
228 #[must_use]
229 pub fn merge_cover(&self) -> CanonicalRange {
230 self.ranges
231 .iter()
232 .cloned()
233 .reduce(|left, right| left.merge_cover(&right))
234 .unwrap_or_else(|| CanonicalRange::empty(self.subtype))
235 }
236
237 #[must_use]
238 pub fn to_text(&self) -> String {
239 format!(
240 "{{{}}}",
241 self.ranges
242 .iter()
243 .map(CanonicalRange::to_text)
244 .collect::<Vec<_>>()
245 .join(",")
246 )
247 }
248}
249
250pub fn parse_range(text: &str, subtype: RangeSubtype) -> Result<CanonicalRange> {
251 let text = text.trim();
252 if text.eq_ignore_ascii_case("empty") {
253 return Ok(CanonicalRange {
254 subtype,
255 lower: None,
256 upper: None,
257 lower_inclusive: false,
258 upper_inclusive: false,
259 empty: true,
260 });
261 }
262 let mut chars = text.chars();
263 let opening = chars.next().ok_or_else(|| invalid_range(text, subtype))?;
264 let closing = text
265 .chars()
266 .next_back()
267 .ok_or_else(|| invalid_range(text, subtype))?;
268 if !matches!(opening, '[' | '(') || !matches!(closing, ']' | ')') || text.len() < 2 {
269 return Err(invalid_range(text, subtype));
270 }
271 let body = &text[opening.len_utf8()..text.len() - closing.len_utf8()];
272 let (lower_text, upper_text) =
273 split_range_bounds(body).ok_or_else(|| invalid_range(text, subtype))?;
274 let mut lower = parse_bound(lower_text, subtype, text)?;
275 let mut upper = parse_bound(upper_text, subtype, text)?;
276 let mut lower_inclusive = opening == '[' && lower.is_some();
277 let mut upper_inclusive = closing == ']' && upper.is_some();
278 if is_discrete(subtype) {
279 if !lower_inclusive {
280 if let Some(value) = lower.as_ref() {
281 lower = Some(increment_discrete(value, subtype)?);
282 lower_inclusive = true;
283 }
284 }
285 if upper_inclusive {
286 if let Some(value) = upper.as_ref() {
287 upper = Some(increment_discrete(value, subtype)?);
288 upper_inclusive = false;
289 }
290 }
291 }
292 let empty = match (&lower, &upper) {
293 (Some(lower), Some(upper)) => match lower.cmp(upper) {
294 Ordering::Greater => true,
295 Ordering::Equal => !(lower_inclusive && upper_inclusive),
296 Ordering::Less => false,
297 },
298 _ => false,
299 };
300 if empty {
301 return parse_range("empty", subtype);
302 }
303 Ok(CanonicalRange {
304 subtype,
305 lower,
306 upper,
307 lower_inclusive,
308 upper_inclusive,
309 empty: false,
310 })
311}
312
313pub fn parse_multirange(text: &str, subtype: RangeSubtype) -> Result<CanonicalMultirange> {
314 let text = text.trim();
315 if !text.starts_with('{') || !text.ends_with('}') {
316 return Err(invalid_multirange(text, subtype));
317 }
318 let body = &text[1..text.len() - 1];
319 let mut ranges = split_multirange_items(body)
320 .ok_or_else(|| invalid_multirange(text, subtype))?
321 .into_iter()
322 .map(|item| parse_range(item, subtype))
323 .collect::<Result<Vec<_>>>()?;
324 ranges.retain(|range| !range.empty);
325 ranges.sort_by(compare_lower_bounds);
326 let mut normalized: Vec<CanonicalRange> = Vec::with_capacity(ranges.len());
327 for range in ranges {
328 if let Some(previous) = normalized.last_mut() {
329 if previous.overlaps(&range) || previous.adjacent(&range) {
330 *previous = previous.merge(&range);
331 continue;
332 }
333 }
334 normalized.push(range);
335 }
336 Ok(CanonicalMultirange {
337 subtype,
338 ranges: normalized,
339 })
340}
341
342pub fn multirange_from_ranges(
343 subtype: RangeSubtype,
344 ranges: impl IntoIterator<Item = CanonicalRange>,
345) -> CanonicalMultirange {
346 let mut ranges = ranges
347 .into_iter()
348 .filter(|range| !range.empty)
349 .collect::<Vec<_>>();
350 ranges.sort_by(compare_lower_bounds);
351 let mut normalized: Vec<CanonicalRange> = Vec::with_capacity(ranges.len());
352 for range in ranges {
353 if let Some(previous) = normalized.last_mut() {
354 if previous.overlaps(&range) || previous.adjacent(&range) {
355 *previous = previous.merge(&range);
356 continue;
357 }
358 }
359 normalized.push(range);
360 }
361 CanonicalMultirange {
362 subtype,
363 ranges: normalized,
364 }
365}
366
367fn split_range_bounds(body: &str) -> Option<(&str, &str)> {
368 let mut quoted = false;
369 let mut escaped = false;
370 for (index, character) in body.char_indices() {
371 if escaped {
372 escaped = false;
373 continue;
374 }
375 if character == '\\' {
376 escaped = true;
377 continue;
378 }
379 if character == '"' {
380 quoted = !quoted;
381 continue;
382 }
383 if character == ',' && !quoted {
384 return Some((&body[..index], &body[index + 1..]));
385 }
386 }
387 None
388}
389
390fn split_multirange_items(body: &str) -> Option<Vec<&str>> {
391 if body.trim().is_empty() {
392 return Some(Vec::new());
393 }
394 let mut items = Vec::new();
395 let mut start = None;
396 let mut quoted = false;
397 let mut escaped = false;
398 for (index, character) in body.char_indices() {
399 if escaped {
400 escaped = false;
401 continue;
402 }
403 if character == '\\' {
404 escaped = true;
405 continue;
406 }
407 if character == '"' {
408 quoted = !quoted;
409 continue;
410 }
411 if quoted {
412 continue;
413 }
414 match character {
415 '[' | '(' if start.is_none() => start = Some(index),
416 ']' | ')' => {
417 let item_start = start.take()?;
418 items.push(body[item_start..=index].trim());
419 }
420 ',' if start.is_none() => {}
421 _ => {}
422 }
423 }
424 if quoted || escaped || start.is_some() || items.is_empty() {
425 None
426 } else {
427 Some(items)
428 }
429}
430
431fn parse_bound(raw: &str, subtype: RangeSubtype, whole: &str) -> Result<Option<Value>> {
432 let raw = raw.trim();
433 if raw.is_empty() {
434 return Ok(None);
435 }
436 let text = unquote_bound(raw).ok_or_else(|| invalid_range(whole, subtype))?;
437 let value = match subtype {
438 RangeSubtype::Integer => text
439 .parse::<i32>()
440 .map(|value| Value::Int(i64::from(value)))
441 .map_err(|_| range_subtype_error(&text, "integer"))?,
442 RangeSubtype::BigInteger => text
443 .parse::<i64>()
444 .map(Value::Int)
445 .map_err(|_| range_subtype_error(&text, "bigint"))?,
446 RangeSubtype::Numeric => DecimalValue::parse(&text)
447 .map(Value::Decimal)
448 .ok_or_else(|| range_subtype_error(&text, "numeric"))?,
449 RangeSubtype::Date => TemporalValue::try_parse_date(&text)
450 .map(Value::Temporal)
451 .map_err(|_| range_subtype_error(&text, "date"))?,
452 RangeSubtype::Timestamp => TemporalValue::parse_timestamp(&text)
453 .map(Value::Temporal)
454 .ok_or_else(|| range_subtype_error(&text, "timestamp without time zone"))?,
455 RangeSubtype::TimestampTz => TemporalValue::parse_timestamp_tz(&text)
456 .map(Value::Temporal)
457 .ok_or_else(|| range_subtype_error(&text, "timestamp with time zone"))?,
458 };
459 Ok(Some(value))
460}
461
462fn unquote_bound(raw: &str) -> Option<String> {
463 if !raw.starts_with('"') {
464 return (!raw.contains('"')).then(|| raw.to_string());
465 }
466 if raw.len() < 2 || !raw.ends_with('"') {
467 return None;
468 }
469 let mut value = String::new();
470 let mut chars = raw[1..raw.len() - 1].chars();
471 while let Some(character) = chars.next() {
472 if character == '\\' {
473 value.push(chars.next()?);
474 } else {
475 value.push(character);
476 }
477 }
478 Some(value)
479}
480
481fn increment_discrete(value: &Value, subtype: RangeSubtype) -> Result<Value> {
482 match (subtype, value) {
483 (RangeSubtype::Integer, Value::Int(value)) => i32::try_from(*value)
484 .ok()
485 .and_then(|value| value.checked_add(1))
486 .map(|value| Value::Int(i64::from(value)))
487 .ok_or_else(|| range_overflow("integer")),
488 (RangeSubtype::BigInteger, Value::Int(value)) => value
489 .checked_add(1)
490 .map(Value::Int)
491 .ok_or_else(|| range_overflow("bigint")),
492 (RangeSubtype::Date, Value::Temporal(TemporalValue::Date { days })) => days
493 .checked_add(1)
494 .map(|days| Value::Temporal(TemporalValue::Date { days }))
495 .ok_or_else(|| range_overflow("date")),
496 _ => Err(SQLError::Internal(format!(
497 "range subtype {subtype:?} received incompatible bound {value:?}"
498 ))),
499 }
500}
501
502fn is_discrete(subtype: RangeSubtype) -> bool {
503 matches!(
504 subtype,
505 RangeSubtype::Integer | RangeSubtype::BigInteger | RangeSubtype::Date
506 )
507}
508
509fn upper_before_lower(left: &CanonicalRange, right: &CanonicalRange) -> bool {
510 match (left.upper(), right.lower()) {
511 (None, _) | (_, None) => false,
512 (Some(upper), Some(lower)) => match upper.cmp(lower) {
513 Ordering::Less => true,
514 Ordering::Greater => false,
515 Ordering::Equal => !(left.upper_inclusive && right.lower_inclusive),
516 },
517 }
518}
519
520fn touching_bounds(
521 upper: Option<&Value>,
522 upper_inclusive: bool,
523 lower: Option<&Value>,
524 lower_inclusive: bool,
525) -> bool {
526 matches!((upper, lower), (Some(upper), Some(lower)) if upper == lower)
527 && upper_inclusive != lower_inclusive
528}
529
530fn lower_contains(outer: &CanonicalRange, inner: &CanonicalRange) -> bool {
531 match (outer.lower(), inner.lower()) {
532 (None, _) => true,
533 (Some(_), None) => false,
534 (Some(left), Some(right)) => match left.cmp(right) {
535 Ordering::Less => true,
536 Ordering::Greater => false,
537 Ordering::Equal => outer.lower_inclusive || !inner.lower_inclusive,
538 },
539 }
540}
541
542fn upper_contains(outer: &CanonicalRange, inner: &CanonicalRange) -> bool {
543 match (outer.upper(), inner.upper()) {
544 (None, _) => true,
545 (Some(_), None) => false,
546 (Some(left), Some(right)) => match left.cmp(right) {
547 Ordering::Greater => true,
548 Ordering::Less => false,
549 Ordering::Equal => outer.upper_inclusive || !inner.upper_inclusive,
550 },
551 }
552}
553
554fn minimum_lower(left: &CanonicalRange, right: &CanonicalRange) -> (Option<Value>, bool) {
555 match (left.lower(), right.lower()) {
556 (None, _) | (_, None) => (None, false),
557 (Some(left_value), Some(right_value)) => match left_value.cmp(right_value) {
558 Ordering::Less => (Some(left_value.clone()), left.lower_inclusive),
559 Ordering::Greater => (Some(right_value.clone()), right.lower_inclusive),
560 Ordering::Equal => (
561 Some(left_value.clone()),
562 left.lower_inclusive || right.lower_inclusive,
563 ),
564 },
565 }
566}
567
568fn maximum_upper(left: &CanonicalRange, right: &CanonicalRange) -> (Option<Value>, bool) {
569 match (left.upper(), right.upper()) {
570 (None, _) | (_, None) => (None, false),
571 (Some(left_value), Some(right_value)) => match left_value.cmp(right_value) {
572 Ordering::Greater => (Some(left_value.clone()), left.upper_inclusive),
573 Ordering::Less => (Some(right_value.clone()), right.upper_inclusive),
574 Ordering::Equal => (
575 Some(left_value.clone()),
576 left.upper_inclusive || right.upper_inclusive,
577 ),
578 },
579 }
580}
581
582fn compare_lower_bounds(left: &CanonicalRange, right: &CanonicalRange) -> Ordering {
583 match (left.lower(), right.lower()) {
584 (None, None) => Ordering::Equal,
585 (None, Some(_)) => Ordering::Less,
586 (Some(_), None) => Ordering::Greater,
587 (Some(left_value), Some(right_value)) => left_value
588 .cmp(right_value)
589 .then_with(|| right.lower_inclusive.cmp(&left.lower_inclusive)),
590 }
591}
592
593fn format_bound(value: &Value) -> String {
594 let raw = match value {
595 Value::Int(value) => value.to_string(),
596 Value::Decimal(value) => value.to_sql_string(),
597 Value::Temporal(value) => value.to_sql_string(),
598 other => super::value_to_string(other),
599 };
600 if raw.is_empty()
601 || raw.chars().any(|character| {
602 character.is_whitespace()
603 || matches!(character, ',' | '[' | ']' | '(' | ')' | '"' | '\\')
604 })
605 {
606 format!("\"{}\"", raw.replace('\\', "\\\\").replace('"', "\\\""))
607 } else {
608 raw
609 }
610}
611
612fn invalid_range(text: &str, subtype: RangeSubtype) -> SQLError {
613 SQLError::Routine {
614 sqlstate: "22P02".into(),
615 message: format!(
616 "malformed range literal: \"{text}\" for type {}",
617 subtype.range_name()
618 ),
619 }
620}
621
622fn invalid_multirange(text: &str, subtype: RangeSubtype) -> SQLError {
623 SQLError::Routine {
624 sqlstate: "22P02".into(),
625 message: format!(
626 "malformed multirange literal: \"{text}\" for type {}",
627 subtype.multirange_name()
628 ),
629 }
630}
631
632fn range_subtype_error(text: &str, type_name: &str) -> SQLError {
633 SQLError::Routine {
634 sqlstate: "22P02".into(),
635 message: format!("invalid input syntax for type {type_name}: \"{text}\""),
636 }
637}
638
639fn range_overflow(type_name: &str) -> SQLError {
640 SQLError::Routine {
641 sqlstate: "22003".into(),
642 message: format!("{type_name} out of range"),
643 }
644}
645
646#[cfg(test)]
647mod tests {
648 use super::*;
649
650 #[test]
651 fn discrete_ranges_canonicalize_to_inclusive_exclusive_bounds() {
652 assert_eq!(
653 parse_range("(1,4]", RangeSubtype::Integer)
654 .unwrap()
655 .to_text(),
656 "[2,5)"
657 );
658 assert_eq!(
659 parse_range("[2024-01-01,2024-01-02]", RangeSubtype::Date)
660 .unwrap()
661 .to_text(),
662 "[2024-01-01,2024-01-03)"
663 );
664 }
665
666 #[test]
667 fn multiranges_merge_overlapping_and_adjacent_members() {
668 assert_eq!(
669 parse_multirange("{[10,12),[1,3),[3,5)}", RangeSubtype::Integer)
670 .unwrap()
671 .to_text(),
672 "{[1,5),[10,12)}"
673 );
674 }
675
676 #[test]
677 fn range_relationships_cover_temporal_constraint_checks() {
678 let left = parse_range("[1,3)", RangeSubtype::Integer).unwrap();
679 let right = parse_range("[3,5)", RangeSubtype::Integer).unwrap();
680 let coverage = multirange_from_ranges(RangeSubtype::Integer, [left, right]);
681 let child = parse_range("[2,4)", RangeSubtype::Integer).unwrap();
682 assert!(coverage.contains_range(&child));
683 }
684}