1mod query_impls;
2
3use std::any::TypeId;
4use std::marker::PhantomData;
5use std::sync::{Mutex, OnceLock};
6
7use crate::entity::entity_set::{EntitySet, EntitySetIterator};
8use crate::entity::multi_property::type_ids_to_multi_property_id;
9use crate::entity::property_list::{PropertyInitializationList, PropertyList};
10use crate::entity::property_store::PropertyStore;
11use crate::entity::Entity;
12use crate::hashing::HashMap;
13use crate::prelude::EntityId;
14use crate::{Context, IxaError};
15
16pub struct EntityPropertyTuple<E: Entity, T> {
32 inner: T,
33 _marker: PhantomData<E>,
34}
35
36impl<E: Entity, T: Copy> Copy for EntityPropertyTuple<E, T> {}
38
39impl<E: Entity, T: Clone> Clone for EntityPropertyTuple<E, T> {
40 fn clone(&self) -> Self {
41 Self {
42 inner: self.inner.clone(),
43 _marker: PhantomData,
44 }
45 }
46}
47
48impl<E: Entity, T: std::fmt::Debug> std::fmt::Debug for EntityPropertyTuple<E, T> {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 f.debug_struct("EntityPropertyTuple")
51 .field("inner", &self.inner)
52 .finish()
53 }
54}
55
56impl<E: Entity, T> EntityPropertyTuple<E, T> {
57 pub fn new(inner: T) -> Self {
59 Self {
60 inner,
61 _marker: PhantomData,
62 }
63 }
64
65 pub fn inner(&self) -> &T {
67 &self.inner
68 }
69
70 pub fn into_inner(self) -> T {
72 self.inner
73 }
74}
75
76impl<E: Entity, T: QueryInternal<E>> QueryInternal<E> for EntityPropertyTuple<E, T> {
77 type QueryParts<'a>
78 = T::QueryParts<'a>
79 where
80 Self: 'a;
81
82 fn get_type_ids(&self) -> Vec<TypeId> {
83 self.inner.get_type_ids()
84 }
85
86 fn multi_property_id(&self) -> Option<usize> {
87 self.inner.multi_property_id()
88 }
89
90 fn is_empty_query(&self) -> bool {
91 self.inner.is_empty_query()
92 }
93
94 fn query_parts(&self) -> Self::QueryParts<'_> {
95 self.inner.query_parts()
96 }
97
98 fn new_query_result<'c>(&self, context: &'c Context) -> EntitySet<'c, E> {
99 self.inner.new_query_result(context)
100 }
101
102 fn match_entity(&self, entity_id: EntityId<E>, context: &Context) -> bool {
103 self.inner.match_entity(entity_id, context)
104 }
105
106 fn filter_entities(&self, entities: &mut Vec<EntityId<E>>, context: &Context) {
107 self.inner.filter_entities(entities, context)
108 }
109}
110
111impl<E: Entity, T: PropertyList<E>> PropertyList<E> for EntityPropertyTuple<E, T> {
112 fn validate() -> Result<(), IxaError> {
113 T::validate()
114 }
115
116 fn contains_properties(property_type_ids: &[TypeId]) -> bool {
117 T::contains_properties(property_type_ids)
118 }
119
120 fn set_values_for_new_entity(
121 &self,
122 entity_id: EntityId<E>,
123 property_store: &mut PropertyStore<E>,
124 ) {
125 let tuple = *self;
126 tuple
127 .into_inner()
128 .set_values_for_new_entity(entity_id, property_store)
129 }
130
131 fn get_values_for_entity(context: &Context, entity_id: EntityId<E>) -> Self {
132 EntityPropertyTuple::new(T::get_values_for_entity(context, entity_id))
133 }
134}
135
136impl<E: Entity, PL: PropertyList<E>> PropertyInitializationList<E> for EntityPropertyTuple<E, PL> {}
137
138pub trait QueryInternal<E: Entity>: 'static {
140 type QueryParts<'a>: AsRef<[&'a dyn std::any::Any]>
142 where
143 Self: 'a;
144
145 #[must_use]
147 fn get_type_ids(&self) -> Vec<TypeId>;
148
149 #[must_use]
152 fn multi_property_id(&self) -> Option<usize> {
153 #[allow(clippy::type_complexity)]
154 static REGISTRY: OnceLock<Mutex<HashMap<(usize, TypeId), &'static Option<usize>>>> =
155 OnceLock::new();
156
157 let map = REGISTRY.get_or_init(|| Mutex::new(HashMap::default()));
158 let mut map = map.lock().unwrap();
159 let key = (E::id(), TypeId::of::<Self>());
160 let entry = *map.entry(key).or_insert_with(|| {
161 let mut types = self.get_type_ids();
162 types.sort_unstable();
163 Box::leak(Box::new(type_ids_to_multi_property_id(
164 E::id(),
165 types.as_slice(),
166 )))
167 });
168
169 *entry
170 }
171
172 #[must_use]
174 fn is_empty_query(&self) -> bool {
175 false
176 }
177
178 #[must_use]
180 fn query_parts(&self) -> Self::QueryParts<'_>;
181
182 #[must_use]
184 fn new_query_result<'c>(&self, context: &'c Context) -> EntitySet<'c, E>;
185
186 #[must_use]
188 fn new_query_result_iterator<'c>(&self, context: &'c Context) -> EntitySetIterator<'c, E> {
189 self.new_query_result(context).into_iter()
190 }
191
192 #[must_use]
194 fn match_entity(&self, entity_id: EntityId<E>, context: &Context) -> bool;
195
196 fn filter_entities(&self, entities: &mut Vec<EntityId<E>>, context: &Context);
198}
199
200pub trait Query<E: Entity>: QueryInternal<E> {}
208
209impl<E: Entity, QI: QueryInternal<E>> Query<E> for EntityPropertyTuple<E, QI> {}
210impl<E: Entity> Query<E> for E {}
211
212#[cfg(test)]
213mod tests {
214
215 use super::QueryInternal;
216 use crate::prelude::*;
217 use crate::{
218 define_derived_property, define_entity, define_multi_property, define_property, Context,
219 };
220
221 define_entity!(Person);
222
223 define_property!(struct Age(u8), Person, default_const = Age(0));
224 define_property!(struct County(u32), Person, default_const = County(0));
225 define_property!(struct Height(u32), Person, default_const = Height(0));
226 define_property!(
227 enum RiskCategory {
228 High,
229 Low,
230 },
231 Person
232 );
233
234 define_multi_property!(Person, (Age, County));
235
236 #[test]
237 fn empty_tuple_query_internal_matches_all_entities() {
238 let mut context = Context::new();
239 let person1 = context
240 .add_entity(with!(Person, Age(42), RiskCategory::High))
241 .unwrap();
242 let person2 = context
243 .add_entity(with!(Person, Age(30), RiskCategory::Low))
244 .unwrap();
245
246 assert_eq!(<() as QueryInternal<Person>>::get_type_ids(&()), Vec::new());
247 assert!(<() as QueryInternal<Person>>::is_empty_query(&()));
248 assert!(<() as QueryInternal<Person>>::query_parts(&())
249 .as_ref()
250 .is_empty());
251
252 let people = <() as QueryInternal<Person>>::new_query_result_iterator(&(), &context)
253 .collect::<Vec<_>>();
254 assert_eq!(people, vec![person1, person2]);
255 assert!(<() as QueryInternal<Person>>::match_entity(
256 &(),
257 person1,
258 &context
259 ));
260
261 let mut ids = vec![person1, person2];
262 <() as QueryInternal<Person>>::filter_entities(&(), &mut ids, &context);
263 assert_eq!(ids, vec![person1, person2]);
264 }
265
266 #[test]
267 fn entity_query_internal_has_no_type_ids() {
268 let mut context = Context::new();
269 let person1 = context
270 .add_entity(with!(Person, Age(42), RiskCategory::High))
271 .unwrap();
272 let person2 = context
273 .add_entity(with!(Person, Age(30), RiskCategory::Low))
274 .unwrap();
275
276 assert_eq!(
277 <Person as QueryInternal<Person>>::get_type_ids(&Person),
278 Vec::new()
279 );
280 assert_eq!(
281 <Person as QueryInternal<Person>>::multi_property_id(&Person),
282 None
283 );
284 assert!(<Person as QueryInternal<Person>>::query_parts(&Person)
285 .as_ref()
286 .is_empty());
287
288 let people = <Person as QueryInternal<Person>>::new_query_result(&Person, &context)
289 .into_iter()
290 .collect::<Vec<_>>();
291 assert_eq!(people, vec![person1, person2]);
292 assert!(<Person as QueryInternal<Person>>::match_entity(
293 &Person, person1, &context
294 ));
295
296 let mut ids = vec![person1, person2];
297 <Person as QueryInternal<Person>>::filter_entities(&Person, &mut ids, &context);
298 assert_eq!(ids, vec![person1, person2]);
299 }
300
301 #[test]
302 fn singleton_query_result_iterator_uses_indexed_and_unindexed_paths() {
303 let mut context = Context::new();
304 let high = context
305 .add_entity(with!(Person, RiskCategory::High))
306 .unwrap();
307 let _low = context
308 .add_entity(with!(Person, RiskCategory::Low))
309 .unwrap();
310
311 let people = <(RiskCategory,) as QueryInternal<Person>>::new_query_result_iterator(
312 &(RiskCategory::High,),
313 &context,
314 )
315 .collect::<Vec<_>>();
316 assert_eq!(people, vec![high]);
317
318 let mut indexed_context = Context::new();
319 indexed_context.index_property::<Person, RiskCategory>();
320 let indexed_high = indexed_context
321 .add_entity(with!(Person, RiskCategory::High))
322 .unwrap();
323 let _indexed_low = indexed_context
324 .add_entity(with!(Person, RiskCategory::Low))
325 .unwrap();
326
327 let people = <(RiskCategory,) as QueryInternal<Person>>::new_query_result_iterator(
328 &(RiskCategory::High,),
329 &indexed_context,
330 )
331 .collect::<Vec<_>>();
332 assert_eq!(people, vec![indexed_high]);
333
334 let mut empty_index_context = Context::new();
335 empty_index_context.index_property::<Person, Age>();
336 let _ = empty_index_context
337 .add_entity(with!(Person, Age(42), RiskCategory::Low))
338 .unwrap();
339
340 let people = <(Age,) as QueryInternal<Person>>::new_query_result_iterator(
341 &(Age(99),),
342 &empty_index_context,
343 )
344 .collect::<Vec<_>>();
345 assert!(people.is_empty());
346 }
347
348 #[test]
349 fn tuple_query_result_iterator_uses_indexed_and_unindexed_paths() {
350 let mut context = Context::new();
351 let matching1 = context
352 .add_entity(with!(Person, Age(28), County(0), RiskCategory::High))
353 .unwrap();
354 let _wrong_county = context
355 .add_entity(with!(Person, Age(28), County(1), RiskCategory::Low))
356 .unwrap();
357 let _wrong_age = context
358 .add_entity(with!(Person, Age(30), County(0), RiskCategory::Low))
359 .unwrap();
360 let matching2 = context
361 .add_entity(with!(Person, Age(28), County(0), RiskCategory::Low))
362 .unwrap();
363
364 let people = <(Age, County) as QueryInternal<Person>>::new_query_result_iterator(
365 &(Age(28), County(0)),
366 &context,
367 )
368 .collect::<Vec<_>>();
369 assert_eq!(people, vec![matching1, matching2]);
370
371 let mut indexed_context = Context::new();
372 indexed_context.index_property::<Person, (Age, County)>();
373 let indexed_matching = indexed_context
374 .add_entity(with!(Person, Age(28), County(0), RiskCategory::High))
375 .unwrap();
376 let _indexed_nonmatching = indexed_context
377 .add_entity(with!(Person, Age(28), County(1), RiskCategory::Low))
378 .unwrap();
379
380 let people = <(Age, County) as QueryInternal<Person>>::new_query_result_iterator(
381 &(Age(28), County(0)),
382 &indexed_context,
383 )
384 .collect::<Vec<_>>();
385 assert_eq!(people, vec![indexed_matching]);
386
387 let people = <(Age, County) as QueryInternal<Person>>::new_query_result_iterator(
388 &(Age(99), County(99)),
389 &indexed_context,
390 )
391 .collect::<Vec<_>>();
392 assert!(people.is_empty());
393 }
394
395 #[test]
396 fn singleton_filter_entities_keeps_matching_entities() {
397 let mut context = Context::new();
398 let high1 = context
399 .add_entity(with!(Person, RiskCategory::High))
400 .unwrap();
401 let low = context
402 .add_entity(with!(Person, RiskCategory::Low))
403 .unwrap();
404 let high2 = context
405 .add_entity(with!(Person, RiskCategory::High))
406 .unwrap();
407
408 let mut people = vec![high1, low, high2];
409 context.filter_entities(&mut people, with!(Person, RiskCategory::High));
410
411 assert_eq!(people, vec![high1, high2]);
412 }
413
414 #[test]
415 fn tuple_filter_entities_falls_through_after_unsupported_multi_index_lookup() {
416 let mut context = Context::new();
417 let matching1 = context
418 .add_entity(with!(Person, Age(28), County(0), RiskCategory::High))
419 .unwrap();
420 let wrong_county = context
421 .add_entity(with!(Person, Age(28), County(1), RiskCategory::Low))
422 .unwrap();
423 let wrong_age = context
424 .add_entity(with!(Person, Age(30), County(0), RiskCategory::Low))
425 .unwrap();
426 let matching2 = context
427 .add_entity(with!(Person, Age(28), County(0), RiskCategory::Low))
428 .unwrap();
429
430 let mut people = vec![matching1, wrong_county, wrong_age, matching2];
431 context.filter_entities(&mut people, with!(Person, County(0), Age(28)));
432
433 assert_eq!(people, vec![matching1, matching2]);
434 }
435
436 #[test]
437 fn with_query_results() {
438 let mut context = Context::new();
439 let _ = context
440 .add_entity(with!(Person, RiskCategory::High))
441 .unwrap();
442
443 context.with_query_results(with!(Person, RiskCategory::High), &mut |people| {
444 assert_eq!(people.into_iter().count(), 1);
445 });
446 }
447
448 #[test]
449 fn with_query_results_empty() {
450 let context = Context::new();
451
452 context.with_query_results(with!(Person, RiskCategory::High), &mut |people| {
453 assert_eq!(people.into_iter().count(), 0);
454 });
455 }
456
457 #[test]
458 fn query_entity_count() {
459 let mut context = Context::new();
460 let _ = context
461 .add_entity(with!(Person, RiskCategory::High))
462 .unwrap();
463
464 assert_eq!(
465 context.query_entity_count(with!(Person, RiskCategory::High)),
466 1
467 );
468 }
469
470 #[test]
471 fn query_entity_count_empty() {
472 let context = Context::new();
473
474 assert_eq!(
475 context.query_entity_count(with!(Person, RiskCategory::High)),
476 0
477 );
478 }
479
480 #[test]
481 fn with_query_results_macro_index_first() {
482 let mut context = Context::new();
483 let _ = context
484 .add_entity(with!(Person, RiskCategory::High))
485 .unwrap();
486 context.index_property::<_, RiskCategory>();
487 assert!(context.is_property_indexed::<Person, RiskCategory>());
488
489 context.with_query_results(with!(Person, RiskCategory::High), &mut |people| {
490 assert_eq!(people.into_iter().count(), 1);
491 });
492 }
493
494 #[test]
495 fn with_query_results_macro_index_second() {
496 let mut context = Context::new();
497 let _ = context.add_entity(with!(Person, RiskCategory::High));
498
499 context.with_query_results(with!(Person, RiskCategory::High), &mut |people| {
500 assert_eq!(people.into_iter().count(), 1);
501 });
502 assert!(!context.is_property_indexed::<Person, RiskCategory>());
503
504 context.index_property::<Person, RiskCategory>();
505 assert!(context.is_property_indexed::<Person, RiskCategory>());
506
507 context.with_query_results(with!(Person, RiskCategory::High), &mut |people| {
508 assert_eq!(people.into_iter().count(), 1);
509 });
510 }
511
512 #[test]
513 fn with_query_results_macro_change() {
514 let mut context = Context::new();
515 let person1 = context
516 .add_entity(with!(Person, RiskCategory::High))
517 .unwrap();
518
519 context.with_query_results(with!(Person, RiskCategory::High), &mut |people| {
520 assert_eq!(people.into_iter().count(), 1);
521 });
522
523 context.with_query_results(with!(Person, RiskCategory::Low), &mut |people| {
524 assert_eq!(people.into_iter().count(), 0);
525 });
526
527 context.set_property(person1, RiskCategory::Low);
528 context.with_query_results(with!(Person, RiskCategory::High), &mut |people| {
529 assert_eq!(people.into_iter().count(), 0);
530 });
531
532 context.with_query_results(with!(Person, RiskCategory::Low), &mut |people| {
533 assert_eq!(people.into_iter().count(), 1);
534 });
535 }
536
537 #[test]
538 fn with_query_results_index_after_add() {
539 let mut context = Context::new();
540 let _ = context
541 .add_entity(with!(Person, RiskCategory::High))
542 .unwrap();
543 context.index_property::<Person, RiskCategory>();
544 assert!(context.is_property_indexed::<Person, RiskCategory>());
545 context.with_query_results(with!(Person, RiskCategory::High), &mut |people| {
546 assert_eq!(people.into_iter().count(), 1);
547 });
548 }
549
550 #[test]
551 fn with_query_results_add_after_index() {
552 let mut context = Context::new();
553 let _ = context
554 .add_entity(with!(Person, RiskCategory::High))
555 .unwrap();
556 context.index_property::<Person, RiskCategory>();
557 assert!(context.is_property_indexed::<Person, RiskCategory>());
558 context.with_query_results(with!(Person, RiskCategory::High), &mut |people| {
559 assert_eq!(people.into_iter().count(), 1);
560 });
561
562 let _ = context
563 .add_entity(with!(Person, RiskCategory::High))
564 .unwrap();
565 context.with_query_results(with!(Person, RiskCategory::High), &mut |people| {
566 assert_eq!(people.into_iter().count(), 2);
567 });
568 }
569
570 #[test]
571 fn with_query_results_cast_value() {
572 let mut context = Context::new();
573 let _ = context
574 .add_entity(with!(Person, Age(42), RiskCategory::High))
575 .unwrap();
576
577 context.with_query_results(with!(Person, Age(42)), &mut |people| {
578 assert_eq!(people.into_iter().count(), 1);
579 });
580 }
581
582 #[test]
583 fn with_query_results_intersection() {
584 let mut context = Context::new();
585 let _ = context
586 .add_entity(with!(Person, Age(42), RiskCategory::High))
587 .unwrap();
588 let _ = context
589 .add_entity(with!(Person, Age(42), RiskCategory::Low))
590 .unwrap();
591 let _ = context
592 .add_entity(with!(Person, Age(40), RiskCategory::Low))
593 .unwrap();
594
595 context.with_query_results(with!(Person, Age(42), RiskCategory::High), &mut |people| {
596 assert_eq!(people.into_iter().count(), 1);
597 });
598 }
599
600 #[test]
601 fn with_query_results_intersection_non_macro() {
602 let mut context = Context::new();
603 let _ = context
604 .add_entity(with!(Person, Age(42), RiskCategory::High))
605 .unwrap();
606 let _ = context
607 .add_entity(with!(Person, Age(42), RiskCategory::Low))
608 .unwrap();
609 let _ = context
610 .add_entity(with!(Person, Age(40), RiskCategory::Low))
611 .unwrap();
612
613 context.with_query_results(with!(Person, Age(42), RiskCategory::High), &mut |people| {
614 assert_eq!(people.into_iter().count(), 1);
615 });
616 }
617
618 #[test]
619 fn with_query_results_intersection_one_indexed() {
620 let mut context = Context::new();
621 let _ = context
622 .add_entity(with!(Person, Age(42), RiskCategory::High))
623 .unwrap();
624 let _ = context
625 .add_entity(with!(Person, Age(42), RiskCategory::Low))
626 .unwrap();
627 let _ = context
628 .add_entity(with!(Person, Age(40), RiskCategory::Low))
629 .unwrap();
630
631 context.index_property::<Person, Age>();
632 context.with_query_results(with!(Person, Age(42), RiskCategory::High), &mut |people| {
633 assert_eq!(people.into_iter().count(), 1);
634 });
635 }
636
637 #[test]
638 fn query_derived_prop() {
639 let mut context = Context::new();
640 define_derived_property!(struct Senior(bool), Person, [Age], |age| Senior(age.0 >= 65));
641
642 let person = context
643 .add_entity(with!(Person, Age(64), RiskCategory::High))
644 .unwrap();
645 context
646 .add_entity(with!(Person, Age(88), RiskCategory::High))
647 .unwrap();
648
649 let mut not_seniors = Vec::new();
650 context.with_query_results(with!(Person, Senior(false)), &mut |people| {
651 not_seniors = people.to_owned_vec();
652 });
653 let mut seniors = Vec::new();
654 context.with_query_results(with!(Person, Senior(true)), &mut |people| {
655 seniors = people.to_owned_vec();
656 });
657 assert_eq!(seniors.len(), 1, "One senior");
658 assert_eq!(not_seniors.len(), 1, "One non-senior");
659
660 context.set_property(person, Age(65));
661
662 context.with_query_results(with!(Person, Senior(false)), &mut |people| {
663 not_seniors = people.to_owned_vec()
664 });
665 context.with_query_results(with!(Person, Senior(true)), &mut |people| {
666 seniors = people.to_owned_vec()
667 });
668
669 assert_eq!(seniors.len(), 2, "Two seniors");
670 assert_eq!(not_seniors.len(), 0, "No non-seniors");
671 }
672
673 #[test]
674 fn query_derived_prop_with_index() {
675 let mut context = Context::new();
676 define_derived_property!(struct Senior(bool), Person, [Age], |age| Senior(age.0 >= 65));
677
678 context.index_property::<Person, Senior>();
679 let person = context
680 .add_entity(with!(Person, Age(64), RiskCategory::Low))
681 .unwrap();
682 let _ = context.add_entity(with!(Person, Age(88), RiskCategory::Low));
683
684 let mut not_seniors = Vec::new();
685 context.with_query_results(with!(Person, Senior(false)), &mut |people| {
686 not_seniors = people.to_owned_vec()
687 });
688 let mut seniors = Vec::new();
689 context.with_query_results(with!(Person, Senior(true)), &mut |people| {
690 seniors = people.to_owned_vec()
691 });
692 assert_eq!(seniors.len(), 1, "One senior");
693 assert_eq!(not_seniors.len(), 1, "One non-senior");
694
695 context.set_property(person, Age(65));
696
697 context.with_query_results(with!(Person, Senior(false)), &mut |people| {
698 not_seniors = people.to_owned_vec()
699 });
700 context.with_query_results(with!(Person, Senior(true)), &mut |people| {
701 seniors = people.to_owned_vec()
702 });
703
704 assert_eq!(seniors.len(), 2, "Two seniors");
705 assert_eq!(not_seniors.len(), 0, "No non-seniors");
706 }
707
708 define_multi_property!(Person, (Age, County, Height));
710 define_multi_property!(Person, (County, Height));
711
712 #[test]
713 fn query_derived_prop_with_optimized_index() {
714 let mut context = Context::new();
715 define_derived_property!(
717 struct Ach(u8, u32, u32),
718 Person,
719 [Age, County, Height],
720 [],
721 |age, county, height| Ach(age.0, county.0, height.0)
722 );
723
724 let _ = context.add_entity(with!(
726 Person,
727 Age(64),
728 County(2),
729 Height(120),
730 RiskCategory::Low
731 ));
732 let _ = context.add_entity(with!(
733 Person,
734 Age(88),
735 County(2),
736 Height(130),
737 RiskCategory::Low
738 ));
739 let p2 = context
740 .add_entity(with!(
741 Person,
742 Age(8),
743 County(1),
744 Height(140),
745 RiskCategory::Low
746 ))
747 .unwrap();
748 let p3 = context
749 .add_entity(with!(
750 Person,
751 Age(28),
752 County(1),
753 Height(140),
754 RiskCategory::Low
755 ))
756 .unwrap();
757 let p4 = context
758 .add_entity(with!(
759 Person,
760 Age(28),
761 County(2),
762 Height(160),
763 RiskCategory::Low
764 ))
765 .unwrap();
766 let p5 = context
767 .add_entity(with!(
768 Person,
769 Age(28),
770 County(2),
771 Height(160),
772 RiskCategory::Low
773 ))
774 .unwrap();
775
776 context.with_query_results(with!(Person, Ach(28, 2, 160)), &mut |people| {
778 assert!(people.contains(p4));
779 assert!(people.contains(p5));
780 assert_eq!(people.into_iter().count(), 2, "Should have 2 matches");
781 });
782
783 context.with_query_results(
785 with!(Person, Age(28), County(2), Height(160)),
786 &mut |people| {
787 assert!(people.contains(p4));
788 assert!(people.contains(p5));
789 assert_eq!(people.into_iter().count(), 2, "Should have 2 matches");
790 },
791 );
792
793 context.with_query_results(
795 with!(Person, County(2), Height(160), Age(28)),
796 &mut |people| {
797 assert!(people.contains(p4));
798 assert!(people.contains(p5));
799 assert_eq!(people.into_iter().count(), 2, "Should have 2 matches");
800 },
801 );
802
803 context.with_query_results(
805 with!(Person, Height(160), County(2), Age(28)),
806 &mut |people| {
807 assert!(people.contains(p4));
808 assert!(people.contains(p5));
809 assert_eq!(people.into_iter().count(), 2, "Should have 2 matches");
810 },
811 );
812
813 context.with_query_results(
815 with!(Person, Height(140), County(1), Age(28)),
816 &mut |people| {
817 assert!(people.contains(p3));
818 assert_eq!(people.into_iter().count(), 1, "Should have 1 matches");
819 },
820 );
821
822 context.set_property(p2, Age(28));
823 context.with_query_results(
825 with!(Person, Height(140), County(1), Age(28)),
826 &mut |people| {
827 assert!(people.contains(p2));
828 assert!(people.contains(p3));
829 assert_eq!(people.into_iter().count(), 2, "Should have 2 matches");
830 },
831 );
832
833 context.with_query_results(with!(Person, Height(140), County(1)), &mut |people| {
834 assert!(people.contains(p2));
835 assert!(people.contains(p3));
836 assert_eq!(people.into_iter().count(), 2, "Should have 2 matches");
837 });
838 }
839
840 #[test]
841 fn test_match_entity() {
842 let mut context = Context::new();
843 let person = context
844 .add_entity(with!(
845 Person,
846 Age(28),
847 County(2),
848 Height(160),
849 RiskCategory::Low
850 ))
851 .unwrap();
852 assert!(context.match_entity(person, with!(Person, Age(28), County(2), Height(160))));
853 assert!(!context.match_entity(person, with!(Person, Age(13), County(2), Height(160))));
854 assert!(!context.match_entity(person, with!(Person, Age(28), County(33), Height(160))));
855 assert!(!context.match_entity(person, with!(Person, Age(28), County(2), Height(9))));
856 }
857
858 #[test]
859 fn filter_entities_for_unindexed_query() {
860 let mut context = Context::new();
861 let mut people = Vec::new();
862
863 for idx in 0..10 {
864 let person = context
865 .add_entity(with!(
866 Person,
867 Age(28),
868 County(idx % 2),
869 Height(160),
870 RiskCategory::Low
871 ))
872 .unwrap();
873 people.push(person);
874 }
875
876 context.filter_entities(
877 &mut people,
878 with!(Person, Age(28), County(0), Height(160), RiskCategory::Low),
879 );
880
881 let expected = (0..5)
882 .map(|idx| PersonId::new(idx * 2))
883 .collect::<Vec<PersonId>>();
884 assert_eq!(people, expected);
885 }
886
887 #[test]
888 fn filter_entities_for_indexed_query() {
889 let mut context = Context::new();
890 let mut people = Vec::new();
891
892 context.index_property::<Person, (Age, County)>();
893
894 for idx in 0..10 {
895 let person = context
896 .add_entity(with!(
897 Person,
898 Age(28),
899 County(idx % 2),
900 Height(160),
901 RiskCategory::Low
902 ))
903 .unwrap();
904 people.push(person);
905 }
906
907 context.filter_entities(&mut people, with!(Person, County(0), Age(28)));
908
909 let expected = (0..5)
910 .map(|idx| PersonId::new(idx * 2))
911 .collect::<Vec<PersonId>>();
912 assert_eq!(people, expected);
913 }
914
915 #[test]
916 fn entity_property_tuple_basic() {
917 use super::EntityPropertyTuple;
918
919 let mut context = Context::new();
920 let p1 = context
921 .add_entity(with!(Person, Age(42), RiskCategory::High))
922 .unwrap();
923 let _ = context
924 .add_entity(with!(Person, Age(42), RiskCategory::Low))
925 .unwrap();
926 let _ = context
927 .add_entity(with!(Person, Age(30), RiskCategory::High))
928 .unwrap();
929
930 let query: EntityPropertyTuple<Person, _> =
932 EntityPropertyTuple::new((Age(42), RiskCategory::High));
933
934 context.with_query_results(query, &mut |people| {
935 assert!(people.contains(p1));
936 assert_eq!(people.into_iter().count(), 1);
937 });
938
939 assert!(context.match_entity(p1, query));
941
942 assert_eq!(context.query_entity_count(query), 1);
944 }
945
946 #[test]
947 fn entity_property_tuple_empty_query() {
948 use super::EntityPropertyTuple;
949
950 let mut context = Context::new();
951 let _ = context
952 .add_entity(with!(Person, Age(42), RiskCategory::High))
953 .unwrap();
954 let _ = context
955 .add_entity(with!(Person, Age(30), RiskCategory::Low))
956 .unwrap();
957
958 let query: EntityPropertyTuple<Person, _> = EntityPropertyTuple::new(());
960
961 assert_eq!(context.query_entity_count(query), 2);
962 }
963
964 #[test]
965 fn entity_property_tuple_singleton() {
966 use super::EntityPropertyTuple;
967
968 let mut context = Context::new();
969 let _ = context
970 .add_entity(with!(Person, Age(42), RiskCategory::High))
971 .unwrap();
972 let _ = context
973 .add_entity(with!(Person, Age(42), RiskCategory::Low))
974 .unwrap();
975 let _ = context
976 .add_entity(with!(Person, Age(30), RiskCategory::High))
977 .unwrap();
978
979 let query: EntityPropertyTuple<Person, _> = EntityPropertyTuple::new((Age(42),));
981
982 assert_eq!(context.query_entity_count(query), 2);
983 }
984
985 #[test]
986 fn entity_property_tuple_inner_access() {
987 use super::EntityPropertyTuple;
988
989 let query: EntityPropertyTuple<Person, _> =
990 EntityPropertyTuple::new((Age(42), RiskCategory::High));
991
992 let inner = query.inner();
994 assert_eq!(inner.0, Age(42));
995 assert_eq!(inner.1, RiskCategory::High);
996
997 let (age, risk) = query.into_inner();
999 assert_eq!(age, Age(42));
1000 assert_eq!(risk, RiskCategory::High);
1001 }
1002
1003 #[test]
1004 fn all_macro_no_properties() {
1005 use crate::with;
1006
1007 let mut context = Context::new();
1008 let _ = context
1009 .add_entity(with!(Person, Age(42), RiskCategory::High))
1010 .unwrap();
1011 let _ = context
1012 .add_entity(with!(Person, Age(30), RiskCategory::Low))
1013 .unwrap();
1014
1015 let query = with!(Person);
1017 assert_eq!(context.query_entity_count(query), 2);
1018 }
1019
1020 #[test]
1021 fn all_macro_single_property() {
1022 use crate::with;
1023
1024 let mut context = Context::new();
1025 let _ = context
1026 .add_entity(with!(Person, Age(42), RiskCategory::High))
1027 .unwrap();
1028 let _ = context
1029 .add_entity(with!(Person, Age(42), RiskCategory::Low))
1030 .unwrap();
1031 let _ = context
1032 .add_entity(with!(Person, Age(30), RiskCategory::High))
1033 .unwrap();
1034
1035 let query = with!(Person, Age(42));
1037 assert_eq!(context.query_entity_count(query), 2);
1038 }
1039
1040 #[test]
1041 fn all_macro_multiple_properties() {
1042 use crate::with;
1043
1044 let mut context = Context::new();
1045 let p1 = context
1046 .add_entity(with!(Person, Age(42), RiskCategory::High))
1047 .unwrap();
1048 let _ = context
1049 .add_entity(with!(Person, Age(42), RiskCategory::Low))
1050 .unwrap();
1051 let _ = context
1052 .add_entity(with!(Person, Age(30), RiskCategory::High))
1053 .unwrap();
1054
1055 let query = with!(Person, Age(42), RiskCategory::High);
1057 assert_eq!(context.query_entity_count(query), 1);
1058
1059 context.with_query_results(query, &mut |people| {
1060 assert!(people.contains(p1));
1061 });
1062 }
1063
1064 #[test]
1065 fn all_macro_with_trailing_comma() {
1066 use crate::with;
1067
1068 let mut context = Context::new();
1069 let _ = context
1070 .add_entity(with!(Person, Age(42), RiskCategory::High))
1071 .unwrap();
1072
1073 let query = with!(Person, Age(42));
1075 assert_eq!(context.query_entity_count(query), 1);
1076
1077 let query = with!(Person, Age(42), RiskCategory::High);
1078 assert_eq!(context.query_entity_count(query), 1);
1079 }
1080
1081 #[test]
1082 fn entity_property_tuple_as_property_list() {
1083 use super::EntityPropertyTuple;
1084 use crate::entity::property_list::PropertyList;
1085
1086 assert!(EntityPropertyTuple::<Person, (Age,)>::validate().is_ok());
1088 assert!(EntityPropertyTuple::<Person, (Age, RiskCategory)>::validate().is_ok());
1089
1090 assert!(EntityPropertyTuple::<Person, (Age,)>::contains_properties(
1092 &[Age::type_id()]
1093 ));
1094 assert!(
1095 EntityPropertyTuple::<Person, (Age, RiskCategory)>::contains_properties(&[
1096 Age::type_id()
1097 ])
1098 );
1099 assert!(
1100 EntityPropertyTuple::<Person, (Age, RiskCategory)>::contains_properties(&[
1101 Age::type_id(),
1102 RiskCategory::type_id()
1103 ])
1104 );
1105 }
1106
1107 #[test]
1108 fn all_macro_as_property_list_for_add_entity() {
1109 use crate::with;
1110
1111 let mut context = Context::new();
1112
1113 let props = with!(Person, Age(42), RiskCategory::High);
1115 let person = context.add_entity(props).unwrap();
1116
1117 assert_eq!(context.get_property::<Person, Age>(person), Age(42));
1119 assert_eq!(
1120 context.get_property::<Person, RiskCategory>(person),
1121 RiskCategory::High
1122 );
1123 }
1124}