pub use crate::protocol::binary::metadata::{BinaryFieldMeta, BinarySchemaMeta, BinaryType};
pub use crate::protocol::binary::reader::BinaryObjectReader;
pub use crate::protocol::binary::value::BinaryObject;
pub use crate::protocol::binary::writer::BinaryObjectBuilder;
pub use crate::protocol::binary::{field_id, schema_id, type_id};
pub use crate::protocol::types::type_code;
pub use ignite_client_derive::IgniteBinary;
use bigdecimal::BigDecimal;
use uuid::Uuid;
use crate::Result;
use crate::protocol::error::{ProtocolError, value_type_name};
use crate::protocol::types::IgniteValue;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct IgniteChar(pub u16);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct IgniteDate(pub i64);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct IgniteTime(pub i64);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct IgniteTimestamp(pub i64, pub i32);
pub trait WriteBinary {
fn type_name() -> &'static str;
fn write(&self, b: BinaryObjectBuilder) -> BinaryObjectBuilder;
fn to_binary(&self) -> BinaryObject {
self.write(BinaryObjectBuilder::new(Self::type_name()))
.build()
}
fn binary_type() -> BinaryType;
}
pub trait FieldWrite {
fn write_field(&self, b: BinaryObjectBuilder, name: &str) -> BinaryObjectBuilder;
fn field_type_code() -> i32;
}
pub trait ReadBinary: Sized {
fn read(r: &BinaryObjectReader) -> Result<Self>;
}
pub trait FieldRead: Sized {
fn read_field(r: &BinaryObjectReader, name: &str) -> Result<Self>;
}
thread_local! {
static NESTED_SCHEMAS: std::cell::RefCell<std::collections::HashMap<(i32, i32), Vec<i32>>> =
std::cell::RefCell::new(std::collections::HashMap::new());
}
pub(crate) fn with_nested_schemas<F, R>(
schemas: std::collections::HashMap<(i32, i32), Vec<i32>>,
f: F,
) -> R
where
F: FnOnce() -> R,
{
NESTED_SCHEMAS.with(|cell| *cell.borrow_mut() = schemas);
let result = f();
NESTED_SCHEMAS.with(|cell| cell.borrow_mut().clear());
result
}
pub fn nested_object_reader(bytes: bytes::Bytes) -> Result<BinaryObjectReader> {
match BinaryObjectReader::new(bytes.clone()) {
Ok(r) => Ok(r),
Err(ProtocolError::CompactFooterNeedsSchema) => {
let mut hb = bytes.clone();
let header = crate::protocol::binary::header::BinaryHeader::read(&mut hb)?;
let key = (header.type_id, header.schema_id);
let schema = NESTED_SCHEMAS
.with(|cell| cell.borrow().get(&key).cloned())
.ok_or(ProtocolError::CompactFooterNeedsSchema)?;
Ok(BinaryObjectReader::with_schema(bytes, &schema)?)
}
Err(e) => Err(e.into()),
}
}
macro_rules! leaf_field {
($rust_ty:ty, $variant:ident, $code:path, $expected:literal) => {
impl FieldWrite for $rust_ty {
fn write_field(&self, b: BinaryObjectBuilder, name: &str) -> BinaryObjectBuilder {
b.set_value(name, &IgniteValue::$variant(*self))
}
fn field_type_code() -> i32 {
$code as i32
}
}
impl FieldRead for $rust_ty {
fn read_field(r: &BinaryObjectReader, name: &str) -> Result<Self> {
match r.field(name)? {
Some(IgniteValue::$variant(v)) => Ok(v),
Some(other) => Err(ProtocolError::TypeMismatch {
expected: $expected,
got: value_type_name(&other),
}
.into()),
None => Err(ProtocolError::UnexpectedNull.into()),
}
}
}
};
}
leaf_field!(bool, Bool, type_code::BOOL, "Bool");
leaf_field!(i8, Byte, type_code::BYTE, "Byte");
leaf_field!(i16, Short, type_code::SHORT, "Short");
leaf_field!(i32, Int, type_code::INT, "Int");
leaf_field!(i64, Long, type_code::LONG, "Long");
leaf_field!(f32, Float, type_code::FLOAT, "Float");
leaf_field!(f64, Double, type_code::DOUBLE, "Double");
macro_rules! newtype_field {
($newtype:ty, $variant:ident, $code:path, $expected:literal) => {
impl FieldWrite for $newtype {
fn write_field(&self, b: BinaryObjectBuilder, name: &str) -> BinaryObjectBuilder {
b.set_value(name, &IgniteValue::$variant(self.0))
}
fn field_type_code() -> i32 {
$code as i32
}
}
impl FieldRead for $newtype {
fn read_field(r: &BinaryObjectReader, name: &str) -> Result<Self> {
match r.field(name)? {
Some(IgniteValue::$variant(v)) => Ok(Self(v)),
Some(other) => Err(ProtocolError::TypeMismatch {
expected: $expected,
got: value_type_name(&other),
}
.into()),
None => Err(ProtocolError::UnexpectedNull.into()),
}
}
}
};
}
newtype_field!(IgniteChar, Char, type_code::CHAR, "Char");
newtype_field!(IgniteDate, Date, type_code::DATE, "Date");
newtype_field!(IgniteTime, Time, type_code::TIME, "Time");
impl FieldWrite for IgniteTimestamp {
fn write_field(&self, b: BinaryObjectBuilder, name: &str) -> BinaryObjectBuilder {
b.set_value(name, &IgniteValue::Timestamp(self.0, self.1))
}
fn field_type_code() -> i32 {
type_code::TIMESTAMP as i32
}
}
impl FieldRead for IgniteTimestamp {
fn read_field(r: &BinaryObjectReader, name: &str) -> Result<Self> {
match r.field(name)? {
Some(IgniteValue::Timestamp(ms, ns)) => Ok(Self(ms, ns)),
Some(other) => Err(ProtocolError::TypeMismatch {
expected: "Timestamp",
got: value_type_name(&other),
}
.into()),
None => Err(ProtocolError::UnexpectedNull.into()),
}
}
}
impl FieldWrite for String {
fn write_field(&self, b: BinaryObjectBuilder, name: &str) -> BinaryObjectBuilder {
b.set_value(name, &IgniteValue::String(self.clone()))
}
fn field_type_code() -> i32 {
type_code::STRING as i32
}
}
impl FieldRead for String {
fn read_field(r: &BinaryObjectReader, name: &str) -> Result<Self> {
match r.field(name)? {
Some(IgniteValue::String(v)) => Ok(v),
Some(other) => Err(ProtocolError::TypeMismatch {
expected: "String",
got: value_type_name(&other),
}
.into()),
None => Err(ProtocolError::UnexpectedNull.into()),
}
}
}
impl FieldWrite for Uuid {
fn write_field(&self, b: BinaryObjectBuilder, name: &str) -> BinaryObjectBuilder {
b.set_value(name, &IgniteValue::Uuid(*self))
}
fn field_type_code() -> i32 {
type_code::UUID as i32
}
}
impl FieldRead for Uuid {
fn read_field(r: &BinaryObjectReader, name: &str) -> Result<Self> {
match r.field(name)? {
Some(IgniteValue::Uuid(v)) => Ok(v),
Some(other) => Err(ProtocolError::TypeMismatch {
expected: "Uuid",
got: value_type_name(&other),
}
.into()),
None => Err(ProtocolError::UnexpectedNull.into()),
}
}
}
impl FieldWrite for BigDecimal {
fn write_field(&self, b: BinaryObjectBuilder, name: &str) -> BinaryObjectBuilder {
b.set_value(name, &IgniteValue::Decimal(self.clone()))
}
fn field_type_code() -> i32 {
type_code::DECIMAL as i32
}
}
impl FieldRead for BigDecimal {
fn read_field(r: &BinaryObjectReader, name: &str) -> Result<Self> {
match r.field(name)? {
Some(IgniteValue::Decimal(v)) => Ok(v),
Some(other) => Err(ProtocolError::TypeMismatch {
expected: "Decimal",
got: value_type_name(&other),
}
.into()),
None => Err(ProtocolError::UnexpectedNull.into()),
}
}
}
impl<T: FieldWrite> FieldWrite for Option<T> {
fn write_field(&self, b: BinaryObjectBuilder, name: &str) -> BinaryObjectBuilder {
match self {
Some(v) => v.write_field(b, name),
None => b.set_value(name, &IgniteValue::Null),
}
}
fn field_type_code() -> i32 {
T::field_type_code()
}
}
impl<T: FieldRead> FieldRead for Option<T> {
fn read_field(r: &BinaryObjectReader, name: &str) -> Result<Self> {
match r.field(name)? {
None | Some(IgniteValue::Null) => Ok(None),
Some(_) => Ok(Some(T::read_field(r, name)?)),
}
}
}
impl FieldWrite for Vec<i32> {
fn write_field(&self, b: BinaryObjectBuilder, name: &str) -> BinaryObjectBuilder {
b.set_value(name, &IgniteValue::IntArray(self.clone()))
}
fn field_type_code() -> i32 {
type_code::INT_ARRAY as i32
}
}
impl FieldRead for Vec<i32> {
fn read_field(r: &BinaryObjectReader, name: &str) -> Result<Self> {
match r.field(name)? {
Some(IgniteValue::IntArray(v)) => Ok(v),
Some(other) => Err(ProtocolError::TypeMismatch {
expected: "IntArray",
got: value_type_name(&other),
}
.into()),
None => Err(ProtocolError::UnexpectedNull.into()),
}
}
}
impl FieldWrite for Vec<String> {
fn write_field(&self, b: BinaryObjectBuilder, name: &str) -> BinaryObjectBuilder {
let arr: Vec<Option<String>> = self.iter().cloned().map(Some).collect();
b.set_value(name, &IgniteValue::StringArray(arr))
}
fn field_type_code() -> i32 {
type_code::STRING_ARRAY as i32
}
}
impl FieldRead for Vec<String> {
fn read_field(r: &BinaryObjectReader, name: &str) -> Result<Self> {
match r.field(name)? {
Some(IgniteValue::StringArray(v)) => v
.into_iter()
.map(|e| {
e.ok_or_else(|| {
ProtocolError::TypeMismatch {
expected: "String",
got: "Null",
}
.into()
})
})
.collect(),
Some(other) => Err(ProtocolError::TypeMismatch {
expected: "StringArray",
got: value_type_name(&other),
}
.into()),
None => Err(ProtocolError::UnexpectedNull.into()),
}
}
}
impl FieldWrite for std::collections::HashMap<String, i32> {
fn write_field(&self, b: BinaryObjectBuilder, name: &str) -> BinaryObjectBuilder {
let pairs: Vec<(IgniteValue, IgniteValue)> = self
.iter()
.map(|(k, v)| (IgniteValue::String(k.clone()), IgniteValue::Int(*v)))
.collect();
b.set_value(name, &IgniteValue::Map(1, pairs))
}
fn field_type_code() -> i32 {
type_code::MAP as i32
}
}
impl FieldRead for std::collections::HashMap<String, i32> {
fn read_field(r: &BinaryObjectReader, name: &str) -> Result<Self> {
match r.field(name)? {
Some(IgniteValue::Map(_, pairs)) => pairs
.into_iter()
.map(|(k, v)| {
let key = match k {
IgniteValue::String(s) => s,
other => {
return Err(ProtocolError::TypeMismatch {
expected: "String",
got: value_type_name(&other),
}
.into());
}
};
let val = match v {
IgniteValue::Int(i) => i,
other => {
return Err(ProtocolError::TypeMismatch {
expected: "Int",
got: value_type_name(&other),
}
.into());
}
};
Ok((key, val))
})
.collect(),
Some(other) => Err(ProtocolError::TypeMismatch {
expected: "Map",
got: value_type_name(&other),
}
.into()),
None => Err(ProtocolError::UnexpectedNull.into()),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct IgniteList<T>(pub Vec<T>);
impl FieldWrite for IgniteList<i32> {
fn write_field(&self, b: BinaryObjectBuilder, name: &str) -> BinaryObjectBuilder {
let vals: Vec<IgniteValue> = self.0.iter().map(|x| IgniteValue::Int(*x)).collect();
b.set_value(name, &IgniteValue::Collection(2, vals))
}
fn field_type_code() -> i32 {
type_code::COLLECTION as i32
}
}
impl FieldRead for IgniteList<i32> {
fn read_field(r: &BinaryObjectReader, name: &str) -> Result<Self> {
match r.field(name)? {
Some(IgniteValue::Collection(_, vals)) => {
let items: Vec<i32> = vals
.into_iter()
.map(|v| match v {
IgniteValue::Int(i) => Ok(i),
other => Err(ProtocolError::TypeMismatch {
expected: "Int",
got: value_type_name(&other),
}
.into()),
})
.collect::<Result<Vec<i32>>>()?;
Ok(IgniteList(items))
}
Some(other) => Err(ProtocolError::TypeMismatch {
expected: "Collection",
got: value_type_name(&other),
}
.into()),
None => Err(ProtocolError::UnexpectedNull.into()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::protocol::binary::{reader::BinaryObjectReader, writer::BinaryObjectBuilder};
#[test]
fn field_write_read_i32_and_string() {
let b = BinaryObjectBuilder::new("t.T");
let b = 42i32.write_field(b, "n");
let b = "hi".to_string().write_field(b, "s");
let obj = b.build();
let r = BinaryObjectReader::new(obj.bytes).unwrap();
assert_eq!(i32::read_field(&r, "n").unwrap(), 42);
assert_eq!(String::read_field(&r, "s").unwrap(), "hi");
}
#[test]
fn field_write_read_option_none() {
let b = Option::<i32>::None.write_field(BinaryObjectBuilder::new("t.T"), "n");
let r = BinaryObjectReader::new(b.build().bytes).unwrap();
assert_eq!(Option::<i32>::read_field(&r, "n").unwrap(), None);
}
#[test]
fn field_write_read_option_some() {
let b = Some(7i32).write_field(BinaryObjectBuilder::new("t.T"), "n");
let r = BinaryObjectReader::new(b.build().bytes).unwrap();
assert_eq!(Option::<i32>::read_field(&r, "n").unwrap(), Some(7));
}
#[test]
fn field_read_option_missing_field_is_none() {
let obj = BinaryObjectBuilder::new("t.T").set_i32("other", 1).build();
let r = BinaryObjectReader::new(obj.bytes).unwrap();
assert_eq!(Option::<i32>::read_field(&r, "n").unwrap(), None);
}
#[test]
fn field_write_read_all_numeric_leaves() {
let b = BinaryObjectBuilder::new("t.T");
let b = true.write_field(b, "bo");
let b = 1i8.write_field(b, "i8");
let b = 2i16.write_field(b, "i16");
let b = 3i32.write_field(b, "i32");
let b = 4i64.write_field(b, "i64");
let b = 5.5f32.write_field(b, "f32");
let b = 6.5f64.write_field(b, "f64");
let obj = b.build();
let r = BinaryObjectReader::new(obj.bytes).unwrap();
assert_eq!(bool::read_field(&r, "bo").unwrap(), true);
assert_eq!(i8::read_field(&r, "i8").unwrap(), 1);
assert_eq!(i16::read_field(&r, "i16").unwrap(), 2);
assert_eq!(i32::read_field(&r, "i32").unwrap(), 3);
assert_eq!(i64::read_field(&r, "i64").unwrap(), 4);
assert_eq!(f32::read_field(&r, "f32").unwrap(), 5.5);
assert_eq!(f64::read_field(&r, "f64").unwrap(), 6.5);
}
#[test]
fn field_write_read_newtypes() {
let b = BinaryObjectBuilder::new("t.T");
let b = IgniteChar(65).write_field(b, "c");
let b = IgniteDate(1_705_276_800_000).write_field(b, "d");
let b = IgniteTime(1_234_567_890).write_field(b, "t");
let b = IgniteTimestamp(1_700_000_000_000, 123).write_field(b, "ts");
let obj = b.build();
let r = BinaryObjectReader::new(obj.bytes).unwrap();
assert_eq!(IgniteChar::read_field(&r, "c").unwrap(), IgniteChar(65));
assert_eq!(
IgniteDate::read_field(&r, "d").unwrap(),
IgniteDate(1_705_276_800_000)
);
assert_eq!(
IgniteTime::read_field(&r, "t").unwrap(),
IgniteTime(1_234_567_890)
);
assert_eq!(
IgniteTimestamp::read_field(&r, "ts").unwrap(),
IgniteTimestamp(1_700_000_000_000, 123)
);
}
#[test]
fn field_write_read_uuid_and_decimal() {
use std::str::FromStr;
let u = Uuid::new_v4();
let d = BigDecimal::from_str("12.34").unwrap();
let b = BinaryObjectBuilder::new("t.T");
let b = u.write_field(b, "u");
let b = d.write_field(b, "d");
let obj = b.build();
let r = BinaryObjectReader::new(obj.bytes).unwrap();
assert_eq!(Uuid::read_field(&r, "u").unwrap(), u);
assert_eq!(
BigDecimal::read_field(&r, "d").unwrap().normalized(),
d.normalized()
);
}
#[test]
fn field_read_missing_required_field_errors() {
let obj = BinaryObjectBuilder::new("t.T").build();
let r = BinaryObjectReader::new(obj.bytes).unwrap();
let err = i32::read_field(&r, "missing").unwrap_err();
assert!(matches!(
err,
crate::IgniteError::Protocol(ProtocolError::UnexpectedNull)
));
}
#[test]
fn field_read_wrong_variant_is_type_mismatch() {
let obj = BinaryObjectBuilder::new("t.T").set_i32("n", 1).build();
let r = BinaryObjectReader::new(obj.bytes).unwrap();
let err = String::read_field(&r, "n").unwrap_err();
assert!(matches!(
err,
crate::IgniteError::Protocol(ProtocolError::TypeMismatch {
expected: "String",
got: "Int"
})
));
}
#[test]
fn field_write_read_vec_i32() {
let b = vec![1i32, -2, 3].write_field(BinaryObjectBuilder::new("t.T"), "a");
let r = BinaryObjectReader::new(b.build().bytes).unwrap();
assert_eq!(<Vec<i32>>::read_field(&r, "a").unwrap(), vec![1, -2, 3]);
assert_eq!(<Vec<i32>>::field_type_code(), type_code::INT_ARRAY as i32);
}
#[test]
fn field_write_read_vec_i32_empty() {
let b = Vec::<i32>::new().write_field(BinaryObjectBuilder::new("t.T"), "a");
let r = BinaryObjectReader::new(b.build().bytes).unwrap();
assert_eq!(<Vec<i32>>::read_field(&r, "a").unwrap(), Vec::<i32>::new());
}
#[test]
fn field_write_read_vec_string() {
let v = vec!["foo".to_string(), "bar".to_string()];
let b = v.write_field(BinaryObjectBuilder::new("t.T"), "s");
let r = BinaryObjectReader::new(b.build().bytes).unwrap();
assert_eq!(<Vec<String>>::read_field(&r, "s").unwrap(), v);
assert_eq!(
<Vec<String>>::field_type_code(),
type_code::STRING_ARRAY as i32
);
}
#[test]
fn field_read_vec_string_null_element_errors() {
let obj = BinaryObjectBuilder::new("t.T")
.set_string_array("s", &[Some("a".to_string()), None])
.build();
let r = BinaryObjectReader::new(obj.bytes).unwrap();
let err = <Vec<String>>::read_field(&r, "s").unwrap_err();
assert!(matches!(
err,
crate::IgniteError::Protocol(ProtocolError::TypeMismatch {
expected: "String",
got: "Null"
})
));
}
#[test]
fn field_write_read_hashmap_string_i32() {
use std::collections::HashMap;
let mut m = HashMap::new();
m.insert("one".to_string(), 1i32);
m.insert("two".to_string(), 2i32);
let b = m.write_field(BinaryObjectBuilder::new("t.T"), "m");
let r = BinaryObjectReader::new(b.build().bytes).unwrap();
assert_eq!(<HashMap<String, i32>>::read_field(&r, "m").unwrap(), m);
assert_eq!(
<HashMap<String, i32>>::field_type_code(),
type_code::MAP as i32
);
}
#[test]
fn field_write_read_ignite_list_i32() {
let list = IgniteList(vec![10i32, 20, 30]);
let b = list.write_field(BinaryObjectBuilder::new("t.T"), "l");
let r = BinaryObjectReader::new(b.build().bytes).unwrap();
assert_eq!(IgniteList::<i32>::read_field(&r, "l").unwrap(), list);
assert_eq!(
IgniteList::<i32>::field_type_code(),
type_code::COLLECTION as i32
);
}
#[test]
fn field_read_vec_i32_missing_field_errors() {
let obj = BinaryObjectBuilder::new("t.T").build();
let r = BinaryObjectReader::new(obj.bytes).unwrap();
let err = <Vec<i32>>::read_field(&r, "missing").unwrap_err();
assert!(matches!(
err,
crate::IgniteError::Protocol(ProtocolError::UnexpectedNull)
));
}
#[test]
fn field_read_hashmap_wrong_variant_is_type_mismatch() {
let obj = BinaryObjectBuilder::new("t.T").set_i32("m", 1).build();
let r = BinaryObjectReader::new(obj.bytes).unwrap();
use std::collections::HashMap;
let err = <HashMap<String, i32>>::read_field(&r, "m").unwrap_err();
assert!(matches!(
err,
crate::IgniteError::Protocol(ProtocolError::TypeMismatch {
expected: "Map",
got: "Int"
})
));
}
}