use std::cmp::Ordering;
use std::ops::Bound;
pub use common::range::{IntegerRangeIter, TypedRange};
use revision::revisioned;
use storekey::{BorrowDecode, Encode};
use surrealdb_types::{SqlFormat, ToSql, write_sql};
use super::value::CoerceErrorExt;
use crate::expr;
use crate::expr::kind::HasKind;
use crate::val::value::{Coerce, CoerceError};
use crate::val::{Array, IndexFormat, Number, Value};
#[revisioned(revision = 1)]
#[derive(Debug, Eq, PartialEq, Clone, Hash, Encode, BorrowDecode)]
#[storekey(format = "()")]
#[storekey(format = "IndexFormat")]
pub struct Range {
pub start: Bound<Value>,
pub end: Bound<Value>,
}
impl Range {
pub const fn unbounded() -> Self {
Range {
start: Bound::Unbounded,
end: Bound::Unbounded,
}
}
pub fn is_empty(&self) -> bool {
match &self.start {
Bound::Included(a) => match &self.end {
Bound::Included(b) => a > b,
Bound::Excluded(b) => a >= b,
Bound::Unbounded => false,
},
Bound::Excluded(a) => match &self.end {
Bound::Included(b) | Bound::Excluded(b) => a >= b,
Bound::Unbounded => false,
},
Bound::Unbounded => false,
}
}
pub fn intersect(self, other: Self) -> Self {
let start = match self.start {
Bound::Included(a) => match other.start {
Bound::Included(b) => Bound::Included(a.max(b)),
Bound::Excluded(b) => {
if a <= b {
Bound::Excluded(b)
} else {
Bound::Included(a)
}
}
Bound::Unbounded => Bound::Included(a),
},
Bound::Excluded(a) => match other.start {
Bound::Excluded(b) => Bound::Excluded(a.max(b)),
Bound::Included(b) => {
if a < b {
Bound::Included(b)
} else {
Bound::Excluded(a)
}
}
Bound::Unbounded => Bound::Excluded(a),
},
Bound::Unbounded => other.start,
};
let end = match self.end {
Bound::Included(a) => match other.end {
Bound::Included(b) => Bound::Included(a.min(b)),
Bound::Excluded(b) => {
if a >= b {
Bound::Excluded(b)
} else {
Bound::Included(a)
}
}
Bound::Unbounded => Bound::Included(a),
},
Bound::Excluded(a) => match other.end {
Bound::Excluded(b) => Bound::Excluded(a.min(b)),
Bound::Included(b) => {
if a > b {
Bound::Included(b)
} else {
Bound::Excluded(a)
}
}
Bound::Unbounded => Bound::Excluded(a),
},
Bound::Unbounded => other.end,
};
Range {
start,
end,
}
}
}
impl PartialOrd for Range {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Range {
fn cmp(&self, other: &Self) -> Ordering {
fn compare_bounds(a: &Bound<Value>, b: &Bound<Value>) -> Ordering {
match a {
Bound::Unbounded => match b {
Bound::Unbounded => Ordering::Equal,
_ => Ordering::Less,
},
Bound::Included(a) => match b {
Bound::Unbounded => Ordering::Greater,
Bound::Included(b) => a.cmp(b),
Bound::Excluded(_) => Ordering::Less,
},
Bound::Excluded(a) => match b {
Bound::Excluded(b) => a.cmp(b),
_ => Ordering::Greater,
},
}
}
match compare_bounds(&self.start, &other.start) {
Ordering::Equal => compare_bounds(&self.end, &other.end),
x => x,
}
}
}
impl ToSql for Range {
fn fmt_sql(&self, f: &mut String, sql_fmt: SqlFormat) {
match self.start {
Bound::Unbounded => {}
Bound::Included(ref x) => write_sql!(f, sql_fmt, "{x}"),
Bound::Excluded(ref x) => write_sql!(f, sql_fmt, "{x}>"),
}
write_sql!(f, sql_fmt, "..");
match self.end {
Bound::Unbounded => {}
Bound::Included(ref x) => write_sql!(f, sql_fmt, "={x}"),
Bound::Excluded(ref x) => write_sql!(f, sql_fmt, "{x}"),
}
}
}
impl Range {
pub fn can_coerce_to_typed<T: Coerce>(&self) -> bool {
match self.start {
Bound::Included(ref x) | Bound::Excluded(ref x) => {
if !x.can_coerce_to::<T>() {
return false;
}
}
Bound::Unbounded => {}
}
match self.end {
Bound::Included(ref x) | Bound::Excluded(ref x) => x.can_coerce_to::<T>(),
Bound::Unbounded => true,
}
}
pub fn coerce_to_typed<T: Coerce + HasKind>(self) -> Result<TypedRange<T>, CoerceError> {
let start = match self.start {
Bound::Included(x) => Bound::Included(
T::coerce(x).with_element_of(|| format!("range<{}>", T::kind().to_sql()))?,
),
Bound::Excluded(x) => Bound::Excluded(
T::coerce(x).with_element_of(|| format!("range<{}>", T::kind().to_sql()))?,
),
Bound::Unbounded => Bound::Unbounded,
};
let end = match self.end {
Bound::Included(x) => Bound::Included(
T::coerce(x).with_element_of(|| format!("range<{}>", T::kind().to_sql()))?,
),
Bound::Excluded(x) => Bound::Excluded(
T::coerce(x).with_element_of(|| format!("range<{}>", T::kind().to_sql()))?,
),
Bound::Unbounded => Bound::Unbounded,
};
Ok(TypedRange {
start,
end,
})
}
pub fn into_literal(self) -> expr::Expr {
match (self.start, self.end) {
(Bound::Unbounded, Bound::Unbounded) => {
expr::Expr::Literal(expr::Literal::UnboundedRange)
}
(Bound::Included(x), Bound::Unbounded) => expr::Expr::Postfix {
op: expr::PostfixOperator::Range,
expr: Box::new(x.into_literal()),
},
(Bound::Excluded(x), Bound::Unbounded) => expr::Expr::Postfix {
op: expr::PostfixOperator::RangeSkip,
expr: Box::new(x.into_literal()),
},
(Bound::Unbounded, Bound::Included(y)) => expr::Expr::Prefix {
op: expr::PrefixOperator::RangeInclusive,
expr: Box::new(y.into_literal()),
},
(Bound::Included(x), Bound::Included(y)) => expr::Expr::Binary {
left: Box::new(x.into_literal()),
op: expr::BinaryOperator::RangeInclusive,
right: Box::new(y.into_literal()),
},
(Bound::Excluded(x), Bound::Included(y)) => expr::Expr::Binary {
left: Box::new(x.into_literal()),
op: expr::BinaryOperator::RangeSkipInclusive,
right: Box::new(y.into_literal()),
},
(Bound::Unbounded, Bound::Excluded(y)) => expr::Expr::Prefix {
op: expr::PrefixOperator::Range,
expr: Box::new(y.into_literal()),
},
(Bound::Included(x), Bound::Excluded(y)) => expr::Expr::Binary {
left: Box::new(x.into_literal()),
op: expr::BinaryOperator::Range,
right: Box::new(y.into_literal()),
},
(Bound::Excluded(x), Bound::Excluded(y)) => expr::Expr::Binary {
left: Box::new(x.into_literal()),
op: expr::BinaryOperator::RangeSkip,
right: Box::new(y.into_literal()),
},
}
}
}
pub trait IntegerRangeExt {
fn cast_to_array(self) -> Array;
}
impl IntegerRangeExt for TypedRange<i64> {
fn cast_to_array(self) -> Array {
let iter = self.iter();
Array(iter.map(|i| Value::Number(Number::Int(i))).collect())
}
}
impl<T> From<TypedRange<T>> for Range
where
Value: From<T>,
{
fn from(value: TypedRange<T>) -> Self {
Range {
start: value.start.map(From::from),
end: value.end.map(From::from),
}
}
}
#[cfg(test)]
mod test {
use super::Range;
use crate::syn;
use crate::val::Value;
use crate::val::convert_public::convert_public_value_to_internal;
fn r(r: &str) -> Range {
let Value::Range(r) = convert_public_value_to_internal(syn::value(r).unwrap()) else {
panic!()
};
*r
}
fn round_trip(r: &Range) {
let enc = storekey::encode_vec(r).unwrap();
let dec = storekey::decode_borrow(&enc).unwrap();
assert_eq!(r, &dec)
}
fn ensure_order(a: &Range, b: &Range) {
let a_enc = storekey::encode_vec(a).unwrap();
let b_enc = storekey::encode_vec(b).unwrap();
assert_eq!(
a.cmp(b),
a_enc.cmp(&b_enc),
"ordering of {a:?} {b:?} is not correct after encoding"
);
}
#[test]
fn encode_decode() {
round_trip(&r("1..2"));
round_trip(&r(".."));
round_trip(&r("1>.."));
round_trip(&r("1>..=3"));
round_trip(&r("..3"));
round_trip(&r("'a'..'b'"));
}
#[test]
fn encoding_ordering() {
ensure_order(&r(".."), &r(".."));
ensure_order(&r(".."), &r("1.."));
ensure_order(&r("1.."), &r("1>.."));
ensure_order(&r(".."), &r("..1"));
ensure_order(&r(".."), &r("..=1"));
ensure_order(&r("1.."), &r("2.."));
ensure_order(&r("'a'.."), &r("'b'.."));
}
}