Skip to content

Commit

Permalink
Adds IonData wrapper type that uses Ion equivalence for equality (#517)
Browse files Browse the repository at this point in the history
  • Loading branch information
popematt committed Apr 25, 2023
1 parent 07779eb commit f69a0eb
Show file tree
Hide file tree
Showing 24 changed files with 896 additions and 109 deletions.
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
140 changes: 112 additions & 28 deletions src/element/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@

use crate::element::builders::{SequenceBuilder, StructBuilder};
use crate::element::reader::ElementReader;
use crate::ion_eq::IonEq;
use crate::ion_data;
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 Down Expand Up @@ -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)) => ion_data::ion_eq_bool(this, that),
(Int(this), Int(that)) => this.ion_eq(that),
(Float(this), Float(that)) => ion_data::ion_eq_f64(this, that),
(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) => ion_data::ion_cmp_bool(this, that)),
Int(this) => compare!(Int(that) => this.ion_cmp(that)),
Float(this) => compare!(Float(that) => ion_data::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::*;

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 Down Expand Up @@ -253,6 +328,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 +437,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 @@ -1057,7 +1141,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
42 changes: 41 additions & 1 deletion src/element/struct.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use crate::element::builders::StructBuilder;
use crate::element::iterators::{FieldIterator, FieldValuesIterator, IndexVec};
use crate::element::Element;
use crate::ion_eq::IonEq;
use crate::ion_data::{IonEq, IonOrd};
use crate::symbol_ref::AsSymbolRef;
use crate::text::text_formatter::IonValueFormatter;
use crate::Symbol;
use std::cmp::Ordering;
use std::collections::HashMap;
use std::fmt::{Display, Formatter};

Expand Down Expand Up @@ -225,6 +226,45 @@ impl PartialEq for Struct {

impl Eq for Struct {}

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

impl IonOrd for Struct {
fn ion_cmp(&self, other: &Self) -> Ordering {
let mut these_fields = self.fields.by_index.iter().collect::<Vec<_>>();
let mut those_fields = other.fields.by_index.iter().collect::<Vec<_>>();
these_fields.sort_by(ion_cmp_field);
those_fields.sort_by(ion_cmp_field);

let mut i0 = these_fields.iter();
let mut i1 = those_fields.iter();
loop {
match [i0.next(), i1.next()] {
[None, Some(_)] => return Ordering::Less,
[None, None] => return Ordering::Equal,
[Some(_), None] => return Ordering::Greater,
[Some(a), Some(b)] => {
let ord = ion_cmp_field(a, b);
if ord != Ordering::Equal {
return ord;
}
}
}
}
}
}

fn ion_cmp_field(this: &&(Symbol, Element), that: &&(Symbol, Element)) -> Ordering {
let ord = this.0.ion_cmp(&that.0);
if !ord.is_eq() {
return ord;
}
IonOrd::ion_cmp(&this.1, &that.1)
}

#[cfg(test)]
mod tests {
use crate::element::Element;
Expand Down
2 changes: 1 addition & 1 deletion src/element/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ pub enum Format {
mod tests {
use crate::element::writer::ElementWriter;
use crate::element::Element;
use crate::ion_eq::IonEq;
use crate::ion_data::IonEq;
use crate::text::text_writer::TextWriterBuilder;

use crate::{IonResult, IonWriter};
Expand Down
Loading

0 comments on commit f69a0eb

Please sign in to comment.