1use std::any::TypeId;
2use std::sync::{Mutex, OnceLock};
3
4use seq_macro::seq;
5
6use crate::hashing::{one_shot_128, HashMap};
7use crate::people::multi_property::{static_reorder_by_keys, type_ids_to_multi_property_id};
8use crate::people::HashValueType;
9use crate::{Context, ContextPeopleExt, PersonProperty};
10
11pub trait Query: Copy + 'static {
17 fn setup(&self, context: &Context);
18 fn get_query(&self) -> Vec<(TypeId, HashValueType)>;
21
22 fn get_type_ids(&self) -> Vec<TypeId>;
24
25 fn multi_property_type_id(&self) -> Option<TypeId> {
27 static REGISTRY: OnceLock<Mutex<HashMap<TypeId, &'static Option<TypeId>>>> =
30 OnceLock::new();
31
32 let map = REGISTRY.get_or_init(|| Mutex::new(HashMap::default()));
33 let mut map = map.lock().unwrap();
34 let type_id = TypeId::of::<Self>();
35 let entry = *map.entry(type_id).or_insert_with(|| {
36 let mut types = self.get_type_ids();
37 types.sort_unstable();
38 Box::leak(Box::new(type_ids_to_multi_property_id(types.as_slice())))
39 });
40
41 *entry
42 }
43
44 fn multi_property_value_hash(&self) -> HashValueType;
45}
46
47impl Query for () {
48 fn setup(&self, _: &Context) {}
49
50 fn get_query(&self) -> Vec<(TypeId, HashValueType)> {
51 Vec::new()
52 }
53
54 fn get_type_ids(&self) -> Vec<TypeId> {
55 Vec::new()
56 }
57
58 fn multi_property_type_id(&self) -> Option<TypeId> {
59 None
60 }
61
62 fn multi_property_value_hash(&self) -> HashValueType {
63 let empty: &[u128] = &[];
64 one_shot_128(&empty)
65 }
66}
67
68impl<T1: PersonProperty> Query for (T1, T1::Value) {
70 fn setup(&self, context: &Context) {
71 context.register_property::<T1>();
72 }
73
74 fn get_query(&self) -> Vec<(TypeId, HashValueType)> {
75 let value = T1::make_canonical(self.1);
76 vec![(T1::type_id(), T1::hash_property_value(&value))]
77 }
78
79 fn get_type_ids(&self) -> Vec<TypeId> {
80 vec![T1::type_id()]
81 }
82
83 fn multi_property_type_id(&self) -> Option<TypeId> {
84 Some(T1::type_id())
87 }
88
89 fn multi_property_value_hash(&self) -> HashValueType {
90 T1::hash_property_value(&T1::make_canonical(self.1))
91 }
92}
93
94impl<T1: PersonProperty> Query for ((T1, T1::Value),) {
98 fn setup(&self, context: &Context) {
99 context.register_property::<T1>();
100 }
101
102 fn get_query(&self) -> Vec<(TypeId, HashValueType)> {
103 let value = T1::make_canonical(self.0 .1);
104 vec![(T1::type_id(), T1::hash_property_value(&value))]
105 }
106
107 fn get_type_ids(&self) -> Vec<TypeId> {
108 vec![T1::type_id()]
109 }
110
111 fn multi_property_type_id(&self) -> Option<TypeId> {
112 Some(T1::type_id())
115 }
116
117 fn multi_property_value_hash(&self) -> HashValueType {
118 T1::hash_property_value(&T1::make_canonical(self.0 .1))
119 }
120}
121
122macro_rules! impl_query {
123 ($ct:expr) => {
124 seq!(N in 0..$ct {
125 impl<
126 #(
127 T~N : PersonProperty,
128 )*
129 > Query for (
130 #(
131 (T~N, T~N::Value),
132 )*
133 )
134 {
135 fn setup(&self, context: &Context) {
136 #(
137 context.register_property::<T~N>();
138 )*
139 }
140
141 fn get_query(&self) -> Vec<(TypeId, HashValueType)> {
142 let mut ordered_items = vec![
143 #(
144 (T~N::type_id(), T~N::hash_property_value(&T~N::make_canonical(self.N.1))),
145 )*
146 ];
147 ordered_items.sort_by(|a, b| a.0.cmp(&b.0));
148 ordered_items
149 }
150
151 fn get_type_ids(&self) -> Vec<TypeId> {
152 vec![
153 #(
154 T~N::type_id(),
155 )*
156 ]
157 }
158
159 fn multi_property_value_hash(&self) -> HashValueType {
160 let keys: [&str; $ct] = [
173 #(
174 T~N::name(),
175 )*
176 ];
177 let mut values: [&Vec<u8>; $ct] = [
182 #(
183 &$crate::bincode::serde::encode_to_vec(self.N.1, bincode::config::standard()).unwrap(),
184 )*
185 ];
186 static_reorder_by_keys(&keys, &mut values);
187
188 let data = values.into_iter().flatten().copied().collect::<Vec<u8>>();
189 one_shot_128(&data.as_slice())
190 }
191 }
192 });
193 }
194}
195
196seq!(Z in 2..10 {
198 impl_query!(Z);
199});
200
201#[cfg(test)]
202mod tests {
203 #![allow(dead_code)]
204 use serde_derive::Serialize;
205
206 use crate::people::PeoplePlugin;
207 use crate::{
208 define_derived_property, define_multi_property, define_person_property, Context,
209 ContextPeopleExt, HashSetExt, PersonProperty,
210 };
211
212 define_person_property!(Age, u8);
213 define_person_property!(County, u32);
214 define_person_property!(Height, u32);
215
216 #[derive(Serialize, Copy, Clone, PartialEq, Eq, Debug)]
217 pub enum RiskCategoryValue {
218 High,
219 Low,
220 }
221
222 define_person_property!(RiskCategory, RiskCategoryValue);
223
224 #[test]
225 fn with_query_results() {
226 let mut context = Context::new();
227 let _ = context
228 .add_person((RiskCategory, RiskCategoryValue::High))
229 .unwrap();
230
231 context.with_query_results((RiskCategory, RiskCategoryValue::High), &mut |people| {
232 assert_eq!(people.len(), 1);
233 });
234 }
235
236 #[test]
237 fn with_query_results_empty() {
238 let context = Context::new();
239
240 context.with_query_results((RiskCategory, RiskCategoryValue::High), &mut |people| {
241 assert_eq!(people.len(), 0);
242 });
243 }
244
245 #[test]
246 fn query_people_count() {
247 let mut context = Context::new();
248 let _ = context
249 .add_person((RiskCategory, RiskCategoryValue::High))
250 .unwrap();
251
252 assert_eq!(
253 context.query_people_count((RiskCategory, RiskCategoryValue::High)),
254 1
255 );
256 }
257
258 #[test]
259 fn query_people_count_empty() {
260 let context = Context::new();
261
262 assert_eq!(
263 context.query_people_count((RiskCategory, RiskCategoryValue::High)),
264 0
265 );
266 }
267
268 #[test]
269 fn with_query_results_macro_index_first() {
270 let mut context = Context::new();
271 let _ = context
272 .add_person((RiskCategory, RiskCategoryValue::High))
273 .unwrap();
274 context.index_property(RiskCategory);
275 assert!(is_property_indexed::<RiskCategory>(&context));
276
277 context.with_query_results((RiskCategory, RiskCategoryValue::High), &mut |people| {
278 assert_eq!(people.len(), 1);
279 });
280 }
281
282 fn is_property_indexed<T: PersonProperty>(context: &Context) -> bool {
283 let container = context.get_data(PeoplePlugin);
284 container
285 .property_indexes
286 .borrow()
287 .get(&T::type_id())
288 .map(|index| index.is_indexed())
289 .unwrap_or(false)
290 }
291
292 #[test]
293 fn with_query_results_macro_index_second() {
294 let mut context = Context::new();
295 let _ = context.add_person((RiskCategory, RiskCategoryValue::High));
296
297 context.with_query_results((RiskCategory, RiskCategoryValue::High), &mut |people| {
298 assert_eq!(people.len(), 1);
299 });
300 assert!(!is_property_indexed::<RiskCategory>(&context));
301
302 context.index_property(RiskCategory);
303 assert!(is_property_indexed::<RiskCategory>(&context));
304
305 context.with_query_results((RiskCategory, RiskCategoryValue::High), &mut |people| {
306 assert_eq!(people.len(), 1);
307 });
308 }
309
310 #[test]
311 fn with_query_results_macro_change() {
312 let mut context = Context::new();
313 let person1 = context
314 .add_person((RiskCategory, RiskCategoryValue::High))
315 .unwrap();
316
317 context.with_query_results((RiskCategory, RiskCategoryValue::High), &mut |people| {
318 assert_eq!(people.len(), 1);
319 });
320
321 context.with_query_results((RiskCategory, RiskCategoryValue::Low), &mut |people| {
322 assert_eq!(people.len(), 0);
323 });
324
325 context.set_person_property(person1, RiskCategory, RiskCategoryValue::Low);
326 context.with_query_results((RiskCategory, RiskCategoryValue::High), &mut |people| {
327 assert_eq!(people.len(), 0);
328 });
329
330 context.with_query_results((RiskCategory, RiskCategoryValue::Low), &mut |people| {
331 assert_eq!(people.len(), 1);
332 });
333 }
334
335 #[test]
336 fn with_query_results_index_after_add() {
337 let mut context = Context::new();
338 let _ = context
339 .add_person((RiskCategory, RiskCategoryValue::High))
340 .unwrap();
341 context.index_property(RiskCategory);
342 assert!(is_property_indexed::<RiskCategory>(&context));
343 context.with_query_results((RiskCategory, RiskCategoryValue::High), &mut |people| {
344 assert_eq!(people.len(), 1);
345 });
346 }
347
348 #[test]
349 fn with_query_results_add_after_index() {
350 let mut context = Context::new();
351 let _ = context
352 .add_person((RiskCategory, RiskCategoryValue::High))
353 .unwrap();
354 context.index_property(RiskCategory);
355 assert!(is_property_indexed::<RiskCategory>(&context));
356 context.with_query_results((RiskCategory, RiskCategoryValue::High), &mut |people| {
357 assert_eq!(people.len(), 1);
358 });
359
360 let _ = context
361 .add_person((RiskCategory, RiskCategoryValue::High))
362 .unwrap();
363 context.with_query_results((RiskCategory, RiskCategoryValue::High), &mut |people| {
364 assert_eq!(people.len(), 2);
365 });
366 }
367
368 #[test]
369 fn add_after_index_without_query() {
371 let mut context = Context::new();
372 let _ = context.add_person(()).unwrap();
373 context.index_property(RiskCategory);
374 }
375
376 #[test]
377 #[should_panic(expected = "Property not initialized")]
378 fn with_query_results_add_after_index_panic() {
380 let mut context = Context::new();
381 context.add_person(()).unwrap();
382 context.index_property(RiskCategory);
383 context.with_query_results((RiskCategory, RiskCategoryValue::High), &mut |_people| {});
384 }
385
386 #[test]
387 fn with_query_results_cast_value() {
388 let mut context = Context::new();
389 let _ = context.add_person((Age, 42)).unwrap();
390
391 context.with_query_results((Age, 42), &mut |people| {
393 assert_eq!(people.len(), 1);
394 });
395 }
396
397 #[test]
398 fn with_query_results_intersection() {
399 let mut context = Context::new();
400 let _ = context
401 .add_person(((Age, 42), (RiskCategory, RiskCategoryValue::High)))
402 .unwrap();
403 let _ = context
404 .add_person(((Age, 42), (RiskCategory, RiskCategoryValue::Low)))
405 .unwrap();
406 let _ = context
407 .add_person(((Age, 40), (RiskCategory, RiskCategoryValue::Low)))
408 .unwrap();
409
410 context.with_query_results(
411 ((Age, 42), (RiskCategory, RiskCategoryValue::High)),
412 &mut |people| {
413 assert_eq!(people.len(), 1);
414 },
415 );
416 }
417
418 #[test]
419 fn with_query_results_intersection_non_macro() {
420 let mut context = Context::new();
421 let _ = context
422 .add_person(((Age, 42), (RiskCategory, RiskCategoryValue::High)))
423 .unwrap();
424 let _ = context
425 .add_person(((Age, 42), (RiskCategory, RiskCategoryValue::Low)))
426 .unwrap();
427 let _ = context
428 .add_person(((Age, 40), (RiskCategory, RiskCategoryValue::Low)))
429 .unwrap();
430
431 context.with_query_results(
432 ((Age, 42), (RiskCategory, RiskCategoryValue::High)),
433 &mut |people| {
434 assert_eq!(people.len(), 1);
435 },
436 );
437 }
438
439 #[test]
440 fn with_query_results_intersection_one_indexed() {
441 let mut context = Context::new();
442 let _ = context
443 .add_person(((Age, 42), (RiskCategory, RiskCategoryValue::High)))
444 .unwrap();
445 let _ = context
446 .add_person(((Age, 42), (RiskCategory, RiskCategoryValue::Low)))
447 .unwrap();
448 let _ = context
449 .add_person(((Age, 40), (RiskCategory, RiskCategoryValue::Low)))
450 .unwrap();
451
452 context.index_property(Age);
453 context.with_query_results(
454 ((Age, 42), (RiskCategory, RiskCategoryValue::High)),
455 &mut |people| {
456 assert_eq!(people.len(), 1);
457 },
458 );
459 }
460
461 #[test]
462 fn query_derived_prop() {
463 let mut context = Context::new();
464 define_derived_property!(Senior, bool, [Age], |age| age >= 65);
465
466 let person = context.add_person((Age, 64)).unwrap();
467 let _ = context.add_person((Age, 88));
468
469 let mut not_seniors = Vec::new();
470 context.with_query_results((Senior, false), &mut |people| {
471 not_seniors = people.to_owned_vec()
472 });
473 let mut seniors = Vec::new();
474 context.with_query_results((Senior, true), &mut |people| {
475 seniors = people.to_owned_vec();
476 });
477 assert_eq!(seniors.len(), 1, "One senior");
478 assert_eq!(not_seniors.len(), 1, "One non-senior");
479
480 context.set_person_property(person, Age, 65);
481
482 context.with_query_results((Senior, false), &mut |people| {
483 not_seniors = people.to_owned_vec()
484 });
485 context.with_query_results((Senior, true), &mut |people| {
486 seniors = people.to_owned_vec()
487 });
488
489 assert_eq!(seniors.len(), 2, "Two seniors");
490 assert_eq!(not_seniors.len(), 0, "No non-seniors");
491 }
492
493 #[test]
494 fn query_derived_prop_with_index() {
495 let mut context = Context::new();
496 define_derived_property!(Senior, bool, [Age], |age| age >= 65);
497
498 context.index_property(Senior);
499 let person = context.add_person((Age, 64)).unwrap();
500 let _ = context.add_person((Age, 88));
501
502 let mut not_seniors = Vec::new();
504 context.with_query_results((Senior, false), &mut |people| {
505 not_seniors = people.to_owned_vec()
506 });
507 let mut seniors = Vec::new();
508 context.with_query_results((Senior, true), &mut |people| {
509 seniors = people.to_owned_vec()
510 });
511 assert_eq!(seniors.len(), 1, "One senior");
512 assert_eq!(not_seniors.len(), 1, "One non-senior");
513
514 context.set_person_property(person, Age, 65);
515
516 context.with_query_results((Senior, false), &mut |people| {
517 not_seniors = people.to_owned_vec()
518 });
519 context.with_query_results((Senior, true), &mut |people| {
520 seniors = people.to_owned_vec()
521 });
522
523 assert_eq!(seniors.len(), 2, "Two seniors");
524 assert_eq!(not_seniors.len(), 0, "No non-seniors");
525 }
526
527 define_multi_property!(ACH, (Age, County, Height));
529 define_multi_property!(CH, (County, Height));
530
531 #[test]
532 fn query_derived_prop_with_optimized_index() {
533 let mut context = Context::new();
534 define_derived_property!(
536 Ach,
537 (u8, u32, u32),
538 [Age, County, Height],
539 |age, county, height| { (age, county, height) }
540 );
541
542 let _ = context.add_person(((Age, 64), (County, 2), (Height, 120)));
544 let _ = context.add_person(((Age, 88), (County, 2), (Height, 130)));
545 let p2 = context
546 .add_person(((Age, 8), (County, 1), (Height, 140)))
547 .unwrap();
548 let p3 = context
549 .add_person(((Age, 28), (County, 1), (Height, 140)))
550 .unwrap();
551 let p4 = context
552 .add_person(((Age, 28), (County, 2), (Height, 160)))
553 .unwrap();
554 let p5 = context
555 .add_person(((Age, 28), (County, 2), (Height, 160)))
556 .unwrap();
557
558 context.with_query_results((Ach, (28, 2, 160)), &mut |people| {
560 assert_eq!(people.len(), 2, "Should have 2 matches");
561 assert!(people.contains(&p4));
562 assert!(people.contains(&p5));
563 });
564
565 context.with_query_results(((Age, 28), (County, 2), (Height, 160)), &mut |people| {
567 assert_eq!(people.len(), 2, "Should have 2 matches");
568 assert!(people.contains(&p4));
569 assert!(people.contains(&p5));
570 });
571
572 context.with_query_results(((County, 2), (Height, 160), (Age, 28)), &mut |people| {
574 assert_eq!(people.len(), 2, "Should have 2 matches");
575 assert!(people.contains(&p4));
576 assert!(people.contains(&p5));
577 });
578
579 context.with_query_results(((Height, 160), (County, 2), (Age, 28)), &mut |people| {
581 assert_eq!(people.len(), 2, "Should have 2 matches");
582 assert!(people.contains(&p4));
583 assert!(people.contains(&p5));
584 });
585
586 context.with_query_results(((Height, 140), (County, 1), (Age, 28)), &mut |people| {
588 assert_eq!(people.len(), 1, "Should have 1 matches");
589 assert!(people.contains(&p3));
590 });
591
592 context.set_person_property(p2, Age, 28);
593 context.with_query_results(((Height, 140), (County, 1), (Age, 28)), &mut |people| {
595 assert_eq!(people.len(), 2, "Should have 2 matches");
596 assert!(people.contains(&p2));
597 assert!(people.contains(&p3));
598 });
599
600 context.with_query_results(((Height, 140), (County, 1)), &mut |people| {
601 assert_eq!(people.len(), 2, "Should have 2 matches");
602 assert!(people.contains(&p2));
603 assert!(people.contains(&p3));
604 });
605 }
606}