surrealdb_core/sql/id/
range.rs

1use super::Id;
2use crate::{
3	ctx::Context,
4	dbs::Options,
5	doc::CursorDoc,
6	err::Error,
7	sql::{Range, Value},
8};
9use reblessive::tree::Stk;
10use revision::revisioned;
11use serde::{Deserialize, Serialize};
12use std::{cmp::Ordering, fmt, ops::Bound};
13
14#[revisioned(revision = 1)]
15#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]
16#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
17pub struct IdRange {
18	pub beg: Bound<Id>,
19	pub end: Bound<Id>,
20}
21
22impl TryFrom<(Bound<Id>, Bound<Id>)> for IdRange {
23	type Error = Error;
24	fn try_from((beg, end): (Bound<Id>, Bound<Id>)) -> Result<Self, Self::Error> {
25		if matches!(beg, Bound::Included(Id::Range(_)) | Bound::Excluded(Id::Range(_))) {
26			return Err(Error::IdInvalid {
27				value: "range".into(),
28			});
29		}
30
31		if matches!(end, Bound::Included(Id::Range(_)) | Bound::Excluded(Id::Range(_))) {
32			return Err(Error::IdInvalid {
33				value: "range".into(),
34			});
35		}
36
37		Ok(IdRange {
38			beg,
39			end,
40		})
41	}
42}
43
44impl TryFrom<Range> for IdRange {
45	type Error = Error;
46	fn try_from(v: Range) -> Result<Self, Self::Error> {
47		let beg = match v.beg {
48			Bound::Included(beg) => Bound::Included(Id::try_from(beg)?),
49			Bound::Excluded(beg) => Bound::Excluded(Id::try_from(beg)?),
50			Bound::Unbounded => Bound::Unbounded,
51		};
52
53		let end = match v.end {
54			Bound::Included(end) => Bound::Included(Id::try_from(end)?),
55			Bound::Excluded(end) => Bound::Excluded(Id::try_from(end)?),
56			Bound::Unbounded => Bound::Unbounded,
57		};
58
59		// The TryFrom implementation ensures that the bounds do not contain an `Id::Range` value
60		IdRange::try_from((beg, end))
61	}
62}
63
64impl TryFrom<Value> for IdRange {
65	type Error = Error;
66	fn try_from(v: Value) -> Result<Self, Self::Error> {
67		match v {
68			Value::Range(v) => IdRange::try_from(*v),
69			v => Err(Error::IdInvalid {
70				value: v.kindof().to_string(),
71			}),
72		}
73	}
74}
75
76impl PartialOrd for IdRange {
77	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
78		Some(self.cmp(other))
79	}
80}
81
82impl Ord for IdRange {
83	fn cmp(&self, other: &Self) -> Ordering {
84		match &self.beg {
85			Bound::Unbounded => match &other.beg {
86				Bound::Unbounded => Ordering::Equal,
87				_ => Ordering::Less,
88			},
89			Bound::Included(v) => match &other.beg {
90				Bound::Unbounded => Ordering::Greater,
91				Bound::Included(w) => match v.cmp(w) {
92					Ordering::Equal => match &self.end {
93						Bound::Unbounded => match &other.end {
94							Bound::Unbounded => Ordering::Equal,
95							_ => Ordering::Greater,
96						},
97						Bound::Included(v) => match &other.end {
98							Bound::Unbounded => Ordering::Less,
99							Bound::Included(w) => v.cmp(w),
100							_ => Ordering::Greater,
101						},
102						Bound::Excluded(v) => match &other.end {
103							Bound::Excluded(w) => v.cmp(w),
104							_ => Ordering::Less,
105						},
106					},
107					ordering => ordering,
108				},
109				_ => Ordering::Less,
110			},
111			Bound::Excluded(v) => match &other.beg {
112				Bound::Excluded(w) => match v.cmp(w) {
113					Ordering::Equal => match &self.end {
114						Bound::Unbounded => match &other.end {
115							Bound::Unbounded => Ordering::Equal,
116							_ => Ordering::Greater,
117						},
118						Bound::Included(v) => match &other.end {
119							Bound::Unbounded => Ordering::Less,
120							Bound::Included(w) => v.cmp(w),
121							_ => Ordering::Greater,
122						},
123						Bound::Excluded(v) => match &other.end {
124							Bound::Excluded(w) => v.cmp(w),
125							_ => Ordering::Less,
126						},
127					},
128					ordering => ordering,
129				},
130				_ => Ordering::Greater,
131			},
132		}
133	}
134}
135
136impl fmt::Display for IdRange {
137	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
138		match &self.beg {
139			Bound::Unbounded => write!(f, ""),
140			Bound::Included(v) => write!(f, "{v}"),
141			Bound::Excluded(v) => write!(f, "{v}>"),
142		}?;
143		match &self.end {
144			Bound::Unbounded => write!(f, ".."),
145			Bound::Excluded(v) => write!(f, "..{v}"),
146			Bound::Included(v) => write!(f, "..={v}"),
147		}?;
148		Ok(())
149	}
150}
151
152impl IdRange {
153	/// Process the values in the bounds for this IdRange
154	pub(crate) async fn compute(
155		&self,
156		stk: &mut Stk,
157		ctx: &Context,
158		opt: &Options,
159		doc: Option<&CursorDoc>,
160	) -> Result<IdRange, Error> {
161		let beg = match &self.beg {
162			Bound::Included(beg) => {
163				Bound::Included(stk.run(|stk| beg.compute(stk, ctx, opt, doc)).await?)
164			}
165			Bound::Excluded(beg) => {
166				Bound::Excluded(stk.run(|stk| beg.compute(stk, ctx, opt, doc)).await?)
167			}
168			Bound::Unbounded => Bound::Unbounded,
169		};
170
171		let end = match &self.end {
172			Bound::Included(end) => {
173				Bound::Included(stk.run(|stk| end.compute(stk, ctx, opt, doc)).await?)
174			}
175			Bound::Excluded(end) => {
176				Bound::Excluded(stk.run(|stk| end.compute(stk, ctx, opt, doc)).await?)
177			}
178			Bound::Unbounded => Bound::Unbounded,
179		};
180
181		// The TryFrom implementation ensures that the bounds do not contain an `Id::Range` value
182		IdRange::try_from((beg, end))
183	}
184}