1mod query_impls;
2
3use std::any::TypeId;
4use std::marker::PhantomData;
5use std::sync::{Mutex, OnceLock};
6
7use crate::entity::entity_set::EntitySetIterator;
8use crate::entity::multi_property::type_ids_to_multi_property_index;
9use crate::entity::property_list::PropertyList;
10use crate::entity::property_store::PropertyStore;
11use crate::entity::{Entity, HashValueType};
12use crate::hashing::HashMap;
13use crate::prelude::EntityId;
14use crate::{Context, IxaError};
15
16pub struct EntityPropertyTuple<E: Entity, T> {
34 inner: T,
35 _marker: PhantomData<E>,
36}
37
38impl<E: Entity, T: Copy> Copy for EntityPropertyTuple<E, T> {}
40
41impl<E: Entity, T: Clone> Clone for EntityPropertyTuple<E, T> {
42 fn clone(&self) -> Self {
43 Self {
44 inner: self.inner.clone(),
45 _marker: PhantomData,
46 }
47 }
48}
49
50impl<E: Entity, T: std::fmt::Debug> std::fmt::Debug for EntityPropertyTuple<E, T> {
51 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 f.debug_struct("EntityPropertyTuple")
53 .field("inner", &self.inner)
54 .finish()
55 }
56}
57
58impl<E: Entity, T> EntityPropertyTuple<E, T> {
59 pub fn new(inner: T) -> Self {
61 Self {
62 inner,
63 _marker: PhantomData,
64 }
65 }
66
67 pub fn inner(&self) -> &T {
69 &self.inner
70 }
71
72 pub fn into_inner(self) -> T {
74 self.inner
75 }
76}
77
78impl<E: Entity, T: Query<E>> Query<E> for EntityPropertyTuple<E, T> {
79 fn get_query(&self) -> Vec<(usize, HashValueType)> {
80 self.inner.get_query()
81 }
82
83 fn get_type_ids(&self) -> Vec<TypeId> {
84 self.inner.get_type_ids()
85 }
86
87 fn multi_property_id(&self) -> Option<usize> {
88 self.inner.multi_property_id()
89 }
90
91 fn multi_property_value_hash(&self) -> HashValueType {
92 self.inner.multi_property_value_hash()
93 }
94
95 fn new_query_result_iterator<'c>(&self, context: &'c Context) -> EntitySetIterator<'c, E> {
96 self.inner.new_query_result_iterator(context)
97 }
98
99 fn match_entity(&self, entity_id: EntityId<E>, context: &Context) -> bool {
100 self.inner.match_entity(entity_id, context)
101 }
102
103 fn filter_entities(&self, entities: &mut Vec<EntityId<E>>, context: &Context) {
104 self.inner.filter_entities(entities, context)
105 }
106}
107
108impl<E: Entity, T: PropertyList<E>> PropertyList<E> for EntityPropertyTuple<E, T> {
109 fn validate() -> Result<(), IxaError> {
110 T::validate()
111 }
112
113 fn contains_properties(property_type_ids: &[TypeId]) -> bool {
114 T::contains_properties(property_type_ids)
115 }
116
117 fn set_values_for_entity(&self, entity_id: EntityId<E>, property_store: &PropertyStore<E>) {
118 self.inner.set_values_for_entity(entity_id, property_store)
119 }
120}
121
122pub trait Query<E: Entity>: Copy + 'static {
129 fn get_query(&self) -> Vec<(usize, HashValueType)>;
132
133 fn get_type_ids(&self) -> Vec<TypeId>;
135
136 fn multi_property_id(&self) -> Option<usize> {
138 static REGISTRY: OnceLock<Mutex<HashMap<TypeId, &'static Option<usize>>>> = OnceLock::new();
141
142 let map = REGISTRY.get_or_init(|| Mutex::new(HashMap::default()));
143 let mut map = map.lock().unwrap();
144 let type_id = TypeId::of::<Self>();
145 let entry = *map.entry(type_id).or_insert_with(|| {
146 let mut types = self.get_type_ids();
147 types.sort_unstable();
148 Box::leak(Box::new(type_ids_to_multi_property_index(types.as_slice())))
149 });
150
151 *entry
152 }
153
154 fn multi_property_value_hash(&self) -> HashValueType;
157
158 fn new_query_result_iterator<'c>(&self, context: &'c Context) -> EntitySetIterator<'c, E>;
160
161 fn match_entity(&self, entity_id: EntityId<E>, context: &Context) -> bool;
163
164 fn filter_entities(&self, entities: &mut Vec<EntityId<E>>, context: &Context);
166}
167
168#[cfg(test)]
169mod tests {
170
171 use crate::hashing::HashSetExt;
172 use crate::prelude::*;
173 use crate::{
174 define_derived_property, define_entity, define_multi_property, define_property, Context,
175 };
176
177 define_entity!(Person);
178
179 define_property!(struct Age(u8), Person, default_const = Age(0));
180 define_property!(struct County(u32), Person, default_const = County(0));
181 define_property!(struct Height(u32), Person, default_const = Height(0));
182 define_property!(
183 enum RiskCategory {
184 High,
185 Low,
186 },
187 Person
188 );
189
190 define_multi_property!((Age, County), Person);
191
192 #[test]
193 fn with_query_results() {
194 let mut context = Context::new();
195 let _ = context.add_entity((RiskCategory::High,)).unwrap();
196
197 context.with_query_results((RiskCategory::High,), &mut |people| {
198 assert_eq!(people.len(), 1);
199 });
200 }
201
202 #[test]
203 fn with_query_results_empty() {
204 let context = Context::new();
205
206 context.with_query_results((RiskCategory::High,), &mut |people| {
207 assert_eq!(people.len(), 0);
208 });
209 }
210
211 #[test]
212 fn query_entity_count() {
213 let mut context = Context::new();
214 let _ = context.add_entity((RiskCategory::High,)).unwrap();
215
216 assert_eq!(context.query_entity_count((RiskCategory::High,)), 1);
217 }
218
219 #[test]
220 fn query_entity_count_empty() {
221 let context = Context::new();
222
223 assert_eq!(context.query_entity_count((RiskCategory::High,)), 0);
224 }
225
226 #[test]
227 fn with_query_results_macro_index_first() {
228 let mut context = Context::new();
229 let _ = context.add_entity((RiskCategory::High,)).unwrap();
230 context.index_property::<_, RiskCategory>();
231 assert!(context.is_property_indexed::<Person, RiskCategory>());
232
233 context.with_query_results((RiskCategory::High,), &mut |people| {
234 assert_eq!(people.len(), 1);
235 });
236 }
237
238 #[test]
239 fn with_query_results_macro_index_second() {
240 let mut context = Context::new();
241 let _ = context.add_entity((RiskCategory::High,));
242
243 context.with_query_results((RiskCategory::High,), &mut |people| {
244 assert_eq!(people.len(), 1);
245 });
246 assert!(!context.is_property_indexed::<Person, RiskCategory>());
247
248 context.index_property::<Person, RiskCategory>();
249 assert!(context.is_property_indexed::<Person, RiskCategory>());
250
251 context.with_query_results((RiskCategory::High,), &mut |people| {
252 assert_eq!(people.len(), 1);
253 });
254 }
255
256 #[test]
257 fn with_query_results_macro_change() {
258 let mut context = Context::new();
259 let person1 = context.add_entity((RiskCategory::High,)).unwrap();
260
261 context.with_query_results((RiskCategory::High,), &mut |people| {
262 assert_eq!(people.len(), 1);
263 });
264
265 context.with_query_results((RiskCategory::Low,), &mut |people| {
266 assert_eq!(people.len(), 0);
267 });
268
269 context.set_property(person1, RiskCategory::Low);
270 context.with_query_results((RiskCategory::High,), &mut |people| {
271 assert_eq!(people.len(), 0);
272 });
273
274 context.with_query_results((RiskCategory::Low,), &mut |people| {
275 assert_eq!(people.len(), 1);
276 });
277 }
278
279 #[test]
280 fn with_query_results_index_after_add() {
281 let mut context = Context::new();
282 let _ = context.add_entity((RiskCategory::High,)).unwrap();
283 context.index_property::<Person, RiskCategory>();
284 assert!(context.is_property_indexed::<Person, RiskCategory>());
285 context.with_query_results((RiskCategory::High,), &mut |people| {
286 assert_eq!(people.len(), 1);
287 });
288 }
289
290 #[test]
291 fn with_query_results_add_after_index() {
292 let mut context = Context::new();
293 let _ = context.add_entity((RiskCategory::High,)).unwrap();
294 context.index_property::<Person, RiskCategory>();
295 assert!(context.is_property_indexed::<Person, RiskCategory>());
296 context.with_query_results((RiskCategory::High,), &mut |people| {
297 assert_eq!(people.len(), 1);
298 });
299
300 let _ = context.add_entity((RiskCategory::High,)).unwrap();
301 context.with_query_results((RiskCategory::High,), &mut |people| {
302 assert_eq!(people.len(), 2);
303 });
304 }
305
306 #[test]
307 fn with_query_results_cast_value() {
308 let mut context = Context::new();
309 let _ = context.add_entity((Age(42), RiskCategory::High)).unwrap();
310
311 context.with_query_results((Age(42),), &mut |people| {
312 assert_eq!(people.len(), 1);
313 });
314 }
315
316 #[test]
317 fn with_query_results_intersection() {
318 let mut context = Context::new();
319 let _ = context.add_entity((Age(42), RiskCategory::High)).unwrap();
320 let _ = context.add_entity((Age(42), RiskCategory::Low)).unwrap();
321 let _ = context.add_entity((Age(40), RiskCategory::Low)).unwrap();
322
323 context.with_query_results((Age(42), RiskCategory::High), &mut |people| {
324 assert_eq!(people.len(), 1);
325 });
326 }
327
328 #[test]
329 fn with_query_results_intersection_non_macro() {
330 let mut context = Context::new();
331 let _ = context.add_entity((Age(42), RiskCategory::High)).unwrap();
332 let _ = context.add_entity((Age(42), RiskCategory::Low)).unwrap();
333 let _ = context.add_entity((Age(40), RiskCategory::Low)).unwrap();
334
335 context.with_query_results((Age(42), RiskCategory::High), &mut |people| {
336 assert_eq!(people.len(), 1);
337 });
338 }
339
340 #[test]
341 fn with_query_results_intersection_one_indexed() {
342 let mut context = Context::new();
343 let _ = context.add_entity((Age(42), RiskCategory::High)).unwrap();
344 let _ = context.add_entity((Age(42), RiskCategory::Low)).unwrap();
345 let _ = context.add_entity((Age(40), RiskCategory::Low)).unwrap();
346
347 context.index_property::<Person, Age>();
348 context.with_query_results((Age(42), RiskCategory::High), &mut |people| {
349 assert_eq!(people.len(), 1);
350 });
351 }
352
353 #[test]
354 fn query_derived_prop() {
355 let mut context = Context::new();
356 define_derived_property!(struct Senior(bool), Person, [Age], |age| Senior(age.0 >= 65));
357
358 let person = context.add_entity((Age(64), RiskCategory::High)).unwrap();
359 context.add_entity((Age(88), RiskCategory::High)).unwrap();
360
361 let mut not_seniors = Vec::new();
362 context.with_query_results((Senior(false),), &mut |people| {
363 not_seniors = people.to_owned_vec();
364 });
365 let mut seniors = Vec::new();
366 context.with_query_results((Senior(true),), &mut |people| {
367 seniors = people.to_owned_vec();
368 });
369 assert_eq!(seniors.len(), 1, "One senior");
370 assert_eq!(not_seniors.len(), 1, "One non-senior");
371
372 context.set_property(person, Age(65));
373
374 context.with_query_results((Senior(false),), &mut |people| {
375 not_seniors = people.to_owned_vec()
376 });
377 context.with_query_results((Senior(true),), &mut |people| {
378 seniors = people.to_owned_vec()
379 });
380
381 assert_eq!(seniors.len(), 2, "Two seniors");
382 assert_eq!(not_seniors.len(), 0, "No non-seniors");
383 }
384
385 #[test]
386 fn query_derived_prop_with_index() {
387 let mut context = Context::new();
388 define_derived_property!(struct Senior(bool), Person, [Age], |age| Senior(age.0 >= 65));
389
390 context.index_property::<Person, Senior>();
391 let person = context.add_entity((Age(64), RiskCategory::Low)).unwrap();
392 let _ = context.add_entity((Age(88), RiskCategory::Low));
393
394 let mut not_seniors = Vec::new();
395 context.with_query_results((Senior(false),), &mut |people| {
396 not_seniors = people.to_owned_vec()
397 });
398 let mut seniors = Vec::new();
399 context.with_query_results((Senior(true),), &mut |people| {
400 seniors = people.to_owned_vec()
401 });
402 assert_eq!(seniors.len(), 1, "One senior");
403 assert_eq!(not_seniors.len(), 1, "One non-senior");
404
405 context.set_property(person, Age(65));
406
407 context.with_query_results((Senior(false),), &mut |people| {
408 not_seniors = people.to_owned_vec()
409 });
410 context.with_query_results((Senior(true),), &mut |people| {
411 seniors = people.to_owned_vec()
412 });
413
414 assert_eq!(seniors.len(), 2, "Two seniors");
415 assert_eq!(not_seniors.len(), 0, "No non-seniors");
416 }
417
418 define_multi_property!((Age, County, Height), Person);
420 define_multi_property!((County, Height), Person);
421
422 #[test]
423 fn query_derived_prop_with_optimized_index() {
424 let mut context = Context::new();
425 define_derived_property!(
427 struct Ach(u8, u32, u32),
428 Person,
429 [Age, County, Height],
430 [],
431 |age, county, height| Ach(age.0, county.0, height.0)
432 );
433
434 let _ = context.add_entity((Age(64), County(2), Height(120), RiskCategory::Low));
436 let _ = context.add_entity((Age(88), County(2), Height(130), RiskCategory::Low));
437 let p2 = context
438 .add_entity((Age(8), County(1), Height(140), RiskCategory::Low))
439 .unwrap();
440 let p3 = context
441 .add_entity((Age(28), County(1), Height(140), RiskCategory::Low))
442 .unwrap();
443 let p4 = context
444 .add_entity((Age(28), County(2), Height(160), RiskCategory::Low))
445 .unwrap();
446 let p5 = context
447 .add_entity((Age(28), County(2), Height(160), RiskCategory::Low))
448 .unwrap();
449
450 context.with_query_results((Ach(28, 2, 160),), &mut |people| {
452 assert_eq!(people.len(), 2, "Should have 2 matches");
453 assert!(people.contains(&p4));
454 assert!(people.contains(&p5));
455 });
456
457 context.with_query_results((Age(28), County(2), Height(160)), &mut |people| {
459 assert_eq!(people.len(), 2, "Should have 2 matches");
460 assert!(people.contains(&p4));
461 assert!(people.contains(&p5));
462 });
463
464 context.with_query_results((County(2), Height(160), Age(28)), &mut |people| {
466 assert_eq!(people.len(), 2, "Should have 2 matches");
467 assert!(people.contains(&p4));
468 assert!(people.contains(&p5));
469 });
470
471 context.with_query_results((Height(160), County(2), Age(28)), &mut |people| {
473 assert_eq!(people.len(), 2, "Should have 2 matches");
474 assert!(people.contains(&p4));
475 assert!(people.contains(&p5));
476 });
477
478 context.with_query_results((Height(140), County(1), Age(28)), &mut |people| {
480 assert_eq!(people.len(), 1, "Should have 1 matches");
481 assert!(people.contains(&p3));
482 });
483
484 context.set_property(p2, Age(28));
485 context.with_query_results((Height(140), County(1), Age(28)), &mut |people| {
487 assert_eq!(people.len(), 2, "Should have 2 matches");
488 assert!(people.contains(&p2));
489 assert!(people.contains(&p3));
490 });
491
492 context.with_query_results((Height(140), County(1)), &mut |people| {
493 assert_eq!(people.len(), 2, "Should have 2 matches");
494 assert!(people.contains(&p2));
495 assert!(people.contains(&p3));
496 });
497 }
498
499 #[test]
500 fn test_match_entity() {
501 let mut context = Context::new();
502 let person = context
503 .add_entity((Age(28), County(2), Height(160), RiskCategory::Low))
504 .unwrap();
505 assert!(context.match_entity(person, (Age(28), County(2), Height(160))));
506 assert!(!context.match_entity(person, (Age(13), County(2), Height(160))));
507 assert!(!context.match_entity(person, (Age(28), County(33), Height(160))));
508 assert!(!context.match_entity(person, (Age(28), County(2), Height(9))));
509 }
510
511 #[test]
512 fn filter_entities_for_unindexed_query() {
513 let mut context = Context::new();
514 let mut people = Vec::new();
515
516 for idx in 0..10 {
517 let person = context
518 .add_entity((Age(28), County(idx % 2), Height(160), RiskCategory::Low))
519 .unwrap();
520 people.push(person);
521 }
522
523 context.filter_entities(
524 &mut people,
525 (Age(28), County(0), Height(160), RiskCategory::Low),
526 );
527
528 let expected = (0..5)
529 .map(|idx| PersonId::new(idx * 2))
530 .collect::<Vec<PersonId>>();
531 assert_eq!(people, expected);
532 }
533
534 #[test]
535 fn filter_entities_for_indexed_query() {
536 let mut context = Context::new();
537 let mut people = Vec::new();
538
539 context.index_property::<Person, (Age, County)>();
540
541 for idx in 0..10 {
542 let person = context
543 .add_entity((Age(28), County(idx % 2), Height(160), RiskCategory::Low))
544 .unwrap();
545 people.push(person);
546 }
547
548 context.filter_entities(&mut people, (County(0), Age(28)));
549
550 let expected = (0..5)
551 .map(|idx| PersonId::new(idx * 2))
552 .collect::<Vec<PersonId>>();
553 assert_eq!(people, expected);
554 }
555
556 #[test]
557 fn entity_property_tuple_basic() {
558 use super::EntityPropertyTuple;
559
560 let mut context = Context::new();
561 let p1 = context.add_entity((Age(42), RiskCategory::High)).unwrap();
562 let _ = context.add_entity((Age(42), RiskCategory::Low)).unwrap();
563 let _ = context.add_entity((Age(30), RiskCategory::High)).unwrap();
564
565 let query: EntityPropertyTuple<Person, _> =
567 EntityPropertyTuple::new((Age(42), RiskCategory::High));
568
569 context.with_query_results(query, &mut |people| {
570 assert_eq!(people.len(), 1);
571 assert!(people.contains(&p1));
572 });
573
574 assert!(context.match_entity(p1, query));
576
577 assert_eq!(context.query_entity_count(query), 1);
579 }
580
581 #[test]
582 fn entity_property_tuple_empty_query() {
583 use super::EntityPropertyTuple;
584
585 let mut context = Context::new();
586 let _ = context.add_entity((Age(42), RiskCategory::High)).unwrap();
587 let _ = context.add_entity((Age(30), RiskCategory::Low)).unwrap();
588
589 let query: EntityPropertyTuple<Person, _> = EntityPropertyTuple::new(());
591
592 assert_eq!(context.query_entity_count(query), 2);
593 }
594
595 #[test]
596 fn entity_property_tuple_singleton() {
597 use super::EntityPropertyTuple;
598
599 let mut context = Context::new();
600 let _ = context.add_entity((Age(42), RiskCategory::High)).unwrap();
601 let _ = context.add_entity((Age(42), RiskCategory::Low)).unwrap();
602 let _ = context.add_entity((Age(30), RiskCategory::High)).unwrap();
603
604 let query: EntityPropertyTuple<Person, _> = EntityPropertyTuple::new((Age(42),));
606
607 assert_eq!(context.query_entity_count(query), 2);
608 }
609
610 #[test]
611 fn entity_property_tuple_inner_access() {
612 use super::EntityPropertyTuple;
613
614 let query: EntityPropertyTuple<Person, _> =
615 EntityPropertyTuple::new((Age(42), RiskCategory::High));
616
617 let inner = query.inner();
619 assert_eq!(inner.0, Age(42));
620 assert_eq!(inner.1, RiskCategory::High);
621
622 let (age, risk) = query.into_inner();
624 assert_eq!(age, Age(42));
625 assert_eq!(risk, RiskCategory::High);
626 }
627
628 #[test]
629 fn all_macro_no_properties() {
630 use crate::all;
631
632 let mut context = Context::new();
633 let _ = context.add_entity((Age(42), RiskCategory::High)).unwrap();
634 let _ = context.add_entity((Age(30), RiskCategory::Low)).unwrap();
635
636 let query = all!(Person);
638 assert_eq!(context.query_entity_count(query), 2);
639 }
640
641 #[test]
642 fn all_macro_single_property() {
643 use crate::all;
644
645 let mut context = Context::new();
646 let _ = context.add_entity((Age(42), RiskCategory::High)).unwrap();
647 let _ = context.add_entity((Age(42), RiskCategory::Low)).unwrap();
648 let _ = context.add_entity((Age(30), RiskCategory::High)).unwrap();
649
650 let query = all!(Person, Age(42));
652 assert_eq!(context.query_entity_count(query), 2);
653 }
654
655 #[test]
656 fn all_macro_multiple_properties() {
657 use crate::all;
658
659 let mut context = Context::new();
660 let p1 = context.add_entity((Age(42), RiskCategory::High)).unwrap();
661 let _ = context.add_entity((Age(42), RiskCategory::Low)).unwrap();
662 let _ = context.add_entity((Age(30), RiskCategory::High)).unwrap();
663
664 let query = all!(Person, Age(42), RiskCategory::High);
666 assert_eq!(context.query_entity_count(query), 1);
667
668 context.with_query_results(query, &mut |people| {
669 assert!(people.contains(&p1));
670 });
671 }
672
673 #[test]
674 fn all_macro_with_trailing_comma() {
675 use crate::all;
676
677 let mut context = Context::new();
678 let _ = context.add_entity((Age(42), RiskCategory::High)).unwrap();
679
680 let query = all!(Person, Age(42),);
682 assert_eq!(context.query_entity_count(query), 1);
683
684 let query = all!(Person, Age(42), RiskCategory::High,);
685 assert_eq!(context.query_entity_count(query), 1);
686 }
687
688 #[test]
689 fn entity_property_tuple_as_property_list() {
690 use super::EntityPropertyTuple;
691 use crate::entity::property_list::PropertyList;
692
693 assert!(EntityPropertyTuple::<Person, (Age,)>::validate().is_ok());
695 assert!(EntityPropertyTuple::<Person, (Age, RiskCategory)>::validate().is_ok());
696
697 assert!(EntityPropertyTuple::<Person, (Age,)>::contains_properties(
699 &[Age::type_id()]
700 ));
701 assert!(
702 EntityPropertyTuple::<Person, (Age, RiskCategory)>::contains_properties(&[
703 Age::type_id()
704 ])
705 );
706 assert!(
707 EntityPropertyTuple::<Person, (Age, RiskCategory)>::contains_properties(&[
708 Age::type_id(),
709 RiskCategory::type_id()
710 ])
711 );
712 }
713
714 #[test]
715 fn all_macro_as_property_list_for_add_entity() {
716 use crate::all;
717
718 let mut context = Context::new();
719
720 let props = all!(Person, Age(42), RiskCategory::High);
722 let person = context.add_entity(props).unwrap();
723
724 assert_eq!(context.get_property::<Person, Age>(person), Age(42));
726 assert_eq!(
727 context.get_property::<Person, RiskCategory>(person),
728 RiskCategory::High
729 );
730 }
731}