diff --git a/service/Cargo.lock b/service/Cargo.lock index 9b85a18..33fc490 100644 --- a/service/Cargo.lock +++ b/service/Cargo.lock @@ -10,6 +10,7 @@ dependencies = [ "axum", "bincode", "dotenv", + "facet", "field_types", "reqwest", "serde", @@ -139,6 +140,12 @@ version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.10.1" @@ -248,6 +255,69 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "facet" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49184c644e74dd0e899d21144285e92598d682845c8ef0d13da05f1c67c2bcc5" +dependencies = [ + "facet-core", + "facet-macros", + "facet-reflect", + "static_assertions", +] + +[[package]] +name = "facet-core" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e09a4180d0a78a9e444cb57ea57b2c1d65ab140e53e35535b76713454a8e158e" +dependencies = [ + "bitflags 2.10.0", + "impls", +] + +[[package]] +name = "facet-macros" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bfcaf327d2f5d94bc208e81d625acfdab871f47f9915a34929335762d4d312" +dependencies = [ + "facet-core", + "facet-macros-emit", +] + +[[package]] +name = "facet-macros-emit" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "084b862aa27ccade8c0d249b57960975611d1f9dfcd3c8c42a25aaa7d14cd1c2" +dependencies = [ + "facet-macros-parse", + "quote 1.0.41", +] + +[[package]] +name = "facet-macros-parse" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c198c8252d559dff4ef2846662b8ac63496ec4cbe734651a5ffedeefaa8652" +dependencies = [ + "proc-macro2 1.0.103", + "quote 1.0.41", + "unsynn", +] + +[[package]] +name = "facet-reflect" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91debebf849ca4acf73f14c74dc5642f8b8e1aa823c86cabfa5d0b4825c66323" +dependencies = [ + "bitflags 2.10.0", + "facet-core", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -340,6 +410,15 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generic-array" version = "0.14.9" @@ -599,6 +678,12 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "impls" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a46645bbd70538861a90d0f26c31537cdf1e44aae99a794fb75a664b70951bc" + [[package]] name = "indexmap" version = "2.12.0" @@ -684,6 +769,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "mutants" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0287524726960e07b119cebd01678f852f147742ae0d925e6a520dca956126" + [[package]] name = "native-tls" version = "0.2.14" @@ -1029,6 +1120,12 @@ dependencies = [ "digest", ] +[[package]] +name = "shadow_counted" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65da48d447333cebe1aadbdd3662f3ba56e76e67f53bc46f3dd5f67c74629d6b" + [[package]] name = "shlex" version = "1.3.0" @@ -1082,6 +1179,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "syn" version = "0.15.44" @@ -1331,6 +1434,18 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +[[package]] +name = "unsynn" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7940603a9e25cf11211cc43b81f4fcad2b8ab4df291ca855f32c40e1ac22d5bc" +dependencies = [ + "fxhash", + "mutants", + "proc-macro2 1.0.103", + "shadow_counted", +] + [[package]] name = "unty" version = "0.0.4" diff --git a/service/Cargo.toml b/service/Cargo.toml index d8de35c..4cbf244 100644 --- a/service/Cargo.toml +++ b/service/Cargo.toml @@ -15,6 +15,7 @@ anyhow = "1.0.100" axum = { version = "0.6.9", features = ["headers"] } bincode = "2.0.1" dotenv = "0.15.0" +facet = { version = "0.31.3", features = ["reflect"] } field_types = "1.1.0" reqwest = "0.11.14" serde = "1.0.152" diff --git a/service/src/lib/outbound/db_custom/mod.rs b/service/src/lib/outbound/db_custom/mod.rs index e417b12..304351b 100644 --- a/service/src/lib/outbound/db_custom/mod.rs +++ b/service/src/lib/outbound/db_custom/mod.rs @@ -7,6 +7,8 @@ use std::{ sync::{Arc, Mutex}, }; +use facet::Facet; + use crate::outbound::db_custom::write_set::{Diffable, Storable, Write, WriteOperation, WriteSet}; #[derive( @@ -20,7 +22,7 @@ impl From for TransactionId { } } -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, facet::Facet)] pub struct Id { id: u64, _type: PhantomData, @@ -392,7 +394,7 @@ impl<'a> PendingTransaction<'a> { self.accessor.get(id, self.timestamp) } - pub fn get_field( + pub fn get_field Facet<'facet> + Clone + 'static>( &mut self, id: Id, field: &'static str, @@ -464,6 +466,16 @@ pub enum Value { } impl Value { + fn size(&self) -> Option { + match self { + Value::String(_) | Value::Array(_) => None, + + Value::Int(_) => Some(size_of::()), + Value::Float(_) => Some(size_of::()), + Value::Bool(_) => Some(size_of::()), + } + } + fn as_string(&self) -> Option<&String> { match self { Value::String(s) => Some(s), @@ -501,41 +513,29 @@ impl Value { mod tests { use std::collections::HashSet; - use crate::{db_type, outbound::db_custom::write_set::FieldDiff}; + use bincode::{Decode, Encode}; + use facet::Facet; use super::*; - db_type! { - struct MyTestData { - name: Name, - age: i64, - contacts: (HashSet>), - } - - struct Name { - first: String, - last: String, - nest: SuperNested, - } - - struct SuperNested { - i_am_a_really_nested_field: i64, - } + #[derive(Debug, Clone, Encode, Decode, Facet)] + struct MyTestData { + name: Name, + age: i64, + ff: String, + //contacts: HashSet>, } - #[derive(bincode::Encode)] - struct A { - name: String, - data: T, + #[derive(Debug, Clone, Encode, Decode, Facet)] + struct Name { + nest: SuperNested, + first: String, + last: String, } - impl A { - fn with(name: impl ToString, data: T) -> Self { - Self { - name: name.to_string(), - data, - } - } + #[derive(Debug, Clone, Encode, Decode, Facet)] + struct SuperNested { + i_am_a_really_nested_field: i64, } #[test] @@ -552,6 +552,7 @@ mod tests { let mut accessor = DatabaseAccessor::new(db); let data = MyTestData { + ff: "FIRST".to_string(), name: Name { first: "John".to_string(), last: "Doe".to_string(), @@ -559,8 +560,8 @@ mod tests { i_am_a_really_nested_field: 67, }, }, - age: 42, - contacts: vec![Id::new(1)].into_iter().collect(), + age: 1337, + //contacts: vec![Id::new(1)].into_iter().collect(), }; println!("initial state: {:#?}", accessor.db.lock().unwrap()); @@ -570,6 +571,7 @@ mod tests { t.insert( Id::new(2), MyTestData { + ff: "FIRST".to_string(), name: Name { first: "Oldy".to_string(), last: "McOlderton".to_string(), @@ -577,8 +579,8 @@ mod tests { i_am_a_really_nested_field: 32, }, }, - age: 69, - contacts: vec![Id::new(1)].into_iter().collect(), + age: 3000, + //contacts: vec![Id::new(1)].into_iter().collect(), }, ); }); @@ -586,16 +588,16 @@ mod tests { let cloned_accessor = accessor.clone(); let handle = std::thread::spawn(move || { cloned_accessor.transact(|t| { - let Some(Value::String(last_name_of_oldy)) = - t.get_field(Id::::new(2), "name.last") - else { - panic!("expected a string") - }; - + //let Some(Value::String(last_name_of_oldy)) = + // t.get_field(Id::::new(2), "name.last") + //else { + // panic!("expected a string") + //}; + // t.modify(Id::::new(1), |mut data| { data.age = 9; - data.name.nest.i_am_a_really_nested_field = 9; - data.name.last = format!("Gearbox {}", last_name_of_oldy); + data.name.nest.i_am_a_really_nested_field = 99999; + //data.name.last = format!("Gearbox {}", last_name_of_oldy); data }); }); @@ -604,7 +606,9 @@ mod tests { let id = Id::::new(1); accessor.transact(|t| { t.modify(id.clone(), |mut data| { - data.age = 9; + data.ff = "hello".to_string(); + data.age = 8; + data.name.first = "JingleJeimer".to_string(); data.name.last = "Not McOlderton Gearbox".to_string(); data @@ -614,11 +618,13 @@ mod tests { let oldys_contacts = accessor.transact(|t| { let oldy = t.get(Id::::new(2)).unwrap(); - oldy.contacts - .into_iter() - .filter_map(|id| t.get(id)) - .map(|data| format!("{} {}", data.name.first, data.name.last)) - .collect::>() + vec![0] + + //oldy.contacts + // .into_iter() + // .filter_map(|id| t.get(id)) + // .map(|data| format!("{} {}", data.name.first, data.name.last)) + // .collect::>() }); handle.join().unwrap(); diff --git a/service/src/lib/outbound/db_custom/write_set.rs b/service/src/lib/outbound/db_custom/write_set.rs index 2b5d3fb..25468c8 100644 --- a/service/src/lib/outbound/db_custom/write_set.rs +++ b/service/src/lib/outbound/db_custom/write_set.rs @@ -1,14 +1,32 @@ use bincode::{Decode, Encode}; +use facet::{Facet, Field, PtrConst, Shape}; use crate::outbound::db_custom::{TypelessId, Value}; +#[derive(bincode::Encode)] +struct TransactionEntity { + name: String, + data: T, +} +impl TransactionEntity { + fn with(name: impl ToString, data: T) -> Self { + Self { + name: name.to_string(), + data, + } + } +} + pub trait Storable: Diffable + std::fmt::Debug + std::any::Any { fn as_full_write_op(&self) -> WriteOperation where Self: Sized; fn as_partial_write_op(&self, other: &Self) -> WriteOperation where - Self: Sized; + Self: Sized, + { + WriteOperation::Partial(self.diff(other).into_iter().map(Into::into).collect()) + } fn apply_partial_write(&mut self, op: &PartialWrite); @@ -27,6 +45,266 @@ pub trait Diffable { Self: Sized; } +struct ShapeVal<'a, 's>(&'a Shape, PtrConst<'a>, &'s str); + +impl<'a, 's> Diffable for ShapeVal<'a, 's> { + fn diff(&self, other: &Self) -> Vec + where + Self: Sized, + { + let mut modifications = vec![]; + + let facet::Type::User(user) = self.0.ty else { + panic!("invalid type"); + }; + + match user { + facet::UserType::Struct(struct_type) => { + //println!("{:#?}", struct_type.fields); + + for field in struct_type.fields { + let field_shape = field.shape(); + //println!("{:#?}", field_shape); + + let (self_field_ptr, other_field_ptr) = unsafe { + let offset = field.offset.try_into().unwrap(); + + let self_ptr: *const u8 = self.1.as_ptr(); + let me_field_ptr = self_ptr.offset(offset); + + let other_ptr: *const u8 = other.1.as_ptr(); + let other_field_ptr = other_ptr.offset(offset); + + let me = PtrConst::new(std::ptr::NonNull::new_unchecked( + me_field_ptr as *mut u8, + )); + let other = PtrConst::new(std::ptr::NonNull::new_unchecked( + other_field_ptr as *mut u8, + )); + + (me, other) + }; + + match field_shape.def { + facet::Def::Undefined => { + let me = ShapeVal( + field_shape, + self_field_ptr, + &format!("{}{}.", self.2, field.name), + ); + let other = ShapeVal( + field_shape, + other_field_ptr, + &format!("{}{}.", self.2, field.name), + ); + + modifications.extend(me.diff(&other).into_iter()); + } + facet::Def::Scalar => { + let partial_eq = field_shape.vtable.partial_eq.unwrap(); + + let are_partially_eq = + unsafe { partial_eq(self_field_ptr, other_field_ptr) }; + + if !are_partially_eq { + //println!("{:#?}", field); + //println!("{:#?}", field_shape); + modifications.push(FieldDiff::of( + format!("{}{}", self.2, field.name), + Value::from_field_value(other, field), + )); + } + } + facet::Def::Map(map_def) => todo!(), + facet::Def::Set(set_def) => {} + facet::Def::List(list_def) => todo!(), + facet::Def::Array(array_def) => todo!(), + facet::Def::NdArray(nd_array_def) => todo!(), + facet::Def::Slice(slice_def) => todo!(), + facet::Def::Option(option_def) => todo!(), + facet::Def::Pointer(pointer_def) => todo!(), + _ => todo!(), + } + } + } + facet::UserType::Enum(enum_type) => todo!(), + facet::UserType::Union(union_type) => panic!("union types are unsupported"), + facet::UserType::Opaque => panic!("opaque types are invalid"), + } + + modifications + } +} + +impl<'a, 's> ShapeVal<'a, 's> { + fn set_field(&self, field_ident: &str, value: Value) { + let mut idents = field_ident.split("."); + let next = idents.next().unwrap(); + + let facet::Type::User(user) = self.0.ty else { + panic!("invalid type"); + }; + + println!("next: {next}"); + + match user { + facet::UserType::Struct(struct_type) => { + if let Some(field) = struct_type.fields.iter().find(|f| f.name == next) { + let field_shape = field.shape(); + match field_shape.ty { + facet::Type::Primitive(primitive_type) => { + let layout = field_shape + .layout + .sized_layout() + .expect("primitive type is sized"); + let primitive_size = layout.size(); + if Some(primitive_size) != value.size() { + panic!("invalid primitive size"); + } + + unsafe { + let self_ptr = self.1.as_byte_ptr() as *mut u8; + let field_ptr = self_ptr.offset(field.offset.try_into().unwrap()); + + match value { + Value::String(_) | Value::Array(_) => { + panic!("String & Array are not primitive types") + } + + Value::Int(value) => { + let r = (field_ptr as *mut i64).as_mut().unwrap(); + println!("value: {value}, r before: {r}"); + *r = value; + println!("r after: {r}"); + } + Value::Float(value) => *(field_ptr as *mut f64) = value, + Value::Bool(value) => *(field_ptr as *mut bool) = value, + } + } + } + facet::Type::Sequence(sequence_type) => todo!(), + facet::Type::User(user_type) => match user_type { + facet::UserType::Struct(struct_type) => { + let field_ptr = unsafe { + let self_ptr = self.1.as_byte_ptr() as *mut u8; + let field_ptr = + self_ptr.offset(field.offset.try_into().unwrap()); + + PtrConst::new(std::ptr::NonNull::new_unchecked(field_ptr)) + }; + let field_val = ShapeVal(field_shape, field_ptr, ""); + + field_val.set_field(&field_ident[(field.name.len() + 1)..], value); + } + facet::UserType::Enum(enum_type) => todo!(), + facet::UserType::Union(union_type) => todo!(), + facet::UserType::Opaque if field_shape.type_identifier == "String" => { + let Value::String(value) = value else { + panic!("got non-string value"); + }; + + let field_ref = unsafe { + let self_ptr = self.1.as_byte_ptr() as *mut u8; + let field_ptr = self_ptr + .offset(field.offset.try_into().unwrap()) + as *mut String; + + field_ptr.as_mut().unwrap() + }; + + *field_ref = value; + } + facet::UserType::Opaque => panic!("unsupported type"), + }, + facet::Type::Pointer(pointer_type) => todo!(), + } + } else { + panic!("couldn't find field: {next}"); + } + } + facet::UserType::Enum(enum_type) => todo!(), + facet::UserType::Union(union_type) => panic!("union types are unsupported"), + facet::UserType::Opaque => panic!("opaque types are invalid"), + } + } +} + +impl Facet<'a> + for<'a> FieldValueRef<'a>> Diffable for T { + fn diff(&self, other: &Self) -> Vec + where + Self: Sized, + { + let (me, other) = unsafe { + let me = ShapeVal( + Self::SHAPE, + PtrConst::new(std::ptr::NonNull::new_unchecked( + self as *const _ as *mut u8, + )), + "", + ); + + let other = ShapeVal( + Self::SHAPE, + PtrConst::new(std::ptr::NonNull::new_unchecked( + other as *const _ as *mut u8, + )), + "", + ); + + (me, other) + }; + + me.diff(&other) + } +} + +impl Facet<'a> + Diffable + Encode + std::fmt::Debug> Storable for T { + fn as_full_write_op(&self) -> WriteOperation + where + Self: Sized, + { + let data = bincode::encode_to_vec( + TransactionEntity::with(Self::SHAPE.type_identifier, self), + bincode::config::standard(), + ) + .expect("encode MUST succeed"); + + WriteOperation::Full(data) + } + + fn apply_partial_write(&mut self, op: &PartialWrite) { + println!("{:#?}", op); + println!("before: {:#?}", self); + let value = Value::from_bytes(&op.data); + self.set_field(&op.field_ident, value); + println!("after: {:#?}", self); + } + + fn set_field(&mut self, field_ident: &str, value: Value) + where + Self: for<'a> Facet<'a> + Sized, + { + let me = unsafe { + ShapeVal( + Self::SHAPE, + PtrConst::new(std::ptr::NonNull::new_unchecked( + self as *const _ as *mut u8, + )), + "", + ) + }; + + me.set_field(field_ident, value); + } + + fn field(&self, field_ident: &str) -> Option + where + Self: for<'a> Facet<'a> + Sized, + { + None + } +} + pub struct FieldDiff { pub(super) field_ident: String, pub(super) value: Value, @@ -52,6 +330,79 @@ impl From for PartialWrite { } } +trait FieldValueRef<'a> { + fn get_field_value_as_ref(&'a self, field: &Field) -> &'a F { + unsafe { + let container_ptr = self as *const _ as *const u8; + let field_ptr = container_ptr.offset(field.offset.try_into().unwrap()) as *const F; + + field_ptr.as_ref().unwrap() + } + } +} + +impl<'a, T: Facet<'a>> FieldValueRef<'a> for T {} + +impl<'a, 's> FieldValueRef<'a> for ShapeVal<'a, 's> { + fn get_field_value_as_ref(&'a self, field: &Field) -> &'a F { + unsafe { + let container_ptr: *const u8 = self.1.as_ptr(); + let field_ptr = container_ptr.offset(field.offset.try_into().unwrap()) as *const F; + + field_ptr.as_ref().unwrap() + } + } +} + +impl Value { + fn from_field_value<'a, T: FieldValueRef<'a>>(container: &'a T, field: &Field) -> Self { + let shape = field.shape(); + + match shape.ty { + facet::Type::Primitive(primitive_type) => match primitive_type { + facet::PrimitiveType::Boolean => { + Self::Bool(*container.get_field_value_as_ref(field)) + } + facet::PrimitiveType::Numeric(numeric_type) => { + let field_layout = field.shape().layout.sized_layout().unwrap(); + + const I32_SIZE: usize = size_of::(); + const I64_SIZE: usize = size_of::(); + + match (field_layout.size(), numeric_type) { + (I32_SIZE, facet::NumericType::Integer { signed }) if signed => { + Value::Int(*container.get_field_value_as_ref::(field) as i64) + } + (I32_SIZE, facet::NumericType::Integer { .. }) => { + Value::Int(*container.get_field_value_as_ref::(field) as i64) + } + + (I64_SIZE, facet::NumericType::Integer { signed }) if signed => { + Value::Int(*container.get_field_value_as_ref::(field) as i64) + } + (I64_SIZE, facet::NumericType::Integer { .. }) => { + Value::Int(*container.get_field_value_as_ref::(field) as i64) + } + _ => panic!("invalid numeric size"), + } + } + facet::PrimitiveType::Textual(textual_type) => todo!(), + facet::PrimitiveType::Never => todo!(), + }, + + facet::Type::Sequence(sequence_type) => todo!(), + facet::Type::User(user_type) => { + if shape.type_identifier == "String" { + Value::String(container.get_field_value_as_ref::(field).clone()) + } else { + todo!("asdkjhfadsfkdasjhfdssklsadkljfkljasdhfkjlhsdfklsdkfhsd"); + } + } + facet::Type::Pointer(pointer_type) => todo!("alkjhdfklajdhs"), + } + } +} + #[derive(Debug, Default, Encode, Decode)] pub struct WriteSet { // TODO: make an actual set @@ -75,212 +426,3 @@ pub struct PartialWrite { pub(super) field_ident: String, pub(super) data: Vec, } - -#[macro_export] -macro_rules! primitive_assign { - ($self:ident, $field_name:ident, $value:ident, $ty:ty, $variant:ident) => {{ - let Value::$variant(value) = $value else { - panic!( - "expected '{}' to be a '{}'", - stringify!($field_name), - stringify!($ty) - ); - }; - - $self.$field_name = value; - }}; -} - -#[macro_export] -macro_rules! primitive_return { - ($self:ident, $field_name:ident, $variant:ident) => { - Some(Value::$variant($self.$field_name.clone())) - }; -} - -#[macro_export] -macro_rules! db_type { - ($self:ident, $field_name:ident, i64, $value:ident, $field_ident:ident) => { - $crate::primitive_assign!($self, $field_name, $value, i64, Int) - }; - ($self:ident, $field_name:ident, String, $value:ident, $field_ident:ident) => { - $crate::primitive_assign!($self, $field_name, $value, String, String) - }; - - ($self:ident, $field_name:ident, (HashSet>), $value:ident, $field_ident:ident) => { - { - let Value::Array(value) = $value else { - panic!( - "expected '{}' to be a 'Vec>'", - stringify!($field_name), - stringify!($ty) - ); - }; - - $self.$field_name = value.into_iter().map(|id| Id::<_>::new(id.0)).collect(); - } - }; - - ($self:ident, $field_name:ident, $field_type:ty, $value:ident, $field_ident:ident) => { - { - let field_name_len = stringify!($field_name).len(); - if field_name_len == $field_ident.len() { - panic!("attempt to assign non-primitive"); - } - - $self.$field_name.set_field(&$field_ident[(field_name_len+1)..], $value); - } - }; - - ($self:ident, $field_name:ident, i64, $field_ident:ident) => {{ - Some(Value::Int($self.$field_name.clone())) - }}; - ($self:ident, $field_name:ident, String, $field_ident:ident) => {{ - Some(Value::String($self.$field_name.clone())) - }}; - ($self:ident, $field_name:ident, (HashSet>), $field_ident:ident) => { - Some(Value::Array($self.$field_name.iter().map(|id| id.into()).collect())) - }; - ($self:ident, $field_name:ident, $field_type:ty, $field_ident:ident) => {{ - let field_name_len = stringify!($field_name).len(); - if field_name_len == $field_ident.len() { - panic!("attempt to assign non-primitive"); - } - - $self.$field_name.field(&$field_ident[(field_name_len+1)..]) - }}; - - (@diff $self:ident, $other:ident, $modifications:ident, $field_name:ident, i64) => { - if $self.$field_name != $other.$field_name { - $modifications.push(FieldDiff::of( - stringify!($field_name), - Value::Int($other.$field_name.clone()), - )); - } - }; - (@diff $self:ident, $other:ident, $modifications:ident, $field_name:ident, String) => { - if $self.$field_name != $other.$field_name { - $modifications.push(FieldDiff::of( - stringify!($field_name), - Value::String($other.$field_name.clone()), - )); - } - }; - (@diff $self:ident, $other:ident, $modifications:ident, $field_name:ident, (HashSet>)) => { - let diff = $self.$field_name.difference(&$other.$field_name); - if diff.count() > 0 { - $modifications.push(FieldDiff::of( - stringify!($field_name), - Value::Array($other.$field_name.iter().map(|id| id.into()).collect()) - )); - } - }; - (@diff $self:ident, $other:ident, $modifications:ident, $field_name:ident, $field_type:ty) => { - $modifications.extend( - $self.$field_name - .diff(&$other.$field_name) - .into_iter() - .map(|diff| { - // NOTE(pcleavelin): we have to recreate the `field_ident` path via recursive - // string building lol - FieldDiff::of( - format!("{}.{}", stringify!($field_name), &diff.field_ident), - diff.value - ) - }) - ); - }; - - { - $( - struct $db_type_name:ident { - $( - $field_name:ident: $field_type:tt, - )* - } - )* - } => { - $( - #[derive(Debug, Clone, ::bincode::Encode, ::bincode::Decode)] - pub struct $db_type_name { - $( - $field_name: $field_type, - )* - } - - impl Storable for $db_type_name { - fn as_full_write_op(&self) -> WriteOperation - where - Self: Sized, - { - let data = - bincode::encode_to_vec(A::with(stringify!($db_type_name), self), bincode::config::standard()) - .expect("encode MUST succeed"); - - WriteOperation::Full(data) - } - fn as_partial_write_op(&self, other: &Self) -> WriteOperation - where - Self: Sized, - { - WriteOperation::Partial(self.diff(other).into_iter().map(Into::into).collect()) - } - - fn apply_partial_write(&mut self, op: &write_set::PartialWrite) { - let value = Value::from_bytes(&op.data); - self.set_field(&op.field_ident, value); - } - - fn set_field(&mut self, field_ident: &str, value: Value) - where Self: Sized, - { - let mut idents = field_ident.split("."); - let next = idents.next().unwrap(); - - match next { - $( - stringify!($field_name) => { - db_type!(self, $field_name, $field_type, value, field_ident) - } - ),* - - _ => panic!("set_field: invalid field '{next}'"), - } - } - - fn field(&self, field_ident: &str) -> Option - where - Self: Sized, - { - let mut idents = field_ident.split("."); - let next = idents.next().unwrap(); - - match next { - $( - stringify!($field_name) => { - db_type!(self, $field_name, $field_type, field_ident) - } - ),* - - _ => panic!("field: invalid field '{next}'"), - } - } - } - - impl Diffable for $db_type_name { - fn diff(&self, other: &Self) -> Vec - where - Self: Sized, - { - let mut modifications = vec![]; - - $( - db_type!(@diff self, other, modifications, $field_name, $field_type); - )* - - modifications - } - } - )* - }; -}