surrealdb_core/sql/
range.rs

1use crate::ctx::Context;
2use crate::dbs::Options;
3use crate::doc::CursorDoc;
4use crate::err::Error;
5use crate::sql::Cond;
6use crate::sql::Expression;
7use crate::sql::Ident;
8use crate::sql::Idiom;
9use crate::sql::Operator;
10use crate::sql::Part;
11use crate::sql::Thing;
12use crate::sql::{strand::no_nul_bytes, Id, Value};
13use crate::syn;
14use reblessive::tree::Stk;
15use revision::revisioned;
16use serde::{Deserialize, Serialize};
17use std::cmp::Ordering;
18use std::fmt;
19use std::ops::Bound;
20use std::str::FromStr;
21
22const ID: &str = "id";
23pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Range";
24
25#[revisioned(revision = 1)]
26#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]
27#[serde(rename = "$surrealdb::private::sql::Range")]
28#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
29#[non_exhaustive]
30pub struct Range {
31	#[serde(with = "no_nul_bytes")]
32	pub tb: String,
33	pub beg: Bound<Id>,
34	pub end: Bound<Id>,
35}
36
37impl FromStr for Range {
38	type Err = ();
39	fn from_str(s: &str) -> Result<Self, Self::Err> {
40		Self::try_from(s)
41	}
42}
43
44impl TryFrom<&str> for Range {
45	type Error = ();
46	fn try_from(v: &str) -> Result<Self, Self::Error> {
47		match syn::range(v) {
48			Ok(v) => Ok(v),
49			_ => Err(()),
50		}
51	}
52}
53
54impl Range {
55	/// Construct a new range
56	pub fn new(tb: String, beg: Bound<Id>, end: Bound<Id>) -> Self {
57		Self {
58			tb,
59			beg,
60			end,
61		}
62	}
63
64	/// Convert `Range` to `Cond`
65	pub fn to_cond(self) -> Option<Cond> {
66		match (self.beg, self.end) {
67			(Bound::Unbounded, Bound::Unbounded) => None,
68			(Bound::Unbounded, Bound::Excluded(id)) => {
69				Some(Cond(Value::Expression(Box::new(Expression::new(
70					Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
71					Operator::LessThan,
72					Thing::from((self.tb, id)).into(),
73				)))))
74			}
75			(Bound::Unbounded, Bound::Included(id)) => {
76				Some(Cond(Value::Expression(Box::new(Expression::new(
77					Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
78					Operator::LessThanOrEqual,
79					Thing::from((self.tb, id)).into(),
80				)))))
81			}
82			(Bound::Excluded(id), Bound::Unbounded) => {
83				Some(Cond(Value::Expression(Box::new(Expression::new(
84					Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
85					Operator::MoreThan,
86					Thing::from((self.tb, id)).into(),
87				)))))
88			}
89			(Bound::Included(id), Bound::Unbounded) => {
90				Some(Cond(Value::Expression(Box::new(Expression::new(
91					Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
92					Operator::MoreThanOrEqual,
93					Thing::from((self.tb, id)).into(),
94				)))))
95			}
96			(Bound::Included(lid), Bound::Included(rid)) => {
97				Some(Cond(Value::Expression(Box::new(Expression::new(
98					Value::Expression(Box::new(Expression::new(
99						Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
100						Operator::MoreThanOrEqual,
101						Thing::from((self.tb.clone(), lid)).into(),
102					))),
103					Operator::And,
104					Value::Expression(Box::new(Expression::new(
105						Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
106						Operator::LessThanOrEqual,
107						Thing::from((self.tb, rid)).into(),
108					))),
109				)))))
110			}
111			(Bound::Included(lid), Bound::Excluded(rid)) => {
112				Some(Cond(Value::Expression(Box::new(Expression::new(
113					Value::Expression(Box::new(Expression::new(
114						Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
115						Operator::MoreThanOrEqual,
116						Thing::from((self.tb.clone(), lid)).into(),
117					))),
118					Operator::And,
119					Value::Expression(Box::new(Expression::new(
120						Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
121						Operator::LessThan,
122						Thing::from((self.tb, rid)).into(),
123					))),
124				)))))
125			}
126			(Bound::Excluded(lid), Bound::Included(rid)) => {
127				Some(Cond(Value::Expression(Box::new(Expression::new(
128					Value::Expression(Box::new(Expression::new(
129						Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
130						Operator::MoreThan,
131						Thing::from((self.tb.clone(), lid)).into(),
132					))),
133					Operator::And,
134					Value::Expression(Box::new(Expression::new(
135						Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
136						Operator::LessThanOrEqual,
137						Thing::from((self.tb, rid)).into(),
138					))),
139				)))))
140			}
141			(Bound::Excluded(lid), Bound::Excluded(rid)) => {
142				Some(Cond(Value::Expression(Box::new(Expression::new(
143					Value::Expression(Box::new(Expression::new(
144						Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
145						Operator::MoreThan,
146						Thing::from((self.tb.clone(), lid)).into(),
147					))),
148					Operator::And,
149					Value::Expression(Box::new(Expression::new(
150						Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
151						Operator::LessThan,
152						Thing::from((self.tb, rid)).into(),
153					))),
154				)))))
155			}
156		}
157	}
158
159	/// Process this type returning a computed simple Value
160	pub(crate) async fn compute(
161		&self,
162		stk: &mut Stk,
163		ctx: &Context<'_>,
164		opt: &Options,
165		doc: Option<&CursorDoc<'_>>,
166	) -> Result<Value, Error> {
167		Ok(Value::Range(Box::new(Range {
168			tb: self.tb.clone(),
169			beg: match &self.beg {
170				Bound::Included(id) => Bound::Included(id.compute(stk, ctx, opt, doc).await?),
171				Bound::Excluded(id) => Bound::Excluded(id.compute(stk, ctx, opt, doc).await?),
172				Bound::Unbounded => Bound::Unbounded,
173			},
174			end: match &self.end {
175				Bound::Included(id) => Bound::Included(id.compute(stk, ctx, opt, doc).await?),
176				Bound::Excluded(id) => Bound::Excluded(id.compute(stk, ctx, opt, doc).await?),
177				Bound::Unbounded => Bound::Unbounded,
178			},
179		})))
180	}
181}
182
183impl PartialOrd for Range {
184	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
185		match self.tb.partial_cmp(&other.tb) {
186			Some(Ordering::Equal) => match &self.beg {
187				Bound::Unbounded => match &other.beg {
188					Bound::Unbounded => Some(Ordering::Equal),
189					_ => Some(Ordering::Less),
190				},
191				Bound::Included(v) => match &other.beg {
192					Bound::Unbounded => Some(Ordering::Greater),
193					Bound::Included(w) => match v.partial_cmp(w) {
194						Some(Ordering::Equal) => match &self.end {
195							Bound::Unbounded => match &other.end {
196								Bound::Unbounded => Some(Ordering::Equal),
197								_ => Some(Ordering::Greater),
198							},
199							Bound::Included(v) => match &other.end {
200								Bound::Unbounded => Some(Ordering::Less),
201								Bound::Included(w) => v.partial_cmp(w),
202								_ => Some(Ordering::Greater),
203							},
204							Bound::Excluded(v) => match &other.end {
205								Bound::Excluded(w) => v.partial_cmp(w),
206								_ => Some(Ordering::Less),
207							},
208						},
209						ordering => ordering,
210					},
211					_ => Some(Ordering::Less),
212				},
213				Bound::Excluded(v) => match &other.beg {
214					Bound::Excluded(w) => match v.partial_cmp(w) {
215						Some(Ordering::Equal) => match &self.end {
216							Bound::Unbounded => match &other.end {
217								Bound::Unbounded => Some(Ordering::Equal),
218								_ => Some(Ordering::Greater),
219							},
220							Bound::Included(v) => match &other.end {
221								Bound::Unbounded => Some(Ordering::Less),
222								Bound::Included(w) => v.partial_cmp(w),
223								_ => Some(Ordering::Greater),
224							},
225							Bound::Excluded(v) => match &other.end {
226								Bound::Excluded(w) => v.partial_cmp(w),
227								_ => Some(Ordering::Less),
228							},
229						},
230						ordering => ordering,
231					},
232					_ => Some(Ordering::Greater),
233				},
234			},
235			ordering => ordering,
236		}
237	}
238}
239
240impl fmt::Display for Range {
241	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
242		write!(f, "{}:", self.tb)?;
243		match &self.beg {
244			Bound::Unbounded => write!(f, ""),
245			Bound::Included(id) => write!(f, "{id}"),
246			Bound::Excluded(id) => write!(f, "{id}>"),
247		}?;
248		match &self.end {
249			Bound::Unbounded => write!(f, ".."),
250			Bound::Excluded(id) => write!(f, "..{id}"),
251			Bound::Included(id) => write!(f, "..={id}"),
252		}?;
253		Ok(())
254	}
255}