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