Skip to content

Commit 08e77a9

Browse files
authored
feat(grpc): load full entity to match subscription in set entity (#414)
1 parent c5d2508 commit 08e77a9

File tree

3 files changed

+253
-0
lines changed

3 files changed

+253
-0
lines changed

crates/proto/src/schema.rs

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,3 +331,114 @@ impl TryFrom<proto::types::Ty> for Ty {
331331
}
332332
}
333333
}
334+
335+
#[cfg(test)]
336+
mod tests {
337+
use super::*;
338+
use dojo_types::schema::{Member, Struct};
339+
340+
#[test]
341+
fn test_nested_struct_proto_conversion_preserves_values() {
342+
// Create a nested struct with primitive values
343+
let nested_struct = Struct {
344+
name: "TroopGuards".to_string(),
345+
children: vec![
346+
Member {
347+
name: "knight_count".to_string(),
348+
ty: Ty::Primitive(Primitive::U32(Some(100))),
349+
key: false,
350+
},
351+
Member {
352+
name: "crossbowman_count".to_string(),
353+
ty: Ty::Primitive(Primitive::U32(Some(50))),
354+
key: false,
355+
},
356+
],
357+
};
358+
359+
let main_struct = Struct {
360+
name: "Game-Structure".to_string(),
361+
children: vec![Member {
362+
name: "troop_guards".to_string(),
363+
ty: Ty::Struct(nested_struct),
364+
key: false,
365+
}],
366+
};
367+
368+
// Convert to proto
369+
let proto_struct: proto::types::Struct = main_struct.into();
370+
371+
// Verify main struct
372+
assert_eq!(proto_struct.name, "Game-Structure");
373+
assert_eq!(proto_struct.children.len(), 1);
374+
375+
// Verify nested struct
376+
let troop_guards = &proto_struct.children[0];
377+
assert_eq!(troop_guards.name, "troop_guards");
378+
assert!(!troop_guards.key);
379+
380+
let nested_ty = troop_guards.ty.as_ref().expect("ty should be present");
381+
if let Some(proto::types::ty::TyType::Struct(nested)) = &nested_ty.ty_type {
382+
assert_eq!(nested.name, "TroopGuards");
383+
assert_eq!(nested.children.len(), 2);
384+
385+
// Verify knight_count
386+
let knight_count = &nested.children[0];
387+
assert_eq!(knight_count.name, "knight_count");
388+
let knight_ty = knight_count.ty.as_ref().expect("ty should be present");
389+
if let Some(proto::types::ty::TyType::Primitive(prim)) = &knight_ty.ty_type {
390+
if let Some(proto::types::primitive::PrimitiveType::U32(val)) = prim.primitive_type
391+
{
392+
assert_eq!(val, 100, "knight_count should be 100, got {}", val);
393+
} else {
394+
panic!("knight_count should be U32");
395+
}
396+
} else {
397+
panic!("knight_count should be Primitive");
398+
}
399+
400+
// Verify crossbowman_count
401+
let crossbowman_count = &nested.children[1];
402+
assert_eq!(crossbowman_count.name, "crossbowman_count");
403+
let crossbowman_ty = crossbowman_count.ty.as_ref().expect("ty should be present");
404+
if let Some(proto::types::ty::TyType::Primitive(prim)) = &crossbowman_ty.ty_type {
405+
if let Some(proto::types::primitive::PrimitiveType::U32(val)) = prim.primitive_type
406+
{
407+
assert_eq!(val, 50, "crossbowman_count should be 50, got {}", val);
408+
} else {
409+
panic!("crossbowman_count should be U32");
410+
}
411+
} else {
412+
panic!("crossbowman_count should be Primitive");
413+
}
414+
} else {
415+
panic!("troop_guards ty should be Struct");
416+
}
417+
}
418+
419+
#[test]
420+
fn test_primitive_none_becomes_zero_in_proto() {
421+
// Test that None primitive values become zero after conversion
422+
let prim = Primitive::U32(None);
423+
let proto_prim: proto::types::Primitive = prim.into();
424+
425+
if let Some(proto::types::primitive::PrimitiveType::U32(val)) = proto_prim.primitive_type {
426+
assert_eq!(val, 0, "None U32 should become 0 in proto");
427+
} else {
428+
panic!("Should be U32");
429+
}
430+
}
431+
432+
#[test]
433+
fn test_primitive_some_value_preserved_in_proto() {
434+
// Test that Some(value) is preserved after conversion
435+
let prim = Primitive::U32(Some(42));
436+
let proto_prim: proto::types::Primitive = prim.into();
437+
438+
if let Some(proto::types::primitive::PrimitiveType::U32(val)) = proto_prim.primitive_type {
439+
assert_eq!(val, 42, "Some(42) should become 42 in proto");
440+
} else {
441+
panic!("Should be U32");
442+
}
443+
}
444+
}

