mod common;
pub mod decode;
pub mod encode;
pub use common::Encoding;
#[cfg(test)]
mod tests {
use super::*;
use crate::decode::{IonDecodeResult, IonDecoderBuilder, IonDecoderConfig};
use crate::encode::{IonEncodeError, IonEncoderBuilder, IonEncoderConfig};
use itertools::Itertools;
use ion_rs::element::writer::TextKind;
use ion_rs::element::{Element, IntoAnnotatedElement};
use ion_rs::types::{Bytes, Sequence, Struct};
use ion_rs::{Decimal, Int, IonType, Str, Timestamp};
use partiql_value::{bag, list, tuple, DateTime, Value};
use rust_decimal_macros::dec;
use std::num::NonZeroU8;
fn decode_ion_text(contents: &str, encoding: Encoding) -> IonDecodeResult {
let reader = ion_rs::ReaderBuilder::new().build(contents)?;
let mut iter = IonDecoderBuilder::new(IonDecoderConfig::default().with_mode(encoding))
.build(reader)?;
let val = iter.next();
val.unwrap()
}
fn encode_ion_text(value: &Value, encoding: Encoding) -> Result<String, IonEncodeError> {
let mut buff = vec![];
let mut writer = ion_rs::TextWriterBuilder::new(TextKind::Compact)
.build(&mut buff)
.expect("writer");
let mut encoder = IonEncoderBuilder::new(IonEncoderConfig::default().with_mode(encoding))
.build(&mut writer)?;
encoder.write_value(value)?;
drop(encoder);
drop(writer);
Ok(String::from_utf8(buff).expect("string"))
}
fn decode_ion_element(
contents: ion_rs::element::Element,
encoding: Encoding,
) -> IonDecodeResult {
let reader = ion_rs::element::element_stream_reader::ElementStreamReader::new(contents);
let mut iter = IonDecoderBuilder::new(IonDecoderConfig::default().with_mode(encoding))
.build(reader)?;
let val = iter.next();
val.unwrap()
}
fn encode_ion_element(
value: &Value,
encoding: Encoding,
) -> Result<Vec<ion_rs::element::Element>, IonEncodeError> {
let mut out = vec![];
let mut writer = ion_rs::element::element_stream_writer::ElementStreamWriter::new(&mut out);
let mut encoder = IonEncoderBuilder::new(IonEncoderConfig::default().with_mode(encoding))
.build(&mut writer)?;
encoder.write_value(value)?;
drop(encoder);
drop(writer);
Ok(out)
}
#[track_caller]
fn assert_decode_encode(
ion: &str,
element: impl Into<ion_rs::element::Element>,
val: impl Into<Value>,
encoding: Encoding,
) {
let expected_value = val.into();
let decoded_ion_text = decode_ion_text(ion, encoding).expect("decode text expected");
assert_eq!(decoded_ion_text, expected_value);
let expected_element = element.into();
let decoded_ion_element =
decode_ion_element(expected_element, encoding).expect("decode element encoded");
assert_eq!(decoded_ion_element, expected_value);
let encoded_text_value =
encode_ion_text(&expected_value, encoding).expect("encode to text");
let decoded_encoded_text_value =
decode_ion_text(&encoded_text_value, encoding).expect("decode of encode to text");
assert_eq!(decoded_encoded_text_value, expected_value);
let mut encoded_element_value =
encode_ion_element(&expected_value, encoding).expect("encode to element");
assert_eq!(encoded_element_value.len(), 1);
let decoded_encoded_element_value =
decode_ion_element(encoded_element_value.pop().unwrap(), encoding)
.expect("decode of encode to element");
assert_eq!(decoded_encoded_element_value, expected_value);
}
#[track_caller]
fn assert_ion(ion: &str, element: impl Into<ion_rs::element::Element>, val: impl Into<Value>) {
assert_decode_encode(ion, element, val, Encoding::Ion);
}
#[track_caller]
fn assert_partiql_encoded_ion(
ion: &str,
element: impl Into<ion_rs::element::Element>,
val: impl Into<Value>,
) {
assert_decode_encode(ion, element, val, Encoding::PartiqlEncodedAsIon);
}
#[test]
fn partiql_value_from_ion() {
assert_ion(
"null",
ion_rs::element::Value::Null(IonType::Null),
Value::Null,
);
assert_ion("true", ion_rs::element::Value::Bool(true), true);
assert_ion("false", ion_rs::element::Value::Bool(false), false);
assert_ion("42", ion_rs::element::Value::Int(Int::I64(42)), 42);
assert_ion("-5", ion_rs::element::Value::Int(Int::I64(-5)), -5);
assert_ion("1.1e0", ion_rs::element::Value::Float(1.1), 1.1);
assert_ion(
"1.",
ion_rs::element::Value::Decimal(Decimal::new(1, 0)),
dec!(1),
);
assert_ion(
"'foo'",
ion_rs::element::Value::String(Str::from("foo")),
"foo",
);
assert_ion(
"\"foo\"",
ion_rs::element::Value::String(Str::from("foo")),
"foo",
);
assert_ion(
"2017-01-01T01:02:03.4+00:30",
ion_rs::element::Value::Timestamp(
Timestamp::with_ymd_hms_millis(2017, 1, 1, 1, 2, 3, 400)
.build_at_offset(30)
.expect("ion timestamp"),
),
DateTime::from_ymdhms_nano_offset_minutes(
2017,
NonZeroU8::new(1).unwrap(),
1,
1,
2,
3,
400000000,
Some(30),
),
);
assert_ion(
"2017-01-01T01:02:03.4-00:00",
ion_rs::element::Value::Timestamp(
Timestamp::with_ymd_hms_millis(2017, 1, 1, 1, 2, 3, 400)
.build_at_unknown_offset()
.expect("ion timestamp"),
),
DateTime::from_ymdhms_nano_offset_minutes(
2017,
NonZeroU8::new(1).unwrap(),
1,
1,
2,
3,
400000000,
None,
),
);
assert_ion(
"{{ +AB/ }}",
ion_rs::element::Value::Blob(Bytes::from(vec![248, 0, 127])),
Value::Blob(Box::new(vec![248, 0, 127])),
);
assert_ion(
"{{ \"CLOB of text.\" }}",
ion_rs::element::Value::Clob(Bytes::from("CLOB of text.")),
Value::Blob(Box::new("CLOB of text.".bytes().collect_vec())),
);
assert_ion(
"[1,2,\"3\"]",
ion_rs::element::Value::List(Sequence::new([
ion_rs::element::Value::Int(Int::I64(1)),
ion_rs::element::Value::Int(Int::I64(2)),
ion_rs::element::Value::String(Str::from("3")),
])),
list![1, 2, "3"],
);
assert_ion(
"{\"k\": [1,2,3]}",
ion_rs::element::Value::Struct(
Struct::builder()
.with_field("k", ion_rs::element::List(Sequence::new([1, 2, 3])))
.build(),
),
tuple![("k", list![1, 2, 3])],
);
}
#[test]
fn partiql_value_from_partiql_encoded_ion() {
assert_partiql_encoded_ion(
"null",
ion_rs::element::Value::Null(IonType::Null),
Value::Null,
);
assert_partiql_encoded_ion(
"$missing::null",
ion_rs::element::Value::Null(IonType::Null).with_annotations(["$missing"]),
Value::Missing,
);
assert_partiql_encoded_ion("true", ion_rs::element::Value::Bool(true), true);
assert_partiql_encoded_ion("false", ion_rs::element::Value::Bool(false), false);
assert_partiql_encoded_ion("42", ion_rs::element::Value::Int(Int::I64(42)), 42);
assert_partiql_encoded_ion("-5", ion_rs::element::Value::Int(Int::I64(-5)), -5);
assert_partiql_encoded_ion("1.1e0", ion_rs::element::Value::Float(1.1), 1.1);
assert_partiql_encoded_ion(
"1.",
ion_rs::element::Value::Decimal(Decimal::new(1, 0)),
dec!(1),
);
assert_partiql_encoded_ion(
"'foo'",
ion_rs::element::Value::String(Str::from("foo")),
"foo",
);
assert_partiql_encoded_ion(
"\"foo\"",
ion_rs::element::Value::String(Str::from("foo")),
"foo",
);
assert_partiql_encoded_ion(
"2017-01-01T01:02:03.4+00:30",
ion_rs::element::Value::Timestamp(
Timestamp::with_ymd_hms_millis(2017, 1, 1, 1, 2, 3, 400)
.build_at_offset(30)
.expect("ion timestamp"),
),
DateTime::from_ymdhms_nano_offset_minutes(
2017,
NonZeroU8::new(1).unwrap(),
1,
1,
2,
3,
400000000,
Some(30),
),
);
assert_partiql_encoded_ion(
"2017-01-01T01:02:03.4-00:00",
ion_rs::element::Value::Timestamp(
Timestamp::with_ymd_hms_millis(2017, 1, 1, 1, 2, 3, 400)
.build_at_unknown_offset()
.expect("ion timestamp"),
),
DateTime::from_ymdhms_nano_offset_minutes(
2017,
NonZeroU8::new(1).unwrap(),
1,
1,
2,
3,
400000000,
None,
),
);
assert_partiql_encoded_ion(
"$time::{ hour: 12, minute: 11, second: 10.08}",
ion_rs::element::Value::Struct(
Struct::builder()
.with_fields([
("hour", ion_rs::element::Value::Int(Int::I64(12))),
("minute", ion_rs::element::Value::Int(Int::I64(11))),
("second", ion_rs::element::Value::Float(10.08)),
])
.build(),
)
.with_annotations(["$time"]),
DateTime::from_hms_nano(12, 11, 10, 80000000),
);
assert_partiql_encoded_ion(
"$time::{ hour: 12, minute: 11, second: 10.08, timezone_hour: 0, timezone_minute: 30}",
ion_rs::element::Value::Struct(
Struct::builder()
.with_fields([
("hour", ion_rs::element::Value::Int(Int::I64(12))),
("minute", ion_rs::element::Value::Int(Int::I64(11))),
("second", ion_rs::element::Value::Float(10.08)),
("timezone_hour", ion_rs::element::Value::Int(Int::I64(0))),
("timezone_minute", ion_rs::element::Value::Int(Int::I64(30))),
])
.build(),
)
.with_annotations(["$time"]),
DateTime::from_hms_nano_tz(12, 11, 10, 80000000, None, Some(30)),
);
assert_partiql_encoded_ion(
"$date::1957-05-25T",
ion_rs::element::Value::Timestamp(
Timestamp::with_ymd(1957, 5, 25)
.build()
.expect("ion timestamp"),
)
.with_annotations(["$date"]),
DateTime::from_ymd(1957, NonZeroU8::new(5).unwrap(), 25),
);
assert_partiql_encoded_ion(
"{{ +AB/ }}",
ion_rs::element::Value::Blob(Bytes::from(vec![248, 0, 127])),
Value::Blob(Box::new(vec![248, 0, 127])),
);
assert_partiql_encoded_ion(
"{{ \"CLOB of text.\" }}",
ion_rs::element::Value::Clob(Bytes::from("CLOB of text.")),
Value::Blob(Box::new("CLOB of text.".bytes().collect_vec())),
);
assert_partiql_encoded_ion(
"[1,2,\"3\"]",
ion_rs::element::Value::List(Sequence::new([
ion_rs::element::Value::Int(Int::I64(1)),
ion_rs::element::Value::Int(Int::I64(2)),
ion_rs::element::Value::String(Str::from("3")),
])),
list![1, 2, "3"],
);
assert_partiql_encoded_ion(
"$bag::[1,2,\"3\", null, $missing::null]",
ion_rs::element::Value::List(Sequence::new::<Element, _>([
ion_rs::element::Value::Int(Int::I64(1)).into(),
ion_rs::element::Value::Int(Int::I64(2)).into(),
ion_rs::element::Value::String(Str::from("3")).into(),
ion_rs::element::Value::Null(IonType::Null).into(),
ion_rs::element::Value::Null(IonType::Null).with_annotations(["$missing"]),
]))
.with_annotations(["$bag"]),
bag![1, 2, "3", Value::Null, Value::Missing],
);
assert_partiql_encoded_ion(
"{\"k\": []}",
ion_rs::element::Value::Struct(
Struct::builder()
.with_field("k", ion_rs::element::List(Sequence::new::<Element, _>([])))
.build(),
),
tuple![("k", list![])],
);
assert_partiql_encoded_ion(
"{\"k\": [1,2,3]}",
ion_rs::element::Value::Struct(
Struct::builder()
.with_field("k", ion_rs::element::List(Sequence::new([1, 2, 3])))
.build(),
),
tuple![("k", list![1, 2, 3])],
);
}
}