Skip to main content

surrealdb_expr/val/
range.rs

1use std::cmp::Ordering;
2use std::ops::Bound;
3
4pub use common::range::{IntegerRangeIter, TypedRange};
5use revision::revisioned;
6use storekey::{BorrowDecode, Encode};
7use surrealdb_types::{SqlFormat, ToSql, write_sql};
8
9use super::value::CoerceErrorExt;
10use crate::expr;
11use crate::expr::kind::HasKind;
12use crate::val::value::{Coerce, CoerceError};
13use crate::val::{Array, IndexFormat, Number, Value};
14
15/// A range of surrealql values,
16///
17/// Can be any kind of values, "a"..1 is allowed.
18#[revisioned(revision = 1)]
19#[derive(Debug, Eq, PartialEq, Clone, Hash, Encode, BorrowDecode)]
20#[storekey(format = "()")]
21#[storekey(format = "IndexFormat")]
22pub struct Range {
23	pub start: Bound<Value>,
24	pub end: Bound<Value>,
25}
26
27impl Range {
28	/// returns a range with no bounds.
29	pub const fn unbounded() -> Self {
30		Range {
31			start: Bound::Unbounded,
32			end: Bound::Unbounded,
33		}
34	}
35
36	/// Returns if the range cannot contain a value.
37	pub fn is_empty(&self) -> bool {
38		match &self.start {
39			Bound::Included(a) => match &self.end {
40				Bound::Included(b) => a > b,
41				Bound::Excluded(b) => a >= b,
42				Bound::Unbounded => false,
43			},
44			Bound::Excluded(a) => match &self.end {
45				Bound::Included(b) | Bound::Excluded(b) => a >= b,
46				Bound::Unbounded => false,
47			},
48			Bound::Unbounded => false,
49		}
50	}
51
52	/// Returns the intersection of two ranges.
53	pub fn intersect(self, other: Self) -> Self {
54		let start = match self.start {
55			Bound::Included(a) => match other.start {
56				Bound::Included(b) => Bound::Included(a.max(b)),
57				Bound::Excluded(b) => {
58					if a <= b {
59						Bound::Excluded(b)
60					} else {
61						Bound::Included(a)
62					}
63				}
64				Bound::Unbounded => Bound::Included(a),
65			},
66			Bound::Excluded(a) => match other.start {
67				Bound::Excluded(b) => Bound::Excluded(a.max(b)),
68				Bound::Included(b) => {
69					if a < b {
70						Bound::Included(b)
71					} else {
72						Bound::Excluded(a)
73					}
74				}
75				Bound::Unbounded => Bound::Excluded(a),
76			},
77			Bound::Unbounded => other.start,
78		};
79
80		let end = match self.end {
81			Bound::Included(a) => match other.end {
82				Bound::Included(b) => Bound::Included(a.min(b)),
83				Bound::Excluded(b) => {
84					if a >= b {
85						Bound::Excluded(b)
86					} else {
87						Bound::Included(a)
88					}
89				}
90				Bound::Unbounded => Bound::Included(a),
91			},
92			Bound::Excluded(a) => match other.end {
93				Bound::Excluded(b) => Bound::Excluded(a.min(b)),
94				Bound::Included(b) => {
95					if a > b {
96						Bound::Included(b)
97					} else {
98						Bound::Excluded(a)
99					}
100				}
101				Bound::Unbounded => Bound::Excluded(a),
102			},
103			Bound::Unbounded => other.end,
104		};
105
106		Range {
107			start,
108			end,
109		}
110	}
111}
112
113impl PartialOrd for Range {
114	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
115		Some(self.cmp(other))
116	}
117}
118
119impl Ord for Range {
120	fn cmp(&self, other: &Self) -> Ordering {
121		fn compare_bounds(a: &Bound<Value>, b: &Bound<Value>) -> Ordering {
122			match a {
123				Bound::Unbounded => match b {
124					Bound::Unbounded => Ordering::Equal,
125					_ => Ordering::Less,
126				},
127				Bound::Included(a) => match b {
128					Bound::Unbounded => Ordering::Greater,
129					Bound::Included(b) => a.cmp(b),
130					Bound::Excluded(_) => Ordering::Less,
131				},
132				Bound::Excluded(a) => match b {
133					Bound::Excluded(b) => a.cmp(b),
134					_ => Ordering::Greater,
135				},
136			}
137		}
138		match compare_bounds(&self.start, &other.start) {
139			Ordering::Equal => compare_bounds(&self.end, &other.end),
140			x => x,
141		}
142	}
143}
144
145impl ToSql for Range {
146	fn fmt_sql(&self, f: &mut String, sql_fmt: SqlFormat) {
147		match self.start {
148			Bound::Unbounded => {}
149			Bound::Included(ref x) => write_sql!(f, sql_fmt, "{x}"),
150			Bound::Excluded(ref x) => write_sql!(f, sql_fmt, "{x}>"),
151		}
152		write_sql!(f, sql_fmt, "..");
153		match self.end {
154			Bound::Unbounded => {}
155			Bound::Included(ref x) => write_sql!(f, sql_fmt, "={x}"),
156			Bound::Excluded(ref x) => write_sql!(f, sql_fmt, "{x}"),
157		}
158	}
159}
160
161impl Range {
162	pub fn can_coerce_to_typed<T: Coerce>(&self) -> bool {
163		match self.start {
164			Bound::Included(ref x) | Bound::Excluded(ref x) => {
165				if !x.can_coerce_to::<T>() {
166					return false;
167				}
168			}
169			Bound::Unbounded => {}
170		}
171
172		match self.end {
173			Bound::Included(ref x) | Bound::Excluded(ref x) => x.can_coerce_to::<T>(),
174			Bound::Unbounded => true,
175		}
176	}
177
178	pub fn coerce_to_typed<T: Coerce + HasKind>(self) -> Result<TypedRange<T>, CoerceError> {
179		let start = match self.start {
180			Bound::Included(x) => Bound::Included(
181				T::coerce(x).with_element_of(|| format!("range<{}>", T::kind().to_sql()))?,
182			),
183			Bound::Excluded(x) => Bound::Excluded(
184				T::coerce(x).with_element_of(|| format!("range<{}>", T::kind().to_sql()))?,
185			),
186			Bound::Unbounded => Bound::Unbounded,
187		};
188		let end = match self.end {
189			Bound::Included(x) => Bound::Included(
190				T::coerce(x).with_element_of(|| format!("range<{}>", T::kind().to_sql()))?,
191			),
192			Bound::Excluded(x) => Bound::Excluded(
193				T::coerce(x).with_element_of(|| format!("range<{}>", T::kind().to_sql()))?,
194			),
195			Bound::Unbounded => Bound::Unbounded,
196		};
197		Ok(TypedRange {
198			start,
199			end,
200		})
201	}
202
203	pub fn into_literal(self) -> expr::Expr {
204		match (self.start, self.end) {
205			(Bound::Unbounded, Bound::Unbounded) => {
206				expr::Expr::Literal(expr::Literal::UnboundedRange)
207			}
208			(Bound::Included(x), Bound::Unbounded) => expr::Expr::Postfix {
209				op: expr::PostfixOperator::Range,
210				expr: Box::new(x.into_literal()),
211			},
212			(Bound::Excluded(x), Bound::Unbounded) => expr::Expr::Postfix {
213				op: expr::PostfixOperator::RangeSkip,
214				expr: Box::new(x.into_literal()),
215			},
216
217			(Bound::Unbounded, Bound::Included(y)) => expr::Expr::Prefix {
218				op: expr::PrefixOperator::RangeInclusive,
219				expr: Box::new(y.into_literal()),
220			},
221			(Bound::Included(x), Bound::Included(y)) => expr::Expr::Binary {
222				left: Box::new(x.into_literal()),
223				op: expr::BinaryOperator::RangeInclusive,
224				right: Box::new(y.into_literal()),
225			},
226			(Bound::Excluded(x), Bound::Included(y)) => expr::Expr::Binary {
227				left: Box::new(x.into_literal()),
228				op: expr::BinaryOperator::RangeSkipInclusive,
229				right: Box::new(y.into_literal()),
230			},
231			(Bound::Unbounded, Bound::Excluded(y)) => expr::Expr::Prefix {
232				op: expr::PrefixOperator::Range,
233				expr: Box::new(y.into_literal()),
234			},
235			(Bound::Included(x), Bound::Excluded(y)) => expr::Expr::Binary {
236				left: Box::new(x.into_literal()),
237				op: expr::BinaryOperator::Range,
238				right: Box::new(y.into_literal()),
239			},
240			(Bound::Excluded(x), Bound::Excluded(y)) => expr::Expr::Binary {
241				left: Box::new(x.into_literal()),
242				op: expr::BinaryOperator::RangeSkip,
243				right: Box::new(y.into_literal()),
244			},
245		}
246	}
247}
248
249/// `TypedRange<i64>` lives in `surrealdb-common`, below the value layer, so the
250/// conversion into an [`Array`] cannot be an inherent method on it.
251pub trait IntegerRangeExt {
252	fn cast_to_array(self) -> Array;
253}
254
255impl IntegerRangeExt for TypedRange<i64> {
256	fn cast_to_array(self) -> Array {
257		let iter = self.iter();
258		Array(iter.map(|i| Value::Number(Number::Int(i))).collect())
259	}
260}
261
262impl<T> From<TypedRange<T>> for Range
263where
264	Value: From<T>,
265{
266	fn from(value: TypedRange<T>) -> Self {
267		Range {
268			start: value.start.map(From::from),
269			end: value.end.map(From::from),
270		}
271	}
272}
273
274#[cfg(test)]
275mod test {
276	use super::Range;
277	use crate::syn;
278	use crate::val::Value;
279	use crate::val::convert_public::convert_public_value_to_internal;
280
281	fn r(r: &str) -> Range {
282		let Value::Range(r) = convert_public_value_to_internal(syn::value(r).unwrap()) else {
283			panic!()
284		};
285		*r
286	}
287
288	fn round_trip(r: &Range) {
289		let enc = storekey::encode_vec(r).unwrap();
290		let dec = storekey::decode_borrow(&enc).unwrap();
291		assert_eq!(r, &dec)
292	}
293
294	fn ensure_order(a: &Range, b: &Range) {
295		let a_enc = storekey::encode_vec(a).unwrap();
296		let b_enc = storekey::encode_vec(b).unwrap();
297
298		assert_eq!(
299			a.cmp(b),
300			a_enc.cmp(&b_enc),
301			"ordering of {a:?} {b:?} is not correct after encoding"
302		);
303	}
304
305	#[test]
306	fn encode_decode() {
307		round_trip(&r("1..2"));
308		round_trip(&r(".."));
309		round_trip(&r("1>.."));
310		round_trip(&r("1>..=3"));
311		round_trip(&r("..3"));
312		round_trip(&r("'a'..'b'"));
313	}
314
315	#[test]
316	fn encoding_ordering() {
317		ensure_order(&r(".."), &r(".."));
318		ensure_order(&r(".."), &r("1.."));
319		ensure_order(&r("1.."), &r("1>.."));
320		ensure_order(&r(".."), &r("..1"));
321		ensure_order(&r(".."), &r("..=1"));
322		ensure_order(&r("1.."), &r("2.."));
323		ensure_order(&r("'a'.."), &r("'b'.."));
324	}
325}