Skip to main content

ixa/entity/
property_store.rs

1/*!
2
3A [`PropertyStore`] implements the registry pattern for property value stores: A [`PropertyStore`]
4wraps a vector of `PropertyValueStore`s, one for each concrete property type. The implementor
5of [`crate::entity::property::Property`] is the value type. Since there's a 1-1 correspondence between property types
6and their value stores, we assign an ID to each property type to make
7property lookup fast. The [`PropertyStore`] stores a list of all properties in the form of
8boxed `PropertyValueStore` instances, which provide a type-erased interface to the backing
9storage (including index) of the property. Storage is only allocated as-needed, so the
10instantiation of a `PropertyValueStore` for a property that is never used is negligible.
11There's no need, then, for lazy initialization of the `PropertyValueStore`s themselves.
12
13This module also implements the initialization of "static" data associated with a property,
14that is, data that is the same across all [`crate::context::Context`] instances, which is computed before `main()`
15using `ctor` magic. (Each property implements a ctor that calls [`add_to_property_registry()`].)
16For simplicity, a property's ctor implementation, supplied by a macro, just calls
17`add_to_property_registry<E: Entity, P: Property<E>>()`, which does all the work. The
18`add_to_property_registry` function adds the following metadata to global metadata stores:
19
20Metadata stored on `PROPERTY_METADATA`, which for each property stores:
21- a list of dependent (derived) properties, and
22- a constructor function to create a new `PropertyValueStore` instance for the property.
23
24Metadata stored on `ENTITY_METADATA`, which for each entity stores:
25- a list of properties associated with the entity, and
26- a list of _required_ properties for the entity. These are properties for
27  which values must be supplied to `add_entity` when creating a new entity.
28
29*/
30
31use std::any::{Any, TypeId};
32use std::collections::HashMap;
33use std::sync::atomic::{AtomicUsize, Ordering};
34use std::sync::{LazyLock, Mutex, OnceLock};
35
36use crate::entity::entity::Entity;
37use crate::entity::entity_store::register_property_with_entity;
38use crate::entity::events::PartialPropertyChangeEventBox;
39use crate::entity::index::{FullIndex, IndexCountResult, IndexSetResult, ValueCountIndex};
40use crate::entity::multi_property::multi_property_id_for_property_type_id;
41use crate::entity::property::{IndexableProperty, Property};
42use crate::entity::property_list::PropertyList;
43use crate::entity::property_value_store::PropertyValueStore;
44use crate::entity::property_value_store_core::PropertyValueStoreCore;
45use crate::entity::value_change_counter::StratifiedValueChangeCounter;
46use crate::entity::{EntityId, PropertyIndexType};
47use crate::Context;
48
49/// A map from Entity ID to a count of the properties already associated with the entity. The value for the key is
50/// equivalent to the next property ID that will be assigned to the next property that requests an ID. Each `Entity`
51/// type has its own series of increasing property IDs.
52///
53/// Note: The mechanism to assign property IDs needs to be distinct from the rest of property registration, because
54/// properties often need to have an ID assigned _before_ its registration proper so that it can be recorded as a
55/// dependency of some other property.
56static NEXT_PROPERTY_ID: LazyLock<Mutex<HashMap<usize, usize>>> =
57    LazyLock::new(|| Mutex::new(HashMap::default()));
58
59/// A container struct to hold the (global) metadata for a single property.
60///
61/// At program startup (before `main()`, using ctors) we compute metadata for all properties
62/// that are linked into the binary, and this data remains unchanged for the life of the program.
63#[derive(Default)]
64pub(super) struct PropertyMetadata<E: Entity> {
65    /// The (derived) properties that depend on this property, as represented by their
66    /// `Property::id` value. This list is used to update the index (if applicable)
67    /// and emit change events for these properties when this property changes.
68    pub dependents: Vec<usize>,
69    /// A function that constructs a new `PropertyValueStoreCore<E, P>` instance in a type-erased
70    /// way, used in the constructor of `PropertyStore`. This is an `Option` because this
71    /// function pointer is recorded possibly out-of-order from when the `PropertyMetadata`
72    /// instance for this property needs to exist (when its dependents are recorded).
73    #[allow(clippy::type_complexity)]
74    pub value_store_constructor: Option<fn() -> Box<dyn PropertyValueStore<E>>>,
75}
76
77/// This maps `(entity_type_id, property_type_index)` to `PropertyMetadata<E>`, which holds a vector of dependents (as IDs)
78/// and a function pointer to the constructor that constucts a `PropertyValueStoreCore<E, P>` type erased as
79/// a `Box<dyn PropertyValueStore<E>>`. This data is actually written by the property `ctor`s with a call to [`crate::entity::entity_store::register_property_with_entity`()].
80#[allow(clippy::type_complexity)]
81static PROPERTY_METADATA_BUILDER: LazyLock<
82    Mutex<HashMap<(usize, usize), Box<dyn Any + Send + Sync>>>,
83> = LazyLock::new(|| Mutex::new(HashMap::default()));
84
85/// The frozen property metadata registry, created exactly once on first read.
86///
87/// This is derived from `PROPERTY_METADATA_BUILDER` by moving the builder `HashMap` out. After this point,
88/// registration is no longer allowed.
89static PROPERTY_METADATA: OnceLock<HashMap<(usize, usize), Box<dyn Any + Send + Sync>>> =
90    OnceLock::new();
91
92/// Private helper to fetch or initialize the frozen metadata.
93fn property_metadata() -> &'static HashMap<(usize, usize), Box<dyn Any + Send + Sync>> {
94    PROPERTY_METADATA.get_or_init(|| {
95        let mut builder = PROPERTY_METADATA_BUILDER.lock().unwrap();
96        std::mem::take(&mut *builder)
97    })
98}
99
100/// The public getter for the dependents of a property with index `property_index` (as stored in
101/// `PROPERTY_METADATA`). The `Property<E: Entity>::dependents()` method defers to this.
102///
103/// This function should only be called once `main()` starts, that is, not in `ctors` constructors,
104/// as it assumes `PROPERTY_METADATA` has been correctly initialized. Hence, the "static" suffix.
105#[must_use]
106pub(super) fn get_property_dependents_static<E: Entity>(property_index: usize) -> &'static [usize] {
107    let map = property_metadata();
108    let property_metadata = map
109        .get(&(E::id(), property_index))
110                               .unwrap_or_else(|| panic!("No registered property found with index = {property_index:?}. You must use the `define_property!` macro to create a registered property."));
111    let property_metadata: &PropertyMetadata<E> = property_metadata.downcast_ref().unwrap_or_else(
112        || panic!(
113            "Property type at index {:?} does not match registered property type. You must use the `define_property!` macro to create a registered property.",
114            property_index
115        )
116    );
117
118    property_metadata.dependents.as_slice()
119}
120
121/// Adds a new item to the registry. The job of this method is to create whatever "singleton"
122/// data/metadata is associated with the [`crate::entity::property::Property`] if it doesn't already exist. In
123/// our use case, this method is called in the `ctor` function of each `Property<E>` type.
124pub fn add_to_property_registry<E: Entity, P: Property<E>>() {
125    // Ensure the ID of the property type is initialized.
126    let property_index = P::id();
127
128    // Registers the property with the entity type.
129    register_property_with_entity(
130        <E as Entity>::type_id(),
131        <P as Property<E>>::type_id(),
132        P::is_required(),
133    );
134
135    let mut property_metadata = PROPERTY_METADATA_BUILDER.lock().unwrap();
136    if PROPERTY_METADATA.get().is_some() {
137        panic!(
138            "`add_to_property_registry()` called after property metadata was frozen; registration must occur during startup/ctors."
139        );
140    }
141
142    // Register the `PropertyValueStoreCore<E, P>` constructor.
143    {
144        let metadata = property_metadata
145            .entry((E::id(), property_index))
146            .or_insert_with(|| Box::new(PropertyMetadata::<E>::default()));
147        let metadata: &mut PropertyMetadata<E> = metadata.downcast_mut().unwrap();
148        metadata
149            .value_store_constructor
150            .get_or_insert(PropertyValueStoreCore::<E, P>::new_boxed);
151    }
152
153    // Construct the dependency graph
154    for dependency in P::non_derived_dependencies() {
155        // Add `property_index` as a dependent of the dependency
156        let dependency_meta = property_metadata
157            .entry((E::id(), dependency))
158            .or_insert_with(|| Box::new(PropertyMetadata::<E>::default()));
159        let dependency_meta: &mut PropertyMetadata<E> = dependency_meta.downcast_mut().unwrap();
160        dependency_meta.dependents.push(property_index);
161    }
162}
163
164/// A convenience getter for `NEXT_ENTITY_INDEX`.
165pub fn get_registered_property_count<E: Entity>() -> usize {
166    let map = NEXT_PROPERTY_ID.lock().unwrap();
167    *map.get(&E::id()).unwrap_or(&0)
168}
169
170/// Encapsulates the synchronization logic for initializing an item's index.
171///
172/// Acquires a global lock on the next available property ID, but only increments
173/// it if we successfully initialize the provided ID. The ID of a property is
174/// assigned at runtime but only once per type. It's possible for a single
175/// type to attempt to initialize its index multiple times from different threads,
176/// which is why all this synchronization is required. However, the overhead
177/// is negligible, as this initialization only happens once upon first access.
178///
179/// In fact, for our use case we know we are calling this function
180/// once for each type in each `Property`'s `ctor` function, which
181/// should be the only time this method is ever called for the type.
182pub fn initialize_property_id<E: Entity>(property_id: &AtomicUsize) -> usize {
183    // Acquire a global lock.
184    let mut guard = NEXT_PROPERTY_ID.lock().unwrap();
185    let candidate = guard.entry(E::id()).or_insert_with(|| 0);
186
187    // Try to claim the candidate index. Here we guard against the potential race condition that
188    // another instance of this plugin in another thread just initialized the index prior to us
189    // obtaining the lock. If the index has been initialized beneath us, we do not update
190    // NEXT_PROPERTY_INDEX, we just return the value `index` was initialized to.
191    // For a justification of the data ordering, see:
192    //     https://github.com/CDCgov/ixa/pull/477#discussion_r2244302872
193    match property_id.compare_exchange(usize::MAX, *candidate, Ordering::AcqRel, Ordering::Acquire)
194    {
195        Ok(_) => {
196            // We won the race — increment the global next plugin index and return the new index
197            *candidate += 1;
198            *candidate - 1
199        }
200        Err(existing) => {
201            // Another thread beat us — don’t increment the global next plugin index,
202            // just return existing
203            existing
204        }
205    }
206}
207
208/// A wrapper around a vector of property value stores.
209pub struct PropertyStore<E: Entity> {
210    /// A vector of `Box<PropertyValueStoreCore<E, P>>`, type-erased to `Box<dyn PropertyValueStore<E>>`
211    items: Vec<Box<dyn PropertyValueStore<E>>>,
212}
213
214impl<E: Entity> Default for PropertyStore<E> {
215    fn default() -> Self {
216        PropertyStore::new()
217    }
218}
219
220impl<E: Entity> PropertyStore<E> {
221    /// Creates a new [`PropertyStore`].
222    #[must_use]
223    pub fn new() -> Self {
224        let num_items = get_registered_property_count::<E>();
225        // The constructors for each `PropertyValueStoreCore<E, P>` are stored in the `PROPERTY_METADATA` global.
226        let property_metadata = property_metadata();
227
228        // We construct the correct concrete `PropertyValueStoreCore<E, P>` value for each ID.
229        let items = (0..num_items)
230            .map(|idx| {
231                let metadata = property_metadata
232                    .get(&(E::id(), idx))
233                    .unwrap_or_else(|| panic!("No property metadata entry for index {idx}"))
234                    .downcast_ref::<PropertyMetadata<E>>()
235                    .unwrap_or_else(|| {
236                        panic!(
237                            "Property metadata entry for index {idx} does not match expected type"
238                        )
239                    });
240                let constructor = metadata
241                    .value_store_constructor
242                    .unwrap_or_else(|| panic!("No PropertyValueStore constructor for index {idx}"));
243                constructor()
244            })
245            .collect();
246
247        Self { items }
248    }
249
250    /// Fetches an immutable reference to the `PropertyValueStoreCore<E, P>`.
251    #[must_use]
252    pub fn get<P: Property<E>>(&self) -> &PropertyValueStoreCore<E, P> {
253        let index = P::id();
254        let property_value_store =
255            self.items
256                .get(index)
257                .unwrap_or_else(||
258                    panic!(
259                        "No registered property found with index = {:?} while trying to get property {}. You must use the `define_property!` macro to create a registered property.",
260                        index,
261                        P::name()
262                    )
263                );
264        let property_value_store: &PropertyValueStoreCore<E, P> = property_value_store
265            .as_any()
266            .downcast_ref::<PropertyValueStoreCore<E, P>>()
267            .unwrap_or_else(||
268                {
269                    panic!(
270                        "Property type at index {:?} does not match registered property type. Found type_id {:?} while getting type_id {:?}. You must use the `define_property!` macro to create a registered property.",
271                        index,
272                        (**property_value_store).type_id(),
273                        TypeId::of::<PropertyValueStoreCore<E, P>>()
274                    )
275                }
276            );
277        property_value_store
278    }
279
280    /// Fetches a mutable reference to the `PropertyValueStoreCore<E, P>`.
281    #[must_use]
282    pub fn get_mut<P: Property<E>>(&mut self) -> &mut PropertyValueStoreCore<E, P> {
283        let index = P::id();
284        let property_value_store =
285            self.items
286                .get_mut(index)
287                .unwrap_or_else(||
288                    panic!(
289                        "No registered property found with index = {:?} while trying to get property {}. You must use the `define_property!` macro to create a registered property.",
290                        index,
291                        P::name()
292                    )
293                );
294        let type_id = (**property_value_store).type_id(); // Only used for error message if error occurs.
295        let property_value_store: &mut PropertyValueStoreCore<E, P> = property_value_store
296            .as_any_mut()
297            .downcast_mut::<PropertyValueStoreCore<E, P>>()
298            .unwrap_or_else(||
299                {
300                    panic!(
301                        "Property type at index {:?} does not match registered property type. Found type_id {:?} while getting type_id {:?}. You must use the `define_property!` macro to create a registered property.",
302                        index,
303                        type_id,
304                        TypeId::of::<PropertyValueStoreCore<E, P>>()
305                    )
306                }
307            );
308        property_value_store
309    }
310
311    /// Creates a `PartialPropertyChangeEvent` instance for the `entity_id` and `property_index`. This method is only
312    /// called for derived dependents of some property that has changed (one of `P`'s non-derived dependencies).
313    #[must_use]
314    pub(crate) fn create_partial_property_change(
315        &self,
316        property_index: usize,
317        entity_id: EntityId<E>,
318        context: &Context,
319    ) -> PartialPropertyChangeEventBox {
320        let property_value_store = self.items
321                                       .get(property_index)
322            .unwrap_or_else(|| panic!("No registered property found with index = {property_index:?}. You must use the `define_property!` macro to create a registered property."));
323
324        property_value_store.create_partial_property_change(entity_id, context)
325    }
326
327    /// Returns whether the property with `property_index` needs partial change-event processing.
328    #[must_use]
329    pub(crate) fn should_create_partial_property_change(
330        &self,
331        property_index: usize,
332        context: &Context,
333    ) -> bool {
334        let property_value_store = self.items
335                                       .get(property_index)
336            .unwrap_or_else(|| panic!("No registered property found with index = {property_index:?}. You must use the `define_property!` macro to create a registered property."));
337
338        property_value_store.should_create_partial_change(context)
339    }
340
341    /// Returns whether or not the property `P` is indexed.
342    ///
343    #[cfg(test)]
344    #[must_use]
345    pub fn is_property_indexed<P: Property<E>>(&self) -> bool {
346        self.items
347            .get(P::id())
348            .unwrap_or_else(|| panic!("No registered property {} found with id = {:?}. You must use the `define_property!` macro to create a registered property.", P::name(), P::id()))
349            .index_type()
350            != PropertyIndexType::Unindexed
351    }
352
353    /// Sets the index type for `P`. Passing `PropertyIndexType::Unindexed` removes any existing index for `P`.
354    ///
355    /// Equivalent multi-properties do not share index storage. Only the representative
356    /// multi-property for an equivalent set can be indexed.
357    pub fn set_property_indexed<P: IndexableProperty<E>>(&mut self, index_type: PropertyIndexType) {
358        if index_type != PropertyIndexType::Unindexed {
359            if let Some((representative_id, representative_name)) =
360                multi_property_id_for_property_type_id(E::id(), P::type_id())
361            {
362                if representative_id != P::id() {
363                    panic!(
364                        "Cannot index multi-property {} because it is equivalent to representative multi-property {}. Index the representative multi-property instead.",
365                        P::name(),
366                        representative_name
367                    );
368                }
369            }
370        }
371
372        let property_value_store = self.items
373            .get_mut(P::id())
374            .unwrap_or_else(|| panic!("No registered property {} found with id = {:?}. You must use the `define_property!` macro to create a registered property.", P::name(), P::id()));
375        let property_value_store = property_value_store
376            .as_any_mut()
377            .downcast_mut::<PropertyValueStoreCore<E, P>>()
378            .unwrap_or_else(|| {
379                panic!(
380                    "Property type at index {:?} does not match registered property type. You must use the `define_property!` macro to create a registered property.",
381                    P::id()
382                )
383            });
384        match index_type {
385            PropertyIndexType::Unindexed => {
386                property_value_store.index = None;
387            }
388            PropertyIndexType::FullIndex => {
389                if property_value_store.index_type() != PropertyIndexType::FullIndex {
390                    property_value_store.index = Some(Box::new(FullIndex::<E, P>::new()));
391                }
392            }
393            PropertyIndexType::ValueCountIndex => {
394                if property_value_store.index_type() != PropertyIndexType::ValueCountIndex {
395                    property_value_store.index = Some(Box::new(ValueCountIndex::<E, P>::new()));
396                }
397            }
398        }
399    }
400
401    /// Creates a stratified value change counter for tracked property `P` with strata `PL`.
402    ///
403    /// Returns the counter ID.
404    #[must_use]
405    pub fn create_value_change_counter<PL, P>(&mut self) -> usize
406    where
407        PL: PropertyList<E> + Eq + std::hash::Hash,
408        P: Property<E> + Eq + std::hash::Hash,
409    {
410        let property_value_store = self.get_mut::<P>();
411        property_value_store.add_value_change_counter(Box::new(StratifiedValueChangeCounter::<
412            E,
413            PL,
414            P,
415        >::new()))
416    }
417
418    /// Updates the index of the property having the given ID for any entities that have been added to the context
419    /// since the last time the index was updated.
420    pub fn index_unindexed_entities_for_property_id(
421        &mut self,
422        context: &Context,
423        property_id: usize,
424    ) {
425        self.items[property_id].index_unindexed_entities(context)
426    }
427
428    /// Updates all indexed properties for any entities that have been added since the last update.
429    pub fn index_unindexed_entities_for_all_properties(&mut self, context: &Context) {
430        for store in &mut self.items {
431            store.index_unindexed_entities(context);
432        }
433    }
434
435    #[must_use]
436    pub fn get_index_set_for_query_parts(
437        &self,
438        property_id: usize,
439        query_parts: &[&dyn Any],
440    ) -> IndexSetResult<'_, E> {
441        self.items[property_id].get_index_set_for_query_parts(query_parts)
442    }
443
444    #[must_use]
445    pub fn get_index_count_for_query_parts(
446        &self,
447        property_id: usize,
448        query_parts: &[&dyn Any],
449    ) -> IndexCountResult {
450        self.items[property_id].get_index_count_for_query_parts(query_parts)
451    }
452}
453
454#[cfg(test)]
455mod tests {
456    #![allow(dead_code)]
457    use std::any::Any;
458
459    use super::*;
460    use crate::entity::index::{IndexCountResult, IndexSetResult};
461    use crate::prelude::*;
462    use crate::{define_entity, define_property, with, Context};
463
464    define_entity!(Person);
465
466    define_property!(struct Age(u8), Person);
467    define_property!(
468        enum InfectionStatus {
469            Susceptible,
470            Infected,
471            Recovered,
472        },
473        Person,
474        default_const = InfectionStatus::Susceptible
475    );
476    define_property!(struct Vaccinated(bool), Person, default_const = Vaccinated(false));
477
478    #[test]
479    fn property_store_default_matches_new() {
480        let property_store = PropertyStore::<Person>::default();
481        assert_eq!(
482            property_store.items.len(),
483            get_registered_property_count::<Person>()
484        );
485    }
486
487    #[test]
488    fn test_get_property_store() {
489        let mut property_store = PropertyStore::new();
490
491        {
492            let ages: &mut PropertyValueStoreCore<_, Age> = property_store.get_mut();
493            ages.set(EntityId::<Person>::new(0), Age(12));
494            ages.set(EntityId::<Person>::new(1), Age(33));
495            ages.set(EntityId::<Person>::new(2), Age(44));
496
497            let infection_statuses: &mut PropertyValueStoreCore<_, InfectionStatus> =
498                property_store.get_mut();
499            infection_statuses.set(EntityId::<Person>::new(0), InfectionStatus::Susceptible);
500            infection_statuses.set(EntityId::<Person>::new(1), InfectionStatus::Susceptible);
501            infection_statuses.set(EntityId::<Person>::new(2), InfectionStatus::Infected);
502
503            let vaccine_status: &mut PropertyValueStoreCore<_, Vaccinated> =
504                property_store.get_mut();
505            vaccine_status.set(EntityId::<Person>::new(0), Vaccinated(true));
506            vaccine_status.set(EntityId::<Person>::new(1), Vaccinated(false));
507            vaccine_status.set(EntityId::<Person>::new(2), Vaccinated(true));
508        }
509
510        // Verify that `get` returns the expected values
511        {
512            let ages: &PropertyValueStoreCore<_, Age> = property_store.get();
513            assert_eq!(ages.get(EntityId::<Person>::new(0)), Age(12));
514            assert_eq!(ages.get(EntityId::<Person>::new(1)), Age(33));
515            assert_eq!(ages.get(EntityId::<Person>::new(2)), Age(44));
516
517            let infection_statuses: &PropertyValueStoreCore<_, InfectionStatus> =
518                property_store.get();
519            assert_eq!(
520                infection_statuses.get(EntityId::<Person>::new(0)),
521                InfectionStatus::Susceptible
522            );
523            assert_eq!(
524                infection_statuses.get(EntityId::<Person>::new(1)),
525                InfectionStatus::Susceptible
526            );
527            assert_eq!(
528                infection_statuses.get(EntityId::<Person>::new(2)),
529                InfectionStatus::Infected
530            );
531
532            let vaccine_status: &PropertyValueStoreCore<_, Vaccinated> = property_store.get();
533            assert_eq!(
534                vaccine_status.get(EntityId::<Person>::new(0)),
535                Vaccinated(true)
536            );
537            assert_eq!(
538                vaccine_status.get(EntityId::<Person>::new(1)),
539                Vaccinated(false)
540            );
541            assert_eq!(
542                vaccine_status.get(EntityId::<Person>::new(2)),
543                Vaccinated(true)
544            );
545        }
546    }
547
548    #[test]
549    fn test_index_query_results_for_property_store() {
550        let mut context = Context::new();
551        context.index_property::<Person, Age>();
552
553        let existing_value = Age(12);
554        let missing_value = Age(99);
555        let existing_query_parts = [&existing_value as &dyn Any];
556        let missing_query_parts = [&missing_value as &dyn Any];
557
558        let _ = context.add_entity(with!(Person, existing_value)).unwrap();
559        let _ = context.add_entity(with!(Person, existing_value)).unwrap();
560
561        let property_store = context.entity_store.get_property_store::<Person>();
562
563        // FullIndex + count
564        assert_eq!(
565            property_store.get_index_count_for_query_parts(Age::id(), &missing_query_parts,),
566            IndexCountResult::Count(0)
567        );
568        assert_eq!(
569            property_store.get_index_count_for_query_parts(Age::id(), &existing_query_parts,),
570            IndexCountResult::Count(2)
571        );
572
573        // FullIndex + set
574        assert!(matches!(
575            property_store.get_index_set_for_query_parts(Age::id(), &missing_query_parts,),
576            IndexSetResult::Empty
577        ));
578        assert!(matches!(
579            property_store.get_index_set_for_query_parts(
580                Age::id(),
581                &existing_query_parts,
582            ),
583            IndexSetResult::Set(set) if set.len() == 2
584        ));
585    }
586
587    #[test]
588    fn test_index_query_results_for_property_store_value_count_index() {
589        let mut context = Context::new();
590        context.index_property_counts::<Person, Age>();
591
592        let existing_value = Age(12);
593        let missing_value = Age(99);
594        let existing_query_parts = [&existing_value as &dyn Any];
595        let missing_query_parts = [&missing_value as &dyn Any];
596
597        let _ = context.add_entity(with!(Person, existing_value)).unwrap();
598        let _ = context.add_entity(with!(Person, existing_value)).unwrap();
599
600        let property_store = context.entity_store.get_property_store::<Person>();
601
602        // ValueCountIndex + count
603        assert_eq!(
604            property_store.get_index_count_for_query_parts(Age::id(), &missing_query_parts,),
605            IndexCountResult::Count(0)
606        );
607        assert_eq!(
608            property_store.get_index_count_for_query_parts(Age::id(), &existing_query_parts,),
609            IndexCountResult::Count(2)
610        );
611
612        // ValueCountIndex + set (unsupported)
613        assert!(matches!(
614            property_store.get_index_set_for_query_parts(Age::id(), &missing_query_parts,),
615            IndexSetResult::Unsupported
616        ));
617        assert!(matches!(
618            property_store.get_index_set_for_query_parts(Age::id(), &existing_query_parts,),
619            IndexSetResult::Unsupported
620        ));
621    }
622
623    #[test]
624    fn test_index_query_results_for_property_store_unindexed() {
625        let mut context = Context::new();
626        let existing_value = Age(12);
627        let missing_value = Age(99);
628        let existing_query_parts = [&existing_value as &dyn Any];
629        let missing_query_parts = [&missing_value as &dyn Any];
630
631        let _ = context.add_entity(with!(Person, existing_value)).unwrap();
632        let _ = context.add_entity(with!(Person, existing_value)).unwrap();
633
634        let property_store = context.entity_store.get_property_store::<Person>();
635
636        // Unindexed + count
637        assert_eq!(
638            property_store.get_index_count_for_query_parts(Age::id(), &missing_query_parts,),
639            IndexCountResult::Unsupported
640        );
641        assert_eq!(
642            property_store.get_index_count_for_query_parts(Age::id(), &existing_query_parts,),
643            IndexCountResult::Unsupported
644        );
645
646        // Unindexed + set
647        assert!(matches!(
648            property_store.get_index_set_for_query_parts(Age::id(), &missing_query_parts,),
649            IndexSetResult::Unsupported
650        ));
651        assert!(matches!(
652            property_store.get_index_set_for_query_parts(Age::id(), &existing_query_parts,),
653            IndexSetResult::Unsupported
654        ));
655    }
656}