Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds IonData wrapper that uses Ion equivalence for equality #517

Merged
merged 8 commits into from
Apr 25, 2023
2 changes: 1 addition & 1 deletion src/binary/decimal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::io::Write;
use arrayvec::ArrayVec;
use bigdecimal::Zero;

use crate::ion_eq::IonEq;
use crate::ion_data::IonEq;
use crate::{
binary::{
int::DecodedInt, raw_binary_writer::MAX_INLINE_LENGTH, var_int::VarInt, var_uint::VarUInt,
Expand Down
8 changes: 8 additions & 0 deletions src/element/annotations.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::element::iterators::SymbolsIterator;
use crate::ion_data::IonOrd;
use crate::Symbol;
use std::cmp::Ordering;

/// An ordered sequence of symbols that convey additional, application-specific information about
/// their associated Ion value.
Expand Down Expand Up @@ -122,3 +124,9 @@ impl<'a> IntoIterator for &'a Annotations {
SymbolsIterator::new(self.symbols.as_slice())
}
}

impl IonOrd for Annotations {
fn ion_cmp(&self, other: &Self) -> Ordering {
self.symbols.ion_cmp(&other.symbols)
}
}
18 changes: 17 additions & 1 deletion src/element/bytes.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
use crate::ion_data::IonEq;
use crate::ion_data::IonOrd;
use std::cmp::Ordering;

/// An owned, immutable byte array.
/// ```rust
/// use ion_rs::element::Bytes;
Expand All @@ -15,11 +19,23 @@
/// let bytes: Bytes = b"world".into();
/// assert_eq!(&bytes, b"world".as_slice());
/// ```
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd)]
pub struct Bytes {
data: Vec<u8>,
}

impl IonEq for Bytes {
fn ion_eq(&self, other: &Self) -> bool {
self == other
}
}

impl IonOrd for Bytes {
fn ion_cmp(&self, other: &Self) -> Ordering {
self.cmp(other)
}
}

