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#[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 pub const fn unbounded() -> Self {
30 Range {
31 start: Bound::Unbounded,
32 end: Bound::Unbounded,
33 }
34 }
35
36 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 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
249pub 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}