Skip to main content

surrealdb_types/value/
range.rs

1use std::cmp::Ordering;
2use std::ops::{Bound, RangeBounds};
3
4use serde::{Deserialize, Serialize};
5
6use crate::sql::{SqlFormat, ToSql};
7use crate::{SurrealValue, Value};
8
9/// Represents a range of values in SurrealDB
10///
11/// A range defines an interval between two values with inclusive or exclusive bounds.
12/// This is commonly used for range queries and comparisons.
13
14#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
15#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
16pub struct Range {
17	/// The lower bound of the range
18	pub start: Bound<Value>,
19	/// The upper bound of the range
20	pub end: Bound<Value>,
21}
22
23impl Range {
24	/// Creates a new range with specified start and ending bounds.
25	pub const fn new(start: Bound<Value>, end: Bound<Value>) -> Self {
26		Range {
27			start,
28			end,
29		}
30	}
31
32	/// Returns a range with no bounds.
33	pub const fn unbounded() -> Self {
34		Range {
35			start: Bound::Unbounded,
36			end: Bound::Unbounded,
37		}
38	}
39
40	/// Returns the start bound of the range.
41	pub fn start(&self) -> Bound<&Value> {
42		self.start.as_ref()
43	}
44
45	/// Returns the upper bound of the range.
46	pub fn end(&self) -> Bound<&Value> {
47		self.end.as_ref()
48	}
49
50	/// Convert into the inner bounds
51	pub fn into_inner(self) -> (Bound<Value>, Bound<Value>) {
52		(self.start, self.end)
53	}
54}
55
56impl From<(Bound<Value>, Bound<Value>)> for Range {
57	fn from((start, end): (Bound<Value>, Bound<Value>)) -> Self {
58		Range {
59			start,
60			end,
61		}
62	}
63}
64
65impl<T: SurrealValue> From<std::ops::Range<T>> for Range {
66	fn from(range: std::ops::Range<T>) -> Self {
67		Range {
68			start: Bound::Included(range.start.into_value()),
69			end: Bound::Excluded(range.end.into_value()),
70		}
71	}
72}
73
74impl<T: SurrealValue> From<std::ops::RangeInclusive<T>> for Range {
75	fn from(range: std::ops::RangeInclusive<T>) -> Self {
76		let (start, end) = range.into_inner();
77		Range {
78			start: Bound::Included(start.into_value()),
79			end: Bound::Included(end.into_value()),
80		}
81	}
82}
83
84impl<T: SurrealValue> From<std::ops::RangeFrom<T>> for Range {
85	fn from(range: std::ops::RangeFrom<T>) -> Self {
86		Range {
87			start: Bound::Included(range.start.into_value()),
88			end: Bound::Unbounded,
89		}
90	}
91}
92
93impl<T: SurrealValue> From<std::ops::RangeTo<T>> for Range {
94	fn from(range: std::ops::RangeTo<T>) -> Self {
95		Range {
96			start: Bound::Unbounded,
97			end: Bound::Excluded(range.end.into_value()),
98		}
99	}
100}
101
102impl From<std::ops::RangeFull> for Range {
103	fn from(_: std::ops::RangeFull) -> Self {
104		Range {
105			start: Bound::Unbounded,
106			end: Bound::Unbounded,
107		}
108	}
109}
110
111impl RangeBounds<Value> for Range {
112	fn start_bound(&self) -> Bound<&Value> {
113		self.start.as_ref()
114	}
115
116	fn end_bound(&self) -> Bound<&Value> {
117		self.end.as_ref()
118	}
119
120	fn contains<U>(&self, item: &U) -> bool
121	where
122		U: ?Sized + PartialOrd<Value>,
123		Value: PartialOrd<U>,
124	{
125		(match self.start_bound() {
126			Bound::Unbounded => true,
127			Bound::Included(start) => start <= item,
128			Bound::Excluded(start) => start < item,
129		}) && (match self.end_bound() {
130			Bound::Unbounded => true,
131			Bound::Included(end) => item <= end,
132			Bound::Excluded(end) => item < end,
133		})
134	}
135}
136
137impl PartialOrd for Range {
138	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
139		Some(self.cmp(other))
140	}
141}
142
143impl Ord for Range {
144	fn cmp(&self, other: &Self) -> Ordering {
145		fn compare_bounds(a: &Bound<Value>, b: &Bound<Value>) -> Ordering {
146			match a {
147				Bound::Unbounded => match b {
148					Bound::Unbounded => Ordering::Equal,
149					_ => Ordering::Less,
150				},
151				Bound::Included(a) => match b {
152					Bound::Unbounded => Ordering::Greater,
153					Bound::Included(b) => a.cmp(b),
154					Bound::Excluded(_) => Ordering::Less,
155				},
156				Bound::Excluded(a) => match b {
157					Bound::Excluded(b) => a.cmp(b),
158					_ => Ordering::Greater,
159				},
160			}
161		}
162		match compare_bounds(&self.start, &other.start) {
163			Ordering::Equal => compare_bounds(&self.end, &other.end),
164			x => x,
165		}
166	}
167}
168
169impl ToSql for Range {
170	fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
171		match self.start {
172			Bound::Unbounded => {}
173			Bound::Included(ref x) => x.fmt_sql(f, fmt),
174			Bound::Excluded(ref x) => {
175				x.fmt_sql(f, fmt);
176				f.push('>');
177			}
178		}
179		f.push_str("..");
180		match self.end {
181			Bound::Unbounded => {}
182			Bound::Included(ref x) => {
183				f.push('=');
184				x.fmt_sql(f, fmt);
185			}
186			Bound::Excluded(ref x) => {
187				x.fmt_sql(f, fmt);
188			}
189		}
190	}
191}