diff --git a/edgedb-protocol/src/model.rs b/edgedb-protocol/src/model.rs index 6930b45e..f90ea498 100644 --- a/edgedb-protocol/src/model.rs +++ b/edgedb-protocol/src/model.rs @@ -13,7 +13,7 @@ pub use self::json::Json; pub use self::time::{DateDuration, RelativeDuration}; pub use self::time::{Datetime, Duration, LocalDate, LocalDatetime, LocalTime}; pub use memory::ConfigMemory; -pub use range::Range; +pub use range::{Range, RangeScalar}; pub use uuid::Uuid; pub use vector::Vector; diff --git a/edgedb-protocol/src/model/range.rs b/edgedb-protocol/src/model/range.rs index 35a9c9f1..420365c2 100644 --- a/edgedb-protocol/src/model/range.rs +++ b/edgedb-protocol/src/model/range.rs @@ -1,4 +1,8 @@ +use crate::queryable::Queryable; use crate::value::Value; +use core::ops::{self, Bound}; +use std::convert::TryFrom; +use std::ops::RangeBounds; pub(crate) const EMPTY: usize = 0x01; pub(crate) const LB_INC: usize = 0x02; @@ -6,70 +10,549 @@ pub(crate) const UB_INC: usize = 0x04; pub(crate) const LB_INF: usize = 0x08; pub(crate) const UB_INF: usize = 0x10; -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Range { - pub(crate) lower: Option, - pub(crate) upper: Option, - pub(crate) inc_lower: bool, - pub(crate) inc_upper: bool, - pub(crate) empty: bool, +mod edgedb_ord { + pub struct PrivateToken; + use crate::model::{Datetime, LocalDate, LocalDatetime}; + use crate::value::Value; + use std::cmp::Ordering; + + pub trait EdgedbOrd { + // the token makes this uncallable and the trait unimplementable + // can be removed once the design is stable + fn edgedb_cmp(&self, other: &Self, _: PrivateToken) -> Ordering; + } + + pub fn edgedb_cmp(x: &T, y: &T) -> Ordering { + EdgedbOrd::edgedb_cmp(x, y, PrivateToken) + } + + impl EdgedbOrd for i32 { + fn edgedb_cmp(&self, other: &Self, _: PrivateToken) -> Ordering { + self.cmp(other) + } + } + + impl EdgedbOrd for i64 { + fn edgedb_cmp(&self, other: &Self, _: PrivateToken) -> Ordering { + self.cmp(other) + } + } + + impl EdgedbOrd for Datetime { + fn edgedb_cmp(&self, other: &Self, _: PrivateToken) -> Ordering { + self.cmp(other) + } + } + + impl EdgedbOrd for LocalDatetime { + fn edgedb_cmp(&self, other: &Self, _: PrivateToken) -> Ordering { + self.cmp(other) + } + } + + impl EdgedbOrd for LocalDate { + fn edgedb_cmp(&self, other: &Self, _: PrivateToken) -> Ordering { + self.cmp(other) + } + } + + // NaN is bigger than all other values + impl EdgedbOrd for f32 { + fn edgedb_cmp(&self, other: &Self, _: PrivateToken) -> Ordering { + self.partial_cmp(other) + .unwrap_or_else(|| self.is_nan().cmp(&other.is_nan())) + } + } + + // NaN is bigger than all other values + impl EdgedbOrd for f64 { + fn edgedb_cmp(&self, other: &Self, _: PrivateToken) -> Ordering { + self.partial_cmp(other) + .unwrap_or_else(|| self.is_nan().cmp(&other.is_nan())) + } + } + + impl EdgedbOrd for Value { + fn edgedb_cmp(&self, other: &Self, _: PrivateToken) -> Ordering { + match (self, other) { + (Value::Int32(x), Value::Int32(y)) => edgedb_cmp(x, y), + (Value::Int64(x), Value::Int64(y)) => edgedb_cmp(x, y), + (Value::Float32(x), Value::Float32(y)) => edgedb_cmp(x, y), + (Value::Float64(x), Value::Float64(y)) => edgedb_cmp(x, y), + (Value::Datetime(x), Value::Datetime(y)) => edgedb_cmp(x, y), + (Value::LocalDatetime(x), Value::LocalDatetime(y)) => edgedb_cmp(x, y), + (Value::LocalDate(x), Value::LocalDate(y)) => edgedb_cmp(x, y), + (_, _) => panic!( + "Both values in a range need to have the same type. Found {} and {}", + self.kind(), + other.kind() + ), + } + } + } + + impl EdgedbOrd for &T { + fn edgedb_cmp(&self, other: &Self, _: PrivateToken) -> Ordering { + edgedb_cmp(*self, *other) + } + } + + impl EdgedbOrd for Box + where + T: EdgedbOrd, + { + fn edgedb_cmp(&self, other: &Self, _: PrivateToken) -> Ordering { + edgedb_cmp(self, other) + } + } +} + +mod range_scalar { + use std::ops::{self, Bound}; + + use super::edgedb_ord::EdgedbOrd; + use crate::model::{Datetime, LocalDate, LocalDatetime, OutOfRangeError}; + use crate::value::Value; + + pub struct PrivateToken; + + pub trait RangeScalar: EdgedbOrd + Sized { + fn is_discrete(&self, _: PrivateToken) -> bool { + false + } + + fn step_up(&mut self, _: PrivateToken) -> Result<(), OutOfRangeError> { + Ok(()) + } + + fn empty_value() -> Self; + } + + impl RangeScalar for i32 { + fn is_discrete(&self, _: PrivateToken) -> bool { + true + } + + fn step_up(&mut self, _: PrivateToken) -> Result<(), OutOfRangeError> { + *self = self.checked_add(1).ok_or(OutOfRangeError)?; + Ok(()) + } + + fn empty_value() -> Self { + 0 + } + } + + impl RangeScalar for i64 { + fn is_discrete(&self, _: PrivateToken) -> bool { + true + } + + fn step_up(&mut self, _: PrivateToken) -> Result<(), OutOfRangeError> { + *self = self.checked_add(1).ok_or(OutOfRangeError)?; + Ok(()) + } + + fn empty_value() -> Self { + 0 + } + } + impl RangeScalar for f32 { + fn empty_value() -> Self { + 0f32 + } + } + + impl RangeScalar for f64 { + fn empty_value() -> Self { + 0f64 + } + } + + // impl RangeScalar for Decimal {} isn't possible because it doesn't support comparisons yet + impl RangeScalar for Value { + fn empty_value() -> Self { + panic!("Range is not supported"); + } + + fn is_discrete(&self, _: PrivateToken) -> bool { + match self { + Value::Int32(x) => i32::is_discrete(x, PrivateToken), + Value::Int64(x) => i64::is_discrete(x, PrivateToken), + Value::Float32(x) => f32::is_discrete(x, PrivateToken), + Value::Float64(x) => f64::is_discrete(x, PrivateToken), + Value::Datetime(x) => Datetime::is_discrete(x, PrivateToken), + Value::LocalDatetime(x) => LocalDatetime::is_discrete(x, PrivateToken), + Value::LocalDate(x) => LocalDate::is_discrete(x, PrivateToken), + _ => panic!("Unexpected Value kind {}", self.kind()), + } + } + + fn step_up(&mut self, _: PrivateToken) -> Result<(), OutOfRangeError> { + todo!() + } + } + + impl RangeScalar for Datetime { + fn empty_value() -> Self { + Datetime::from_unix_micros(0) + } + } + + impl RangeScalar for LocalDatetime { + fn empty_value() -> Self { + Datetime::from_unix_micros(0).into() + } + } + + impl RangeScalar for LocalDate { + fn is_discrete(&self, _: PrivateToken) -> bool { + true + } + + fn step_up(&mut self, _: PrivateToken) -> Result<(), OutOfRangeError> { + *self = Self::try_from_days(self.to_days() + 1)?; + Ok(()) + } + + fn empty_value() -> Self { + Self::from_days(0) + } + } + + impl RangeScalar for Box { + fn is_discrete(&self, _: PrivateToken) -> bool { + self.as_ref().is_discrete(PrivateToken) + } + + fn step_up(&mut self, _: PrivateToken) -> Result<(), OutOfRangeError> { + self.as_mut().step_up(PrivateToken) + } + + fn empty_value() -> Self { + Box::new(T::empty_value()) + } + } + + pub fn start_to_inclusive( + bound: Bound, + ) -> Result, OutOfRangeError> { + match bound { + Bound::Excluded(mut value) => { + if value.is_discrete(PrivateToken) { + value.step_up(PrivateToken)?; + } + Ok(Bound::Included(value)) + } + other => Ok(other), + } + } + + pub fn end_to_exclusive(bound: Bound) -> Result, OutOfRangeError> { + match bound { + Bound::Included(mut value) => { + if value.is_discrete(PrivateToken) { + value.step_up(PrivateToken)?; + } + Ok(Bound::Excluded(value)) + } + other => Ok(other), + } + } +} + +pub use range_scalar::RangeScalar; + +use super::OutOfRangeError; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct BoundedRange { + start_bound: Bound, + end_bound: Bound, +} + +impl BoundedRange { + pub fn start_bound(&self) -> Bound<&T> { + self.start_bound.as_ref() + } + + pub fn end_bound(&self) -> Bound<&T> { + self.end_bound.as_ref() + } + + pub fn as_ref(&self) -> BoundedRange<&T> { + BoundedRange { + start_bound: self.start_bound(), + end_bound: self.end_bound(), + } + } + + pub fn into_bounds(self) -> (Bound, Bound) { + (self.start_bound, self.end_bound) + } + + pub const fn full() -> Self { + BoundedRange { + start_bound: Bound::Unbounded, + end_bound: Bound::Unbounded, + } + } +} + +fn are_range_bounds_empty(start_bound: Bound<&T>, end_bound: Bound<&T>) -> bool { + use edgedb_ord::edgedb_cmp; + + match (start_bound, end_bound) { + (Bound::Unbounded, _) => false, + (_, Bound::Unbounded) => false, + (Bound::Included(start), Bound::Included(end)) => edgedb_cmp(start, end).is_gt(), + (Bound::Excluded(start), Bound::Excluded(end)) => edgedb_cmp(start, end).is_ge(), + (Bound::Included(start), Bound::Excluded(end)) => edgedb_cmp(start, end).is_ge(), + (Bound::Excluded(start), Bound::Included(end)) => edgedb_cmp(start, end).is_ge(), + } +} + +impl BoundedRange { + fn from_bounds( + start_bound: Bound, + end_bound: Bound, + ) -> Result>, OutOfRangeError> { + let start_bound = range_scalar::start_to_inclusive(start_bound)?; + let end_bound = range_scalar::end_to_exclusive(end_bound)?; + + Ok( + if are_range_bounds_empty(start_bound.as_ref(), end_bound.as_ref()) { + Some(BoundedRange { + start_bound, + end_bound, + }) + } else { + None + }, + ) + } +} + +impl RangeBounds for BoundedRange { + fn start_bound(&self) -> Bound<&T> { + self.start_bound() + } + + fn end_bound(&self) -> Bound<&T> { + self.end_bound() + } +} + +#[cfg_attr( + feature = "with-serde", + derive(serde::Serialize, serde::Deserialize), + serde(try_from = "RangeFields", into = "RangeFields") +)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum Range { + Empty, + NonEmpty(BoundedRange), +} + +impl Range { + pub fn from_bounds( + start_bound: Bound, + end_bound: Bound, + ) -> Result, OutOfRangeError> { + Ok(match BoundedRange::from_bounds(start_bound, end_bound)? { + Some(bounded) => Range::NonEmpty(bounded), + None => Range::Empty, + }) + } +} + +impl From> for Range { + fn from(src: ops::Range) -> Range { + Self::from_bounds(Bound::Included(src.start), Bound::Excluded(src.end)) + .expect("OutOfBoundsErrors can't occur because the start is already inclusive, and the end exclusive") + } +} + +impl From for Range { + fn from(_: ops::RangeFull) -> Range { + Range::full() + } +} + +impl From> for Range { + fn from(src: ops::RangeTo) -> Range { + Self::from_bounds(Bound::Unbounded, Bound::Excluded(src.end)) + .expect("OutOfBoundsErrors can't occur because the start is unbounded, and the end exclusive") + } +} + +impl TryFrom> for Range { + type Error = OutOfRangeError; + + fn try_from(src: ops::RangeInclusive) -> Result { + let (start, end) = src.into_inner(); + Self::from_bounds(Bound::Included(start), Bound::Included(end)) + } } -impl From> for Range { - fn from(src: std::ops::Range) -> Range { - Range { - lower: Some(src.start), - upper: Some(src.end), - inc_lower: true, - inc_upper: false, - empty: false, +impl TryFrom> for Range { + type Error = OutOfRangeError; + + fn try_from(src: ops::RangeToInclusive) -> Result { + Self::from_bounds(Bound::Unbounded, Bound::Included(src.end)) + } +} + +struct FromRangeError(&'static &'static str); + +impl TryFrom> for ops::Range { + type Error = FromRangeError; + + fn try_from(value: Range) -> Result { + match value { + Range::Empty => Ok(ops::Range { + start: T::empty_value(), + end: T::empty_value() + }), + Range::NonEmpty(bounded) => { + let inclusive_start = match bounded.start_bound { + Bound::Included(x) => x, + Bound::Excluded(_) => return Err(FromRangeError(&"start_bound must be Included, was Excluded")), + Bound::Unbounded => return Err(FromRangeError(&"start_bound must be Included, was Unbounded")), + }; + let exclusive_end = match bounded.end_bound { + Bound::Included(_) => return Err(FromRangeError(&"end_bound must be Excluded, was Included")), + Bound::Excluded(x) => x, + Bound::Unbounded => return Err(FromRangeError(&"end_bound must be Excluded, was Unbounded")), + }; + Ok(ops::Range { + start: inclusive_start, + end: exclusive_end + }) + } } } } -impl> From> for Value { - fn from(src: std::ops::Range) -> Value { +impl + RangeScalar> From> for Value { + fn from(src: ops::Range) -> Value { Range::from(src).into_value() } } impl Range { /// Constructor of the empty range - pub fn empty() -> Range { - Range { - lower: None, - upper: None, - inc_lower: true, - inc_upper: false, - empty: true, - } + pub const fn empty() -> Self { + Range::Empty } + + pub const fn full() -> Range { + Range::NonEmpty(BoundedRange::full()) + } + + //tbd: should this exist? Should it return an option? pub fn lower(&self) -> Option<&T> { - self.lower.as_ref() + match self { + Range::Empty => None, + Range::NonEmpty(bounded) => match bounded.start_bound() { + Bound::Included(value) => Some(value), + Bound::Excluded(value) => Some(value), + Bound::Unbounded => None, + }, + } } + + //tbd: should this exist? Should it return an option? pub fn upper(&self) -> Option<&T> { - self.upper.as_ref() + match self { + Range::Empty => None, + Range::NonEmpty(bounded) => match bounded.end_bound() { + Bound::Included(value) => Some(value), + Bound::Excluded(value) => Some(value), + Bound::Unbounded => None, + }, + } } + + //tbd: should this exist? Should it return an option? pub fn inc_lower(&self) -> bool { - self.inc_lower + match self { + Range::Empty => false, + Range::NonEmpty(bounded) => match bounded.start_bound() { + Bound::Included(_) => true, + Bound::Excluded(_) => false, + Bound::Unbounded => false, + }, + } } + + //tbd: should this exist? Should it return an option? pub fn inc_upper(&self) -> bool { - self.inc_upper + match self { + Range::Empty => false, + Range::NonEmpty(bounded) => match bounded.end_bound() { + Bound::Included(_) => true, + Bound::Excluded(_) => false, + Bound::Unbounded => false, + }, + } } + pub fn is_empty(&self) -> bool { - self.empty + match self { + Range::Empty => true, + Range::NonEmpty(_) => false, + } + } + + pub fn as_ref(&self) -> Range<&T> { + match self { + Range::Empty => Range::Empty, + Range::NonEmpty(bounded) => Range::NonEmpty(bounded.as_ref()), + } } } impl> Range { pub fn into_value(self) -> Value { - Value::Range(Range { - lower: self.lower.map(|v| Box::new(v.into())), - upper: self.upper.map(|v| Box::new(v.into())), - inc_lower: self.inc_lower, - inc_upper: self.inc_upper, - empty: self.empty, + Value::Range(match self { + Range::Empty => Range::Empty, + Range::NonEmpty(non_empty) => { + let start = non_empty.start_bound.map(|v| Box::new(v.into())); + let end = non_empty.end_bound.map(|v| Box::new(v.into())); + Range::from_bounds(start, end) + .expect("Converting into `Value` should not affect the validity of the bounds. T::Into appears to be broken.") + } }) } } + +#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))] +struct RangeFields { + lower: Option, + upper: Option, + inc_lower: Option, + inc_upper: Option, + empty: Option, +} + +impl<'t, T> From<&'t Range> for RangeFields<&'t T> { + fn from(value: &'t Range) -> Self { + match value { + Range::Empty => RangeFields { + lower: None, + upper: None, + inc_lower: None, + inc_upper: None, + empty: Some(true), + }, + Range::NonEmpty(_) => RangeFields { + lower: value.lower(), + upper: value.upper(), + inc_lower: Some(value.inc_lower()), + inc_upper: Some(value.inc_upper()), + empty: None, + }, + } + } +} diff --git a/edgedb-protocol/src/model/time.rs b/edgedb-protocol/src/model/time.rs index 67129e69..02c975f2 100644 --- a/edgedb-protocol/src/model/time.rs +++ b/edgedb-protocol/src/model/time.rs @@ -580,7 +580,7 @@ impl LocalDate { days: -(30 * 365 + 7), }; // 1970-01-01 - fn try_from_days(days: i32) -> Result { + pub(crate) fn try_from_days(days: i32) -> Result { if !(Self::MIN.days..=Self::MAX.days).contains(&days) { return Err(OutOfRangeError); } diff --git a/edgedb-protocol/src/serialization/decode/raw_scalar.rs b/edgedb-protocol/src/serialization/decode/raw_scalar.rs index 97906e36..8790302c 100644 --- a/edgedb-protocol/src/serialization/decode/raw_scalar.rs +++ b/edgedb-protocol/src/serialization/decode/raw_scalar.rs @@ -1,5 +1,6 @@ use std::convert::TryInto; use std::mem::size_of; +use std::ops::Bound; use std::str; use std::time::SystemTime; @@ -10,7 +11,7 @@ use snafu::{ensure, ResultExt}; use crate::codec; use crate::descriptors::{Descriptor, TypePos}; use crate::errors::{self, DecodeError}; -use crate::model::range; +use crate::model::range::{self, RangeScalar}; use crate::model::{BigInt, Decimal}; use crate::model::{ConfigMemory, Range}; use crate::model::{DateDuration, RelativeDuration}; @@ -675,19 +676,19 @@ impl ScalarArg for EnumValue { } } -impl ScalarArg for Range { +impl ScalarArg for Range { fn encode(&self, encoder: &mut Encoder) -> Result<(), Error> { - let flags = if self.empty { + let flags = if self.is_empty() { range::EMPTY } else { - (if self.inc_lower { range::LB_INC } else { 0 }) - | (if self.inc_upper { range::UB_INC } else { 0 }) - | (if self.lower.is_none() { + (if self.inc_lower() { range::LB_INC } else { 0 }) + | (if self.inc_upper() { range::UB_INC } else { 0 }) + | (if self.lower().is_none() { range::LB_INF } else { 0 }) - | (if self.upper.is_none() { + | (if self.upper().is_none() { range::UB_INF } else { 0 @@ -696,11 +697,11 @@ impl ScalarArg for Range { encoder.buf.reserve(1); encoder.buf.put_u8(flags as u8); - if let Some(lower) = &self.lower { + if let Some(lower) = &self.lower() { encoder.length_prefixed(|encoder| lower.encode(encoder))? } - if let Some(upper) = &self.upper { + if let Some(upper) = &self.upper() { encoder.length_prefixed(|encoder| upper.encode(encoder))?; } Ok(()) @@ -714,20 +715,22 @@ impl ScalarArg for Range { } } fn to_value(&self) -> Result { - Ok(Value::Range(Range { - lower: self - .lower - .as_ref() - .map(|v| v.to_value().map(Box::new)) - .transpose()?, - upper: self - .upper - .as_ref() - .map(|v| v.to_value().map(Box::new)) - .transpose()?, - inc_lower: self.inc_lower, - inc_upper: self.inc_upper, - empty: self.empty, + Ok(Value::Range(match self { + Range::Empty => Range::Empty, + Range::NonEmpty(non_empty) => { + let start = match non_empty.start_bound() { + Bound::Included(v) => Bound::Included(Box::new(v.to_value()?)), + Bound::Excluded(v) => Bound::Excluded(Box::new(v.to_value()?)), + Bound::Unbounded => Bound::Unbounded, + }; + let end = match non_empty.end_bound() { + Bound::Included(v) => Bound::Included(Box::new(v.to_value()?)), + Bound::Excluded(v) => Bound::Excluded(Box::new(v.to_value()?)), + Bound::Unbounded => Bound::Unbounded, + }; + Range::from_bounds(start, end) + .expect("Converting into `Value` should not affect the validity of the bounds. T::Into appears to be broken.") + } })) } }