crates/sqlite/sqlite/src/executor/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,13 @@ impl<P: Provider + Sync + Send + Clone + 'static> Executor<'_, P> {
571571
let mut entity_updated = torii_sqlite_types::Entity::from_row(&row)?;
572572
entity_updated.updated_model = Some(entity.ty.clone());
573573

574+
// Load full entity from DB for subscription matching
575+
// This ensures MemberClause filters work with partial updates (write_member)
576+
let full_model =
577+
Self::entity_model(tx, entity.model_id.clone(), entity.entity_id.clone())
578+
.await?;
579+
entity_updated.match_model = full_model;
580+
574581
if entity_updated.keys.is_empty() {
575582
warn!(target: LOG_TARGET, "Entity has been updated without being set before. Keys are not known and non-updated values will be NULL.");
576583
}

crates/sqlite/types/src/lib.rs

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -784,4 +784,139 @@ mod tests {
784784
let propagated_model = entity_with_metadata.match_model.unwrap();
785785
assert_eq!(propagated_model.name(), "Game-Player");
786786
}
787+
788+
#[test]
789+
fn test_entity_conversion_preserves_nested_struct_values() {
790+
use dojo_types::schema::Member;
791+
792+
let now = Utc::now();
793+
794+
// Create a nested struct similar to TroopGuards
795+
let nested_struct = Ty::Struct(Struct {
796+
name: "TroopGuards".to_string(),
797+
children: vec![
798+
Member {
799+
name: "knight_count".to_string(),
800+
ty: Ty::Primitive(dojo_types::primitive::Primitive::U32(Some(100))),
801+
key: false,
802+
},
803+
Member {
804+
name: "crossbowman_count".to_string(),
805+
ty: Ty::Primitive(dojo_types::primitive::Primitive::U32(Some(50))),
806+
key: false,
807+
},
808+
Member {
809+
name: "paladin_count".to_string(),
810+
ty: Ty::Primitive(dojo_types::primitive::Primitive::U32(Some(25))),
811+
key: false,
812+
},
813+
],
814+
});
815+
816+
// Create the main model with nested struct
817+
let entity = Entity {
818+
id: "0xworld:0xentity".to_string(),
819+
entity_id: "0x123".to_string(),
820+
world_address: "0xabc".to_string(),
821+
keys: "0x1/0x2".to_string(),
822+
event_id: "event_123".to_string(),
823+
executed_at: now,
824+
created_at: now,
825+
updated_at: now,
826+
updated_model: Some(Ty::Struct(Struct {
827+
name: "Game-Structure".to_string(),
828+
children: vec![
829+
Member {
830+
name: "base".to_string(),
831+
ty: Ty::Struct(Struct {
832+
name: "Position".to_string(),
833+
children: vec![
834+
Member {
835+
name: "coord_x".to_string(),
836+
ty: Ty::Primitive(dojo_types::primitive::Primitive::U32(Some(
837+
10,
838+
))),
839+
key: false,
840+
},
841+
Member {
842+
name: "coord_y".to_string(),
843+
ty: Ty::Primitive(dojo_types::primitive::Primitive::U32(Some(
844+
20,
845+
))),
846+
key: false,
847+
},
848+
],
849+
}),
850+
key: false,
851+
},
852+
Member {
853+
name: "troop_guards".to_string(),
854+
ty: nested_struct,
855+
key: false,
856+
},
857+
],
858+
})),
859+
deleted: false,
860+
match_model: None,
861+
};
862+
863+
// Convert to proto Entity
864+
let proto_entity: torii_proto::schema::Entity<false> = entity.into();
865+
866+
// Verify structure
867+
assert_eq!(proto_entity.models.len(), 1);
868+
let model = &proto_entity.models[0];
869+
assert_eq!(model.name, "Game-Structure");
870+
assert_eq!(model.children.len(), 2);
871+
872+
// Find the troop_guards member
873+
let troop_guards = model
874+
.children
875+
.iter()
876+
.find(|c| c.name == "troop_guards")
877+
.expect("troop_guards member should exist");
878+
879+
// Verify nested struct values are preserved
880+
if let Ty::Struct(nested) = &troop_guards.ty {
881+
assert_eq!(nested.name, "TroopGuards");
882+
assert_eq!(nested.children.len(), 3);
883+
884+
// Verify each primitive value
885+
let knight_count = nested
886+
.children
887+
.iter()
888+
.find(|c| c.name == "knight_count")
889+
.expect("knight_count should exist");
890+
if let Ty::Primitive(dojo_types::primitive::Primitive::U32(val)) = &knight_count.ty {
891+
assert_eq!(*val, Some(100), "knight_count should be 100, not zero");
892+
} else {
893+
panic!("knight_count should be U32 primitive");
894+
}
895+
896+
let crossbowman_count = nested
897+
.children
898+
.iter()
899+
.find(|c| c.name == "crossbowman_count")
900+
.expect("crossbowman_count should exist");
901+
if let Ty::Primitive(dojo_types::primitive::Primitive::U32(val)) = &crossbowman_count.ty
902+
{
903+
assert_eq!(*val, Some(50), "crossbowman_count should be 50, not zero");
904+
} else {
905+
panic!("crossbowman_count should be U32 primitive");
906+
}
907+
908+
let paladin_count = nested
909+
.children
910+
.iter()
911+
.find(|c| c.name == "paladin_count")
912+
.expect("paladin_count should exist");
913+
if let Ty::Primitive(dojo_types::primitive::Primitive::U32(val)) = &paladin_count.ty {
914+
assert_eq!(*val, Some(25), "paladin_count should be 25, not zero");
915+
} else {
916+
panic!("paladin_count should be U32 primitive");
917+
}
918+
} else {
919+
panic!("troop_guards should be a Struct type");
920+
}
921+
}
787922
}

0 commit comments

Comments
 (0)