Skip to main content

ixa/entity/query/
mod.rs

1mod query_impls;
2
3use std::any::TypeId;
4use std::marker::PhantomData;
5use std::sync::{Mutex, OnceLock};
6
7use crate::data_structures::bit_set::BitSet;
8use crate::entity::entity_set::{EntitySet, EntitySetIterator};
9use crate::entity::multi_property::type_ids_to_multi_property_id;
10#[cfg(feature = "profiling")]
11use crate::entity::multi_property::{intern_query_identity, QueryIdentityId};
12use crate::entity::property_list::{PropertyInitializationList, PropertyList};
13use crate::entity::property_store::PropertyStore;
14use crate::entity::Entity;
15use crate::hashing::HashMap;
16use crate::prelude::EntityId;
17use crate::{Context, IxaError};
18
19/// A newtype wrapper that associates a tuple of property values with an entity type.
20///
21/// This is not meant to be used directly, but rather as a backing for the with! macro/
22/// a replacement for the query tuple.
23///
24/// # Example
25/// ```ignore
26/// use ixa::{define_entity, define_property, with};
27///
28/// define_entity!(Person);
29/// define_property!(struct Age(u8), Person, default_const = Age(0));
30///
31/// // Build a query for people with Age(42).
32/// let query = with!(Person, Age(42));
33/// ```
34pub struct EntityPropertyTuple<E: Entity, T> {
35    inner: T,
36    _marker: PhantomData<E>,
37}
38
39// Manual implementations to avoid requiring E: Copy/Clone
40impl<E: Entity, T: Copy> Copy for EntityPropertyTuple<E, T> {}
41
42impl<E: Entity, T: Clone> Clone for EntityPropertyTuple<E, T> {
43    fn clone(&self) -> Self {
44        Self {
45            inner: self.inner.clone(),
46            _marker: PhantomData,
47        }
48    }
49}
50
51impl<E: Entity, T: std::fmt::Debug> std::fmt::Debug for EntityPropertyTuple<E, T> {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        f.debug_struct("EntityPropertyTuple")
54            .field("inner", &self.inner)
55            .finish()
56    }
57}
58
59impl<E: Entity, T> EntityPropertyTuple<E, T> {
60    /// Create a new `EntityPropertyTuple` wrapping the given tuple.
61    pub fn new(inner: T) -> Self {
62        Self {
63            inner,
64            _marker: PhantomData,
65        }
66    }
67
68    /// Returns a reference to the inner tuple.
69    pub fn inner(&self) -> &T {
70        &self.inner
71    }
72
73    /// Consumes self and returns the inner tuple.
74    pub fn into_inner(self) -> T {
75        self.inner
76    }
77}
78
79impl<E: Entity, T: QueryInternal<E>> QueryInternal<E> for EntityPropertyTuple<E, T> {
80    type QueryParts<'a>
81        = T::QueryParts<'a>
82    where
83        Self: 'a;
84
85    fn get_type_ids(&self) -> Vec<TypeId> {
86        self.inner.get_type_ids()
87    }
88
89    fn resolved_query(&self) -> ResolvedQuery {
90        self.inner.resolved_query()
91    }
92
93    fn is_empty_query(&self) -> bool {
94        self.inner.is_empty_query()
95    }
96
97    fn query_parts(&self) -> Self::QueryParts<'_> {
98        self.inner.query_parts()
99    }
100
101    fn new_query_result<'c>(&self, context: &'c Context) -> EntitySet<'c, E> {
102        self.inner.new_query_result(context)
103    }
104
105    fn match_entity(&self, entity_id: EntityId<E>, context: &Context) -> bool {
106        self.inner.match_entity(entity_id, context)
107    }
108
109    fn filter_entities(&self, entities: &mut Vec<EntityId<E>>, context: &Context) {
110        self.inner.filter_entities(entities, context)
111    }
112}
113
114impl<E: Entity, T: PropertyList<E>> PropertyList<E> for EntityPropertyTuple<E, T> {
115    fn validate() -> Result<(), IxaError> {
116        T::validate()
117    }
118
119    fn contains_properties(property_type_ids: &[TypeId]) -> bool {
120        T::contains_properties(property_type_ids)
121    }
122
123    fn set_values_for_new_entity(
124        &self,
125        entity_id: EntityId<E>,
126        property_store: &mut PropertyStore<E>,
127    ) {
128        let tuple = *self;
129        tuple
130            .into_inner()
131            .set_values_for_new_entity(entity_id, property_store)
132    }
133
134    fn emit_initialized_events(
135        &self,
136        context: &mut Context,
137        entity_id: EntityId<E>,
138        subscriptions: &BitSet,
139    ) {
140        self.inner
141            .emit_initialized_events(context, entity_id, subscriptions);
142    }
143
144    fn get_values_for_entity(context: &Context, entity_id: EntityId<E>) -> Self {
145        EntityPropertyTuple::new(T::get_values_for_entity(context, entity_id))
146    }
147}
148
149impl<E: Entity, PL: PropertyList<E>> PropertyInitializationList<E> for EntityPropertyTuple<E, PL> {}
150
151/// Cached index and profiling resolution for one concrete query type.
152#[derive(Clone, Copy, Debug, Eq, PartialEq)]
153#[doc(hidden)]
154pub struct ResolvedQuery {
155    pub(crate) index_property_id: Option<usize>,
156    #[cfg(feature = "profiling")]
157    pub(crate) identity: QueryIdentityId,
158}
159
160/// Internal query machinery.
161pub trait QueryInternal<E: Entity>: 'static {
162    /// Allocation-free representation of the query parts exposed by this query.
163    type QueryParts<'a>: AsRef<[&'a dyn std::any::Any]>
164    where
165        Self: 'a;
166
167    /// Returns an unordered list of registered logical property type IDs for
168    /// this query.
169    #[must_use]
170    fn get_type_ids(&self) -> Vec<TypeId>;
171
172    /// Resolves the index property for an already sorted query shape without
173    /// consulting the concrete-query cache.
174    fn resolve_index_property_id(&self, type_ids: &[TypeId]) -> Option<usize> {
175        type_ids_to_multi_property_id(E::id(), type_ids)
176    }
177
178    /// Returns the cached index and profiling resolution for this query shape.
179    #[must_use]
180    fn resolved_query(&self) -> ResolvedQuery
181    where
182        Self: Sized,
183    {
184        static REGISTRY: OnceLock<Mutex<HashMap<(usize, TypeId), ResolvedQuery>>> = OnceLock::new();
185
186        let map = REGISTRY.get_or_init(|| Mutex::new(HashMap::default()));
187        let key = (E::id(), TypeId::of::<Self>());
188        if let Some(resolved) = map.lock().unwrap().get(&key).copied() {
189            return resolved;
190        }
191
192        let mut type_ids = self.get_type_ids();
193        type_ids.sort_unstable();
194        let resolved = ResolvedQuery {
195            index_property_id: self.resolve_index_property_id(&type_ids),
196            #[cfg(feature = "profiling")]
197            identity: intern_query_identity::<E>(&type_ids),
198        };
199
200        *map.lock().unwrap().entry(key).or_insert(resolved)
201    }
202
203    /// Returns the property ID of the representative multi-property having the properties of
204    /// this query, if any.
205    #[must_use]
206    fn multi_property_id(&self) -> Option<usize>
207    where
208        Self: Sized,
209    {
210        self.resolved_query().index_property_id
211    }
212
213    /// Indicates whether this query matches the entire population for `E`.
214    #[must_use]
215    fn is_empty_query(&self) -> bool {
216        false
217    }
218
219    /// Exposes the query parts without allocating.
220    #[must_use]
221    fn query_parts(&self) -> Self::QueryParts<'_>;
222
223    /// Creates a new query result as an `EntitySet`.
224    #[must_use]
225    fn new_query_result<'c>(&self, context: &'c Context) -> EntitySet<'c, E>;
226
227    /// Creates a new `EntitySetIterator`.
228    #[must_use]
229    fn new_query_result_iterator<'c>(&self, context: &'c Context) -> EntitySetIterator<'c, E> {
230        self.new_query_result(context).into_iter()
231    }
232
233    /// Determines if the given person matches this query.
234    #[must_use]
235    fn match_entity(&self, entity_id: EntityId<E>, context: &Context) -> bool;
236
237    /// Removes all `EntityId`s from the given vector that do not match this query.
238    fn filter_entities(&self, entities: &mut Vec<EntityId<E>>, context: &Context);
239}
240
241/// Values accepted by user-facing query APIs such as
242/// [`ContextEntitiesExt::query`](crate::entity::context_extension::ContextEntitiesExt::query)
243/// and
244/// [`ContextEntitiesExt::sample_entity`](crate::entity::context_extension::ContextEntitiesExt::sample_entity).
245///
246/// Use [`with!`](crate::with) to query for specific property values, or pass the entity type
247/// directly to work with the entire population.
248pub trait Query<E: Entity>: QueryInternal<E> {}
249
250impl<E: Entity, QI: QueryInternal<E>> Query<E> for EntityPropertyTuple<E, QI> {}
251impl<E: Entity> Query<E> for E {}
252
253#[cfg(test)]
254mod tests {
255    use super::QueryInternal;
256    #[cfg(feature = "profiling")]
257    use super::{EntityId, EntityPropertyTuple, EntitySet};
258    #[cfg(feature = "profiling")]
259    use crate::entity::multi_property::query_identity_label;
260    use crate::prelude::*;
261    use crate::{
262        define_derived_property, define_entity, define_multi_property, define_property, Context,
263    };
264
265    define_entity!(Person);
266    #[cfg(feature = "profiling")]
267    define_entity!(Case);
268
269    define_property!(struct Age(u8), Person, default_const = Age(0));
270    define_property!(struct County(u32), Person, default_const = County(0));
271    define_property!(struct Height(u32), Person, default_const = Height(0));
272    define_property!(
273        enum RiskCategory {
274            High,
275            Low,
276        },
277        Person
278    );
279
280    define_multi_property!(Person, (Age, County));
281
282    #[test]
283    fn empty_tuple_query_internal_matches_all_entities() {
284        let mut context = Context::new();
285        let person1 = context
286            .add_entity(with!(Person, Age(42), RiskCategory::High))
287            .unwrap();
288        let person2 = context
289            .add_entity(with!(Person, Age(30), RiskCategory::Low))
290            .unwrap();
291
292        assert_eq!(<() as QueryInternal<Person>>::get_type_ids(&()), Vec::new());
293        assert!(<() as QueryInternal<Person>>::is_empty_query(&()));
294        assert!(<() as QueryInternal<Person>>::query_parts(&())
295            .as_ref()
296            .is_empty());
297
298        let people = <() as QueryInternal<Person>>::new_query_result_iterator(&(), &context)
299            .collect::<Vec<_>>();
300        assert_eq!(people, vec![person1, person2]);
301        assert!(<() as QueryInternal<Person>>::match_entity(
302            &(),
303            person1,
304            &context
305        ));
306
307        let mut ids = vec![person1, person2];
308        <() as QueryInternal<Person>>::filter_entities(&(), &mut ids, &context);
309        assert_eq!(ids, vec![person1, person2]);
310    }
311
312    #[test]
313    fn entity_query_internal_has_no_type_ids() {
314        let mut context = Context::new();
315        let person1 = context
316            .add_entity(with!(Person, Age(42), RiskCategory::High))
317            .unwrap();
318        let person2 = context
319            .add_entity(with!(Person, Age(30), RiskCategory::Low))
320            .unwrap();
321
322        assert_eq!(
323            <Person as QueryInternal<Person>>::get_type_ids(&Person),
324            Vec::new()
325        );
326        assert_eq!(
327            <Person as QueryInternal<Person>>::multi_property_id(&Person),
328            None
329        );
330        assert!(<Person as QueryInternal<Person>>::query_parts(&Person)
331            .as_ref()
332            .is_empty());
333
334        let people = <Person as QueryInternal<Person>>::new_query_result(&Person, &context)
335            .into_iter()
336            .collect::<Vec<_>>();
337        assert_eq!(people, vec![person1, person2]);
338        assert!(<Person as QueryInternal<Person>>::match_entity(
339            &Person, person1, &context
340        ));
341
342        let mut ids = vec![person1, person2];
343        <Person as QueryInternal<Person>>::filter_entities(&Person, &mut ids, &context);
344        assert_eq!(ids, vec![person1, person2]);
345    }
346
347    #[cfg(feature = "profiling")]
348    #[test]
349    fn empty_queries_share_entity_all_query_identity() {
350        let empty = <() as QueryInternal<Person>>::resolved_query(&());
351        let entity = <Person as QueryInternal<Person>>::resolved_query(&Person);
352
353        assert_eq!(empty.index_property_id, None);
354        assert_eq!(entity.index_property_id, None);
355        assert_eq!(empty.identity, entity.identity);
356        assert_eq!(query_identity_label(empty.identity), "Person: All");
357        assert_eq!(
358            <() as QueryInternal<Person>>::resolved_query(&()).identity,
359            empty.identity
360        );
361    }
362
363    #[cfg(feature = "profiling")]
364    #[test]
365    fn entity_type_is_part_of_query_identity() {
366        let person = <Person as QueryInternal<Person>>::resolved_query(&Person);
367        let case = <Case as QueryInternal<Case>>::resolved_query(&Case);
368
369        assert_ne!(person.identity, case.identity);
370    }
371
372    #[test]
373    fn singleton_query_resolves_direct_property_id() {
374        let query = (Age(42),);
375        let resolved = <(Age,) as QueryInternal<Person>>::resolved_query(&query);
376
377        assert_eq!(resolved.index_property_id, Some(Age::id()));
378
379        #[cfg(feature = "profiling")]
380        {
381            assert_eq!(
382                resolved.identity,
383                <(Age,) as QueryInternal<Person>>::resolved_query(&(Age(7),)).identity
384            );
385            assert_eq!(query_identity_label(resolved.identity), "Person: (Age)");
386        }
387    }
388
389    #[cfg(feature = "profiling")]
390    #[test]
391    fn multi_property_query_identity_ignores_order_and_values() {
392        let query = (Age(42), County(1));
393        let resolved = <(Age, County) as QueryInternal<Person>>::resolved_query(&query);
394
395        assert_eq!(
396            query_identity_label(resolved.identity),
397            "Person: (Age, County)"
398        );
399
400        let reversed_query = (County(2), Age(20));
401        let reversed = <(County, Age) as QueryInternal<Person>>::resolved_query(&reversed_query);
402        assert_eq!(resolved.identity, reversed.identity);
403        assert_eq!(resolved.index_property_id, reversed.index_property_id);
404    }
405
406    #[cfg(feature = "profiling")]
407    #[test]
408    fn unregistered_multi_property_shape_still_has_an_identity() {
409        let query = (Age(42), Height(70));
410        let resolved = <(Age, Height) as QueryInternal<Person>>::resolved_query(&query);
411
412        assert_eq!(resolved.index_property_id, None);
413        assert_eq!(
414            query_identity_label(resolved.identity),
415            "Person: (Age, Height)"
416        );
417    }
418
419    #[cfg(feature = "profiling")]
420    #[test]
421    fn entity_property_tuple_delegates_complete_query_resolution() {
422        let query = EntityPropertyTuple::<Person, _>::new((Age(42), County(1)));
423        let inner = (Age(20), County(2));
424
425        assert_eq!(query.resolved_query(), inner.resolved_query());
426    }
427
428    #[cfg(feature = "profiling")]
429    #[test]
430    fn custom_query_internal_impl_can_use_default_profile_names() {
431        use std::any::{Any, TypeId};
432
433        struct CustomQuery;
434
435        impl QueryInternal<Person> for CustomQuery {
436            type QueryParts<'a>
437                = [&'a dyn Any; 0]
438            where
439                Self: 'a;
440
441            fn get_type_ids(&self) -> Vec<TypeId> {
442                Vec::new()
443            }
444
445            fn query_parts(&self) -> Self::QueryParts<'_> {
446                []
447            }
448
449            fn new_query_result<'c>(&self, context: &'c Context) -> EntitySet<'c, Person> {
450                <() as QueryInternal<Person>>::new_query_result(&(), context)
451            }
452
453            fn match_entity(&self, entity_id: EntityId<Person>, context: &Context) -> bool {
454                <() as QueryInternal<Person>>::match_entity(&(), entity_id, context)
455            }
456
457            fn filter_entities(&self, entities: &mut Vec<EntityId<Person>>, context: &Context) {
458                <() as QueryInternal<Person>>::filter_entities(&(), entities, context);
459            }
460        }
461
462        let resolved = CustomQuery.resolved_query();
463        assert_eq!(
464            resolved.identity,
465            <Person as QueryInternal<Person>>::resolved_query(&Person).identity
466        );
467        assert_eq!(query_identity_label(resolved.identity), "Person: All");
468    }
469
470    #[test]
471    fn singleton_query_result_iterator_uses_indexed_and_unindexed_paths() {
472        let mut context = Context::new();
473        let high = context
474            .add_entity(with!(Person, RiskCategory::High))
475            .unwrap();
476        let _low = context
477            .add_entity(with!(Person, RiskCategory::Low))
478            .unwrap();
479
480        let people = <(RiskCategory,) as QueryInternal<Person>>::new_query_result_iterator(
481            &(RiskCategory::High,),
482            &context,
483        )
484        .collect::<Vec<_>>();
485        assert_eq!(people, vec![high]);
486
487        let mut indexed_context = Context::new();
488        indexed_context.index_property::<Person, RiskCategory>();
489        let indexed_high = indexed_context
490            .add_entity(with!(Person, RiskCategory::High))
491            .unwrap();
492        let _indexed_low = indexed_context
493            .add_entity(with!(Person, RiskCategory::Low))
494            .unwrap();
495
496        let people = <(RiskCategory,) as QueryInternal<Person>>::new_query_result_iterator(
497            &(RiskCategory::High,),
498            &indexed_context,
499        )
500        .collect::<Vec<_>>();
501        assert_eq!(people, vec![indexed_high]);
502
503        let mut empty_index_context = Context::new();
504        empty_index_context.index_property::<Person, Age>();
505        let _ = empty_index_context
506            .add_entity(with!(Person, Age(42), RiskCategory::Low))
507            .unwrap();
508
509        let people = <(Age,) as QueryInternal<Person>>::new_query_result_iterator(
510            &(Age(99),),
511            &empty_index_context,
512        )
513        .collect::<Vec<_>>();
514        assert!(people.is_empty());
515    }
516
517    #[test]
518    fn tuple_query_result_iterator_uses_indexed_and_unindexed_paths() {
519        let mut context = Context::new();
520        let matching1 = context
521            .add_entity(with!(Person, Age(28), County(0), RiskCategory::High))
522            .unwrap();
523        let _wrong_county = context
524            .add_entity(with!(Person, Age(28), County(1), RiskCategory::Low))
525            .unwrap();
526        let _wrong_age = context
527            .add_entity(with!(Person, Age(30), County(0), RiskCategory::Low))
528            .unwrap();
529        let matching2 = context
530            .add_entity(with!(Person, Age(28), County(0), RiskCategory::Low))
531            .unwrap();
532
533        let people = <(Age, County) as QueryInternal<Person>>::new_query_result_iterator(
534            &(Age(28), County(0)),
535            &context,
536        )
537        .collect::<Vec<_>>();
538        assert_eq!(people, vec![matching1, matching2]);
539
540        let mut indexed_context = Context::new();
541        indexed_context.index_property::<Person, (Age, County)>();
542        let indexed_matching = indexed_context
543            .add_entity(with!(Person, Age(28), County(0), RiskCategory::High))
544            .unwrap();
545        let _indexed_nonmatching = indexed_context
546            .add_entity(with!(Person, Age(28), County(1), RiskCategory::Low))
547            .unwrap();
548
549        let people = <(Age, County) as QueryInternal<Person>>::new_query_result_iterator(
550            &(Age(28), County(0)),
551            &indexed_context,
552        )
553        .collect::<Vec<_>>();
554        assert_eq!(people, vec![indexed_matching]);
555
556        let people = <(Age, County) as QueryInternal<Person>>::new_query_result_iterator(
557            &(Age(99), County(99)),
558            &indexed_context,
559        )
560        .collect::<Vec<_>>();
561        assert!(people.is_empty());
562    }
563
564    #[test]
565    fn singleton_filter_entities_keeps_matching_entities() {
566        let mut context = Context::new();
567        let high1 = context
568            .add_entity(with!(Person, RiskCategory::High))
569            .unwrap();
570        let low = context
571            .add_entity(with!(Person, RiskCategory::Low))
572            .unwrap();
573        let high2 = context
574            .add_entity(with!(Person, RiskCategory::High))
575            .unwrap();
576
577        let mut people = vec![high1, low, high2];
578        context.filter_entities(&mut people, with!(Person, RiskCategory::High));
579
580        assert_eq!(people, vec![high1, high2]);
581    }
582
583    #[test]
584    fn tuple_filter_entities_falls_through_after_unsupported_multi_index_lookup() {
585        let mut context = Context::new();
586        let matching1 = context
587            .add_entity(with!(Person, Age(28), County(0), RiskCategory::High))
588            .unwrap();
589        let wrong_county = context
590            .add_entity(with!(Person, Age(28), County(1), RiskCategory::Low))
591            .unwrap();
592        let wrong_age = context
593            .add_entity(with!(Person, Age(30), County(0), RiskCategory::Low))
594            .unwrap();
595        let matching2 = context
596            .add_entity(with!(Person, Age(28), County(0), RiskCategory::Low))
597            .unwrap();
598
599        let mut people = vec![matching1, wrong_county, wrong_age, matching2];
600        context.filter_entities(&mut people, with!(Person, County(0), Age(28)));
601
602        assert_eq!(people, vec![matching1, matching2]);
603    }
604
605    #[test]
606    fn with_query_results() {
607        let mut context = Context::new();
608        let _ = context
609            .add_entity(with!(Person, RiskCategory::High))
610            .unwrap();
611
612        context.with_query_results(with!(Person, RiskCategory::High), &mut |people| {
613            assert_eq!(people.into_iter().count(), 1);
614        });
615    }
616
617    #[test]
618    fn with_query_results_empty() {
619        let context = Context::new();
620
621        context.with_query_results(with!(Person, RiskCategory::High), &mut |people| {
622            assert_eq!(people.into_iter().count(), 0);
623        });
624    }
625
626    #[test]
627    fn query_entity_count() {
628        let mut context = Context::new();
629        let _ = context
630            .add_entity(with!(Person, RiskCategory::High))
631            .unwrap();
632
633        assert_eq!(
634            context.query_entity_count(with!(Person, RiskCategory::High)),
635            1
636        );
637    }
638
639    #[test]
640    fn query_entity_count_empty() {
641        let context = Context::new();
642
643        assert_eq!(
644            context.query_entity_count(with!(Person, RiskCategory::High)),
645            0
646        );
647    }
648
649    #[test]
650    fn with_query_results_macro_index_first() {
651        let mut context = Context::new();
652        let _ = context
653            .add_entity(with!(Person, RiskCategory::High))
654            .unwrap();
655        context.index_property::<_, RiskCategory>();
656        assert!(context.is_property_indexed::<Person, RiskCategory>());
657
658        context.with_query_results(with!(Person, RiskCategory::High), &mut |people| {
659            assert_eq!(people.into_iter().count(), 1);
660        });
661    }
662
663    #[test]
664    fn with_query_results_macro_index_second() {
665        let mut context = Context::new();
666        let _ = context.add_entity(with!(Person, RiskCategory::High));
667
668        context.with_query_results(with!(Person, RiskCategory::High), &mut |people| {
669            assert_eq!(people.into_iter().count(), 1);
670        });
671        assert!(!context.is_property_indexed::<Person, RiskCategory>());
672
673        context.index_property::<Person, RiskCategory>();
674        assert!(context.is_property_indexed::<Person, RiskCategory>());
675
676        context.with_query_results(with!(Person, RiskCategory::High), &mut |people| {
677            assert_eq!(people.into_iter().count(), 1);
678        });
679    }
680
681    #[test]
682    fn with_query_results_macro_change() {
683        let mut context = Context::new();
684        let person1 = context
685            .add_entity(with!(Person, RiskCategory::High))
686            .unwrap();
687
688        context.with_query_results(with!(Person, RiskCategory::High), &mut |people| {
689            assert_eq!(people.into_iter().count(), 1);
690        });
691
692        context.with_query_results(with!(Person, RiskCategory::Low), &mut |people| {
693            assert_eq!(people.into_iter().count(), 0);
694        });
695
696        context.set_property(person1, RiskCategory::Low);
697        context.with_query_results(with!(Person, RiskCategory::High), &mut |people| {
698            assert_eq!(people.into_iter().count(), 0);
699        });
700
701        context.with_query_results(with!(Person, RiskCategory::Low), &mut |people| {
702            assert_eq!(people.into_iter().count(), 1);
703        });
704    }
705
706    #[test]
707    fn with_query_results_index_after_add() {
708        let mut context = Context::new();
709        let _ = context
710            .add_entity(with!(Person, RiskCategory::High))
711            .unwrap();
712        context.index_property::<Person, RiskCategory>();
713        assert!(context.is_property_indexed::<Person, RiskCategory>());
714        context.with_query_results(with!(Person, RiskCategory::High), &mut |people| {
715            assert_eq!(people.into_iter().count(), 1);
716        });
717    }
718
719    #[test]
720    fn with_query_results_add_after_index() {
721        let mut context = Context::new();
722        let _ = context
723            .add_entity(with!(Person, RiskCategory::High))
724            .unwrap();
725        context.index_property::<Person, RiskCategory>();
726        assert!(context.is_property_indexed::<Person, RiskCategory>());
727        context.with_query_results(with!(Person, RiskCategory::High), &mut |people| {
728            assert_eq!(people.into_iter().count(), 1);
729        });
730
731        let _ = context
732            .add_entity(with!(Person, RiskCategory::High))
733            .unwrap();
734        context.with_query_results(with!(Person, RiskCategory::High), &mut |people| {
735            assert_eq!(people.into_iter().count(), 2);
736        });
737    }
738
739    #[test]
740    fn with_query_results_cast_value() {
741        let mut context = Context::new();
742        let _ = context
743            .add_entity(with!(Person, Age(42), RiskCategory::High))
744            .unwrap();
745
746        context.with_query_results(with!(Person, Age(42)), &mut |people| {
747            assert_eq!(people.into_iter().count(), 1);
748        });
749    }
750
751    #[test]
752    fn with_query_results_intersection() {
753        let mut context = Context::new();
754        let _ = context
755            .add_entity(with!(Person, Age(42), RiskCategory::High))
756            .unwrap();
757        let _ = context
758            .add_entity(with!(Person, Age(42), RiskCategory::Low))
759            .unwrap();
760        let _ = context
761            .add_entity(with!(Person, Age(40), RiskCategory::Low))
762            .unwrap();
763
764        context.with_query_results(with!(Person, Age(42), RiskCategory::High), &mut |people| {
765            assert_eq!(people.into_iter().count(), 1);
766        });
767    }
768
769    #[test]
770    fn with_query_results_intersection_non_macro() {
771        let mut context = Context::new();
772        let _ = context
773            .add_entity(with!(Person, Age(42), RiskCategory::High))
774            .unwrap();
775        let _ = context
776            .add_entity(with!(Person, Age(42), RiskCategory::Low))
777            .unwrap();
778        let _ = context
779            .add_entity(with!(Person, Age(40), RiskCategory::Low))
780            .unwrap();
781
782        context.with_query_results(with!(Person, Age(42), RiskCategory::High), &mut |people| {
783            assert_eq!(people.into_iter().count(), 1);
784        });
785    }
786
787    #[test]
788    fn with_query_results_intersection_one_indexed() {
789        let mut context = Context::new();
790        let _ = context
791            .add_entity(with!(Person, Age(42), RiskCategory::High))
792            .unwrap();
793        let _ = context
794            .add_entity(with!(Person, Age(42), RiskCategory::Low))
795            .unwrap();
796        let _ = context
797            .add_entity(with!(Person, Age(40), RiskCategory::Low))
798            .unwrap();
799
800        context.index_property::<Person, Age>();
801        context.with_query_results(with!(Person, Age(42), RiskCategory::High), &mut |people| {
802            assert_eq!(people.into_iter().count(), 1);
803        });
804    }
805
806    #[test]
807    fn query_derived_prop() {
808        let mut context = Context::new();
809        define_derived_property!(struct Senior(bool), Person, [Age], |age| Senior(age.0 >= 65));
810
811        let person = context
812            .add_entity(with!(Person, Age(64), RiskCategory::High))
813            .unwrap();
814        context
815            .add_entity(with!(Person, Age(88), RiskCategory::High))
816            .unwrap();
817
818        let mut not_seniors = Vec::new();
819        context.with_query_results(with!(Person, Senior(false)), &mut |people| {
820            not_seniors = people.to_owned_vec();
821        });
822        let mut seniors = Vec::new();
823        context.with_query_results(with!(Person, Senior(true)), &mut |people| {
824            seniors = people.to_owned_vec();
825        });
826        assert_eq!(seniors.len(), 1, "One senior");
827        assert_eq!(not_seniors.len(), 1, "One non-senior");
828
829        context.set_property(person, Age(65));
830
831        context.with_query_results(with!(Person, Senior(false)), &mut |people| {
832            not_seniors = people.to_owned_vec()
833        });
834        context.with_query_results(with!(Person, Senior(true)), &mut |people| {
835            seniors = people.to_owned_vec()
836        });
837
838        assert_eq!(seniors.len(), 2, "Two seniors");
839        assert_eq!(not_seniors.len(), 0, "No non-seniors");
840    }
841
842    #[test]
843    fn query_derived_prop_with_index() {
844        let mut context = Context::new();
845        define_derived_property!(struct Senior(bool), Person, [Age], |age| Senior(age.0 >= 65));
846
847        context.index_property::<Person, Senior>();
848        let person = context
849            .add_entity(with!(Person, Age(64), RiskCategory::Low))
850            .unwrap();
851        let _ = context.add_entity(with!(Person, Age(88), RiskCategory::Low));
852
853        let mut not_seniors = Vec::new();
854        context.with_query_results(with!(Person, Senior(false)), &mut |people| {
855            not_seniors = people.to_owned_vec()
856        });
857        let mut seniors = Vec::new();
858        context.with_query_results(with!(Person, Senior(true)), &mut |people| {
859            seniors = people.to_owned_vec()
860        });
861        assert_eq!(seniors.len(), 1, "One senior");
862        assert_eq!(not_seniors.len(), 1, "One non-senior");
863
864        context.set_property(person, Age(65));
865
866        context.with_query_results(with!(Person, Senior(false)), &mut |people| {
867            not_seniors = people.to_owned_vec()
868        });
869        context.with_query_results(with!(Person, Senior(true)), &mut |people| {
870            seniors = people.to_owned_vec()
871        });
872
873        assert_eq!(seniors.len(), 2, "Two seniors");
874        assert_eq!(not_seniors.len(), 0, "No non-seniors");
875    }
876
877    // create a multi-property index
878    define_multi_property!(Person, (Age, County, Height));
879    define_multi_property!(Person, (County, Height));
880
881    #[test]
882    fn query_derived_prop_with_optimized_index() {
883        let mut context = Context::new();
884        // create a 'regular' derived property
885        define_derived_property!(
886            struct Ach(u8, u32, u32),
887            Person,
888            [Age, County, Height],
889            [],
890            |age, county, height| Ach(age.0, county.0, height.0)
891        );
892
893        // add some people
894        let _ = context.add_entity(with!(
895            Person,
896            Age(64),
897            County(2),
898            Height(120),
899            RiskCategory::Low
900        ));
901        let _ = context.add_entity(with!(
902            Person,
903            Age(88),
904            County(2),
905            Height(130),
906            RiskCategory::Low
907        ));
908        let p2 = context
909            .add_entity(with!(
910                Person,
911                Age(8),
912                County(1),
913                Height(140),
914                RiskCategory::Low
915            ))
916            .unwrap();
917        let p3 = context
918            .add_entity(with!(
919                Person,
920                Age(28),
921                County(1),
922                Height(140),
923                RiskCategory::Low
924            ))
925            .unwrap();
926        let p4 = context
927            .add_entity(with!(
928                Person,
929                Age(28),
930                County(2),
931                Height(160),
932                RiskCategory::Low
933            ))
934            .unwrap();
935        let p5 = context
936            .add_entity(with!(
937                Person,
938                Age(28),
939                County(2),
940                Height(160),
941                RiskCategory::Low
942            ))
943            .unwrap();
944
945        // 'regular' derived property
946        context.with_query_results(with!(Person, Ach(28, 2, 160)), &mut |people| {
947            assert!(people.contains(p4));
948            assert!(people.contains(p5));
949            assert_eq!(people.into_iter().count(), 2, "Should have 2 matches");
950        });
951
952        // multi-property index
953        context.with_query_results(
954            with!(Person, Age(28), County(2), Height(160)),
955            &mut |people| {
956                assert!(people.contains(p4));
957                assert!(people.contains(p5));
958                assert_eq!(people.into_iter().count(), 2, "Should have 2 matches");
959            },
960        );
961
962        // multi-property index with different order
963        context.with_query_results(
964            with!(Person, County(2), Height(160), Age(28)),
965            &mut |people| {
966                assert!(people.contains(p4));
967                assert!(people.contains(p5));
968                assert_eq!(people.into_iter().count(), 2, "Should have 2 matches");
969            },
970        );
971
972        // multi-property index with different order
973        context.with_query_results(
974            with!(Person, Height(160), County(2), Age(28)),
975            &mut |people| {
976                assert!(people.contains(p4));
977                assert!(people.contains(p5));
978                assert_eq!(people.into_iter().count(), 2, "Should have 2 matches");
979            },
980        );
981
982        // multi-property index with different order and different value
983        context.with_query_results(
984            with!(Person, Height(140), County(1), Age(28)),
985            &mut |people| {
986                assert!(people.contains(p3));
987                assert_eq!(people.into_iter().count(), 1, "Should have 1 matches");
988            },
989        );
990
991        context.set_property(p2, Age(28));
992        // multi-property index again after changing the value
993        context.with_query_results(
994            with!(Person, Height(140), County(1), Age(28)),
995            &mut |people| {
996                assert!(people.contains(p2));
997                assert!(people.contains(p3));
998                assert_eq!(people.into_iter().count(), 2, "Should have 2 matches");
999            },
1000        );
1001
1002        context.with_query_results(with!(Person, Height(140), County(1)), &mut |people| {
1003            assert!(people.contains(p2));
1004            assert!(people.contains(p3));
1005            assert_eq!(people.into_iter().count(), 2, "Should have 2 matches");
1006        });
1007    }
1008
1009    #[test]
1010    fn test_match_entity() {
1011        let mut context = Context::new();
1012        let person = context
1013            .add_entity(with!(
1014                Person,
1015                Age(28),
1016                County(2),
1017                Height(160),
1018                RiskCategory::Low
1019            ))
1020            .unwrap();
1021        assert!(context.match_entity(person, with!(Person, Age(28), County(2), Height(160))));
1022        assert!(!context.match_entity(person, with!(Person, Age(13), County(2), Height(160))));
1023        assert!(!context.match_entity(person, with!(Person, Age(28), County(33), Height(160))));
1024        assert!(!context.match_entity(person, with!(Person, Age(28), County(2), Height(9))));
1025    }
1026
1027    #[test]
1028    fn filter_entities_for_unindexed_query() {
1029        let mut context = Context::new();
1030        let mut people = Vec::new();
1031
1032        for idx in 0..10 {
1033            let person = context
1034                .add_entity(with!(
1035                    Person,
1036                    Age(28),
1037                    County(idx % 2),
1038                    Height(160),
1039                    RiskCategory::Low
1040                ))
1041                .unwrap();
1042            people.push(person);
1043        }
1044
1045        context.filter_entities(
1046            &mut people,
1047            with!(Person, Age(28), County(0), Height(160), RiskCategory::Low),
1048        );
1049
1050        let expected = (0..5)
1051            .map(|idx| PersonId::new(idx * 2))
1052            .collect::<Vec<PersonId>>();
1053        assert_eq!(people, expected);
1054    }
1055
1056    #[test]
1057    fn filter_entities_for_indexed_query() {
1058        let mut context = Context::new();
1059        let mut people = Vec::new();
1060
1061        context.index_property::<Person, (Age, County)>();
1062
1063        for idx in 0..10 {
1064            let person = context
1065                .add_entity(with!(
1066                    Person,
1067                    Age(28),
1068                    County(idx % 2),
1069                    Height(160),
1070                    RiskCategory::Low
1071                ))
1072                .unwrap();
1073            people.push(person);
1074        }
1075
1076        context.filter_entities(&mut people, with!(Person, County(0), Age(28)));
1077
1078        let expected = (0..5)
1079            .map(|idx| PersonId::new(idx * 2))
1080            .collect::<Vec<PersonId>>();
1081        assert_eq!(people, expected);
1082    }
1083
1084    #[test]
1085    fn entity_property_tuple_basic() {
1086        use super::EntityPropertyTuple;
1087
1088        let mut context = Context::new();
1089        let p1 = context
1090            .add_entity(with!(Person, Age(42), RiskCategory::High))
1091            .unwrap();
1092        let _ = context
1093            .add_entity(with!(Person, Age(42), RiskCategory::Low))
1094            .unwrap();
1095        let _ = context
1096            .add_entity(with!(Person, Age(30), RiskCategory::High))
1097            .unwrap();
1098
1099        // Create query using EntityPropertyTuple
1100        let query: EntityPropertyTuple<Person, _> =
1101            EntityPropertyTuple::new((Age(42), RiskCategory::High));
1102
1103        context.with_query_results(query, &mut |people| {
1104            assert!(people.contains(p1));
1105            assert_eq!(people.into_iter().count(), 1);
1106        });
1107
1108        // Test match_entity
1109        assert!(context.match_entity(p1, query));
1110
1111        // Test query_entity_count
1112        assert_eq!(context.query_entity_count(query), 1);
1113    }
1114
1115    #[test]
1116    fn entity_property_tuple_empty_query() {
1117        use super::EntityPropertyTuple;
1118
1119        let mut context = Context::new();
1120        let _ = context
1121            .add_entity(with!(Person, Age(42), RiskCategory::High))
1122            .unwrap();
1123        let _ = context
1124            .add_entity(with!(Person, Age(30), RiskCategory::Low))
1125            .unwrap();
1126
1127        // Empty query matches all entities
1128        let query: EntityPropertyTuple<Person, _> = EntityPropertyTuple::new(());
1129
1130        assert_eq!(context.query_entity_count(query), 2);
1131    }
1132
1133    #[test]
1134    fn entity_property_tuple_singleton() {
1135        use super::EntityPropertyTuple;
1136
1137        let mut context = Context::new();
1138        let _ = context
1139            .add_entity(with!(Person, Age(42), RiskCategory::High))
1140            .unwrap();
1141        let _ = context
1142            .add_entity(with!(Person, Age(42), RiskCategory::Low))
1143            .unwrap();
1144        let _ = context
1145            .add_entity(with!(Person, Age(30), RiskCategory::High))
1146            .unwrap();
1147
1148        // Single property query
1149        let query: EntityPropertyTuple<Person, _> = EntityPropertyTuple::new((Age(42),));
1150
1151        assert_eq!(context.query_entity_count(query), 2);
1152    }
1153
1154    #[test]
1155    fn entity_property_tuple_inner_access() {
1156        use super::EntityPropertyTuple;
1157
1158        let query: EntityPropertyTuple<Person, _> =
1159            EntityPropertyTuple::new((Age(42), RiskCategory::High));
1160
1161        // Test inner() accessor
1162        let inner = query.inner();
1163        assert_eq!(inner.0, Age(42));
1164        assert_eq!(inner.1, RiskCategory::High);
1165
1166        // Test into_inner()
1167        let (age, risk) = query.into_inner();
1168        assert_eq!(age, Age(42));
1169        assert_eq!(risk, RiskCategory::High);
1170    }
1171
1172    #[test]
1173    fn all_macro_no_properties() {
1174        use crate::with;
1175
1176        let mut context = Context::new();
1177        let _ = context
1178            .add_entity(with!(Person, Age(42), RiskCategory::High))
1179            .unwrap();
1180        let _ = context
1181            .add_entity(with!(Person, Age(30), RiskCategory::Low))
1182            .unwrap();
1183
1184        // with!(Person) should match all Person entities
1185        let query = with!(Person);
1186        assert_eq!(context.query_entity_count(query), 2);
1187    }
1188
1189    #[test]
1190    fn all_macro_single_property() {
1191        use crate::with;
1192
1193        let mut context = Context::new();
1194        let _ = context
1195            .add_entity(with!(Person, Age(42), RiskCategory::High))
1196            .unwrap();
1197        let _ = context
1198            .add_entity(with!(Person, Age(42), RiskCategory::Low))
1199            .unwrap();
1200        let _ = context
1201            .add_entity(with!(Person, Age(30), RiskCategory::High))
1202            .unwrap();
1203
1204        // with!(Person, Age(42)) should match entities with Age = 42
1205        let query = with!(Person, Age(42));
1206        assert_eq!(context.query_entity_count(query), 2);
1207    }
1208
1209    #[test]
1210    fn all_macro_multiple_properties() {
1211        use crate::with;
1212
1213        let mut context = Context::new();
1214        let p1 = context
1215            .add_entity(with!(Person, Age(42), RiskCategory::High))
1216            .unwrap();
1217        let _ = context
1218            .add_entity(with!(Person, Age(42), RiskCategory::Low))
1219            .unwrap();
1220        let _ = context
1221            .add_entity(with!(Person, Age(30), RiskCategory::High))
1222            .unwrap();
1223
1224        // with!(Person, Age(42), RiskCategory::High) should match one entity
1225        let query = with!(Person, Age(42), RiskCategory::High);
1226        assert_eq!(context.query_entity_count(query), 1);
1227
1228        context.with_query_results(query, &mut |people| {
1229            assert!(people.contains(p1));
1230        });
1231    }
1232
1233    #[test]
1234    fn all_macro_with_trailing_comma() {
1235        use crate::with;
1236
1237        let mut context = Context::new();
1238        let _ = context
1239            .add_entity(with!(Person, Age(42), RiskCategory::High))
1240            .unwrap();
1241
1242        // Trailing comma should work
1243        let query = with!(Person, Age(42));
1244        assert_eq!(context.query_entity_count(query), 1);
1245
1246        let query = with!(Person, Age(42), RiskCategory::High);
1247        assert_eq!(context.query_entity_count(query), 1);
1248    }
1249
1250    #[test]
1251    fn entity_property_tuple_as_property_list() {
1252        use super::EntityPropertyTuple;
1253        use crate::entity::property_list::PropertyList;
1254
1255        // Test validate
1256        assert!(EntityPropertyTuple::<Person, (Age,)>::validate().is_ok());
1257        assert!(EntityPropertyTuple::<Person, (Age, RiskCategory)>::validate().is_ok());
1258
1259        // Test contains_properties
1260        assert!(EntityPropertyTuple::<Person, (Age,)>::contains_properties(
1261            &[Age::type_id()]
1262        ));
1263        assert!(
1264            EntityPropertyTuple::<Person, (Age, RiskCategory)>::contains_properties(&[
1265                Age::type_id()
1266            ])
1267        );
1268        assert!(
1269            EntityPropertyTuple::<Person, (Age, RiskCategory)>::contains_properties(&[
1270                Age::type_id(),
1271                RiskCategory::type_id()
1272            ])
1273        );
1274    }
1275
1276    #[test]
1277    fn all_macro_as_property_list_for_add_entity() {
1278        use crate::with;
1279
1280        let mut context = Context::new();
1281
1282        // Use with! macro result to add an entity
1283        let props = with!(Person, Age(42), RiskCategory::High);
1284        let person = context.add_entity(props).unwrap();
1285
1286        // Verify the entity was created with the correct properties
1287        assert_eq!(context.get_property::<Person, Age>(person), Age(42));
1288        assert_eq!(
1289            context.get_property::<Person, RiskCategory>(person),
1290            RiskCategory::High
1291        );
1292    }
1293}