impl PartialEq<[u8]> for Bytes {
fn eq(&self, other: &[u8]) -> bool {
self.as_ref().eq(other)
Expand Down
2 changes: 1 addition & 1 deletion src/element/list.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::element::builders::SequenceBuilder;
use crate::element::iterators::ElementsIterator;
use crate::element::{Element, Sequence};
use crate::ion_eq::IonEq;
use crate::ion_data::IonEq;
use crate::text::text_formatter::IonValueFormatter;
use delegate::delegate;
use std::fmt::{Display, Formatter};
Expand Down
152 changes: 124 additions & 28 deletions src/element/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@

use crate::element::builders::{SequenceBuilder, StructBuilder};
use crate::element::reader::ElementReader;
use crate::ion_eq::IonEq;
use crate::ion_data::IonEq;
use crate::ion_data::IonOrd;
use crate::text::text_formatter::IonValueFormatter;
use crate::{Decimal, Int, IonResult, IonType, ReaderBuilder, Str, Symbol, Timestamp};
use num_bigint::BigInt;
use std::cmp::Ordering;
use std::fmt::{Display, Formatter};

mod annotations;
Expand All @@ -38,6 +40,7 @@ pub use self::bytes::Bytes;
pub use annotations::Annotations;
pub use lob::{Blob, Clob};

use crate::types::float;
pub use list::List;
pub use r#struct::Struct;
pub use sequence::Sequence;
Expand All @@ -47,15 +50,65 @@ impl IonEq for Value {
fn ion_eq(&self, other: &Self) -> bool {
use Value::*;
match (self, other) {
(Float(f1), Float(f2)) => return f1.ion_eq(f2),
(Decimal(d1), Decimal(d2)) => return d1.ion_eq(d2),
(Timestamp(t1), Timestamp(t2)) => return t1.ion_eq(t2),
(List(l1), List(l2)) => return l1.ion_eq(l2),
(SExp(s1), SExp(s2)) => return s1.ion_eq(s2),
_ => {}
};
// For any other case, fall back to vanilla equality
self == other
(Null(this), Null(that)) => this == that,
(Bool(this), Bool(that)) => this == that,
(Int(this), Int(that)) => this.ion_eq(that),
(Float(this), Float(that)) => float::Float::ion_eq_f64(this, that),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is curious, probably worth a comment indicating why this is needed.

(Decimal(this), Decimal(that)) => this.ion_eq(that),
(Timestamp(this), Timestamp(that)) => this.ion_eq(that),
(String(this), String(that)) => this.ion_eq(that),
(Symbol(this), Symbol(that)) => this.ion_eq(that),
(Blob(this), Blob(that)) => this.ion_eq(that),
(Clob(this), Clob(that)) => this.ion_eq(that),
(SExp(this), SExp(that)) => this.ion_eq(that),
(List(this), List(that)) => this.ion_eq(that),
(Struct(this), Struct(that)) => this.ion_eq(that),
_ => false,
}
}
}

impl IonOrd for Value {
fn ion_cmp(&self, other: &Self) -> Ordering {
use Value::*;

// First compare Ion types
let ord = self.ion_type().ion_cmp(&other.ion_type());
if !ord.is_eq() {
return ord;
}

macro_rules! compare {
($p:pat => $e:expr) => {
match other {
$p => $e,
Null(_) => Ordering::Greater,
_ => unreachable!("We already checked the Ion Type!"),
}
};
}

match self {
Null(_) => {
if let Null(_) = other {
Ordering::Equal
} else {
Ordering::Less
}
}
Bool(this) => compare!(Bool(that) => this.cmp(that)),
Int(this) => compare!(Int(that) => this.ion_cmp(that)),
Float(this) => compare!(Float(that) => float::Float::ion_cmp_f64(this, that)),
Decimal(this) => compare!(Decimal(that) => this.ion_cmp(that)),
Timestamp(this) => compare!(Timestamp(that) => this.ion_cmp(that)),
String(this) => compare!(String(that) => this.ion_cmp(that)),
Symbol(this) => compare!(Symbol(that) => this.ion_cmp(that)),
Blob(this) => compare!(Blob(that) => this.ion_cmp(that)),
Clob(this) => compare!(Clob(that) => this.ion_cmp(that)),
SExp(this) => compare!(SExp(that) => this.ion_cmp(that)),
List(this) => compare!(List(that) => this.ion_cmp(that)),
Struct(this) => compare!(Struct(that) => this.ion_cmp(that)),
}
}
}

Expand All @@ -77,6 +130,28 @@ pub enum Value {
Struct(Struct),
}

impl Value {
pub fn ion_type(&self) -> IonType {
use Value::*;
popematt marked this conversation as resolved.
Show resolved Hide resolved

match self {
Null(t) => *t,
Int(_) => IonType::Int,
Float(_) => IonType::Float,
Decimal(_) => IonType::Decimal,
Timestamp(_) => IonType::Timestamp,
String(_) => IonType::String,
Symbol(_) => IonType::Symbol,
Bool(_) => IonType::Bool,
Blob(_) => IonType::Blob,
Clob(_) => IonType::Clob,
SExp(_) => IonType::SExp,
List(_) => IonType::List,
Struct(_) => IonType::Struct,
}
}
}

impl Display for Value {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut ivf = IonValueFormatter { output: f };
Expand All @@ -101,6 +176,12 @@ impl Display for Value {
}
}

impl AsRef<Value> for Value {
fn as_ref(&self) -> &Value {
self
}
}

impl From<IonType> for Value {
fn from(ion_type: IonType) -> Self {
Value::Null(ion_type)
Expand Down Expand Up @@ -253,6 +334,31 @@ impl IonEq for Element {
}
}

// Ordering is done as follows:
// 1. Ion type -- It is a logical way to group Ion values, and it is the cheapest comparison
// 2. Annotations -- the vast majority of Ion values have few annotations, so this should usually be cheap
// 3. Value -- compared using IonOrd
impl IonOrd for Element {
fn ion_cmp(&self, other: &Self) -> Ordering {
let ord = self.ion_type().ion_cmp(&other.ion_type());
if !ord.is_eq() {
return ord;
}

let a1 = self.annotations();
let a2 = other.annotations();

let ord = a1.ion_cmp(a2);
if !ord.is_eq() {
return ord;
}

let v1 = self.value();
let v2 = other.value();
v1.ion_cmp(v2)
}
}

/// An `(annotations, value)` pair representing an Ion value.
#[derive(Debug, Clone)]
pub struct Element {
Expand Down Expand Up @@ -337,23 +443,7 @@ impl Element {
}

pub fn ion_type(&self) -> IonType {
use Value::*;

match &self.value {
Null(t) => *t,
Int(_) => IonType::Int,
Float(_) => IonType::Float,
Decimal(_) => IonType::Decimal,
Timestamp(_) => IonType::Timestamp,
String(_) => IonType::String,
Symbol(_) => IonType::Symbol,
Bool(_) => IonType::Bool,
Blob(_) => IonType::Blob,
Clob(_) => IonType::Clob,
SExp(_) => IonType::SExp,
List(_) => IonType::List,
Struct(_) => IonType::Struct,
}
self.value.ion_type()
}

pub fn annotations(&self) -> &Annotations {
Expand Down Expand Up @@ -514,6 +604,12 @@ impl PartialEq for Element {

impl Eq for Element {}

impl AsRef<Element> for Element {
fn as_ref(&self) -> &Element {
self
}
}

// This implementation allows APIs that require an Into<Element> to accept references to an existing
// Element.
impl<'a> From<&'a Element> for Element {
Expand Down Expand Up @@ -1057,7 +1153,7 @@ mod tests {
#[cfg(test)]
mod value_tests {
use crate::element::*;
use crate::ion_eq::IonEq;
use crate::ion_data::IonEq;
use crate::{ion_list, ion_sexp, ion_struct, IonType};
use rstest::*;

Expand Down
2 changes: 1 addition & 1 deletion src/element/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ mod reader_tests {
use crate::element::builders::{ion_list, ion_sexp, ion_struct};
use crate::element::Value::*;
use crate::element::{Element, IntoAnnotatedElement};
use crate::ion_eq::IonEq;
use crate::ion_data::IonEq;
use crate::types::integer::Int;
use crate::types::timestamp::Timestamp as TS;
use crate::{IonType, Symbol};
Expand Down
28 changes: 16 additions & 12 deletions src/element/sequence.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::element::builders::SequenceBuilder;
use crate::element::iterators::ElementsIterator;
use crate::element::Element;
use crate::ion_eq::IonEq;
use crate::ion_data::{IonEq, IonOrd};
use std::cmp::Ordering;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Sequence {
Expand Down Expand Up @@ -53,22 +54,25 @@ impl From<Vec<Element>> for Sequence {
}
}

impl<'a> IntoIterator for &'a Sequence {
type Item = &'a Element;
// TODO: Change once `impl Trait` type aliases are stable
// type IntoIter = impl Iterator<Item = &'a Element>;
type IntoIter = ElementsIterator<'a>;

fn into_iter(self) -> Self::IntoIter {
self.elements()
}
}

impl IonEq for Sequence {
fn ion_eq(&self, other: &Self) -> bool {
self.elements.ion_eq(&other.elements)
}
}

impl IonEq for Vec<Element> {
fn ion_eq(&self, other: &Self) -> bool {
if self.len() != other.len() {
return false;
}
for (v1, v2) in self.iter().zip(other.iter()) {
if !v1.ion_eq(v2) {
return false;
}
}
true
impl IonOrd for Sequence {
fn ion_cmp(&self, other: &Self) -> Ordering {
self.elements.ion_cmp(&other.elements)
}
}
2 changes: 1 addition & 1 deletion src/element/sexp.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::element::builders::SequenceBuilder;
use crate::element::iterators::ElementsIterator;
use crate::element::{Element, Sequence};
use crate::ion_eq::IonEq;
use crate::ion_data::IonEq;
use crate::text::text_formatter::IonValueFormatter;
use delegate::delegate;
use std::fmt::{Display, Formatter};
Expand Down
Loading