1use std::ops::Range;
3use std::sync::Arc;
4
5use chrono::Duration;
6
7use crate::{EventStoreInner, TimeUnit};
8
9#[must_use = "query builders do nothing unless consumed"]
14pub struct Query {
15 store: Arc<EventStoreInner>,
16 event_id: String,
17}
18
19impl Query {
20 pub(crate) fn new(store: Arc<EventStoreInner>, event_id: String) -> Self {
24 Self { store, event_id }
25 }
26
27 pub(crate) fn last(self, n: usize, unit: TimeUnit) -> RangeQuery {
28 RangeQuery::new(self.store, self.event_id, unit, 0..n)
29 }
30
31 pub fn last_seconds(self, n: usize) -> RangeQuery {
33 self.last(n, TimeUnit::Seconds)
34 }
35
36 pub fn last_minutes(self, n: usize) -> RangeQuery {
50 self.last(n, TimeUnit::Minutes)
51 }
52
53 pub fn last_hours(self, n: usize) -> RangeQuery {
55 self.last(n, TimeUnit::Hours)
56 }
57
58 pub fn last_days(self, n: usize) -> RangeQuery {
72 self.last(n, TimeUnit::Days)
73 }
74
75 pub fn last_weeks(self, n: usize) -> RangeQuery {
77 self.last(n, TimeUnit::Weeks)
78 }
79
80 pub fn last_months(self, n: usize) -> RangeQuery {
82 self.last(n, TimeUnit::Months)
83 }
84
85 pub fn last_years(self, n: usize) -> RangeQuery {
87 self.last(n, TimeUnit::Years)
88 }
89
90 pub fn ever(self) -> RangeQuery {
95 self.last(usize::MAX, TimeUnit::Ever)
98 }
99
100 pub fn days(self, range: Range<usize>) -> RangeQuery {
102 RangeQuery::new(self.store, self.event_id, TimeUnit::Days, range)
103 }
104
105 pub fn days_from(self, offset: usize) -> RangeQuery {
109 RangeQuery::new(
110 self.store,
111 self.event_id,
112 TimeUnit::Days,
113 offset..usize::MAX,
114 )
115 }
116
117 pub fn last_seen(self) -> Option<Duration> {
138 let clock_now = self.store.clock_now();
139 let counter_arc = self.store.get_counter_for_query(&self.event_id)?;
140 let mut counter = counter_arc.lock().unwrap();
141
142 counter.advance_if_needed(clock_now);
143
144 let configs = self.store.config.configs();
146
147 for interval_idx in 0..configs.len() {
149 let config = &configs[interval_idx];
150 let time_unit = config.time_unit();
151
152 if let Some(bucket_idx) = counter.last_seen_in(time_unit) {
153 let interval_start = counter.interval_start(time_unit).expect(
157 "BUG: Interval must exist - we just retrieved config from counter's intervals",
158 );
159
160 if interval_idx == 0 {
162 let estimate = time_unit.bucket_midway(clock_now, interval_start, bucket_idx);
163 return Some(clock_now - estimate);
164 }
165
166 let prev_config = &configs[interval_idx - 1];
169
170 let prev_interval_start = counter.interval_start(prev_config.time_unit()).expect(
174 "BUG: Smaller interval must exist - we just retrieved prev_config from counter's intervals",
175 );
176 let prev_coverage_end = prev_config.first_moment_ever(prev_interval_start);
177
178 let coverage_end_bucket = time_unit
180 .bucket_idx(interval_start, prev_coverage_end)
181 .unwrap_or_default();
182
183 if bucket_idx <= coverage_end_bucket {
185 let gap_earliest = time_unit.bucket_start(clock_now, bucket_idx);
191 let gap_latest = prev_coverage_end;
193
194 let gap_midpoint = gap_earliest + ((gap_latest - gap_earliest) / 2);
196
197 return Some(clock_now - gap_midpoint);
199 }
200
201 let estimate = time_unit.bucket_midway(clock_now, interval_start, bucket_idx);
203 return Some(clock_now - estimate);
204 }
205 }
206 None
207 }
208
209 pub fn first_seen(self) -> Option<Duration> {
231 let clock_now = self.store.clock_now();
232 let counter_arc = self.store.get_counter_for_query(&self.event_id)?;
233 let mut counter = counter_arc.lock().unwrap();
234 counter.advance_if_needed(clock_now);
235
236 let configs = self.store.config.configs();
238
239 for interval_idx in (0..configs.len()).rev() {
241 let config = &configs[interval_idx];
242 let time_unit = config.time_unit();
243 if let Some(bucket_idx) = counter.first_seen_in(time_unit) {
244 let interval_start = counter.interval_start(time_unit).expect(
248 "BUG: Interval must exist - we just retrieved config from counter's intervals",
249 );
250 if interval_idx == 0 {
252 let midway = time_unit.bucket_midway(clock_now, interval_start, bucket_idx);
254 return Some(clock_now - midway);
255 }
256
257 let next_config = &configs[interval_idx - 1];
259
260 let next_interval_start = counter.interval_start(next_config.time_unit()).expect(
266 "BUG: Smaller interval must exist - we just retrieved next_config from counter's intervals",
267 );
268
269 let starting_moment = next_config.first_moment_ever(next_interval_start);
270 let starting_bucket = time_unit
271 .bucket_idx(interval_start, starting_moment)
272 .unwrap_or_default()
273 + 1;
274
275 if bucket_idx > starting_bucket {
278 let midway = time_unit.bucket_midway(clock_now, interval_start, bucket_idx);
280 return Some(clock_now - midway);
281 }
282 let this_count = counter
285 .sum_range(time_unit, 0..starting_bucket + 1)
286 .unwrap_or(0);
287 let next_count = counter
288 .sum_range(next_config.time_unit(), 0..usize::MAX)
289 .unwrap_or(0);
290
291 if this_count > next_count {
294 let earliest = clock_now - time_unit.bucket_end(interval_start, bucket_idx);
296 let latest = clock_now - next_config.first_moment_ever(next_interval_start);
297 return Some((earliest + latest) / 2);
298 }
299 }
302 }
303
304 None
305 }
306
307 pub fn first_seen_in(self, time_unit: TimeUnit) -> Option<Duration> {
325 let clock_now = self.store.clock_now();
326 let time_unit = self.store.config.specified_time_unit(time_unit);
327 let counter_arc = self.store.get_counter_for_query(&self.event_id)?;
328 let mut counter = counter_arc.lock().unwrap();
329 counter.advance_if_needed(clock_now);
330
331 if let Some(bucket_idx) = counter.first_seen_in(time_unit) {
332 let duration = time_unit.duration() * bucket_idx as i32;
334 Some(duration)
335 } else {
336 None
337 }
338 }
339}
340
341#[must_use = "query builders do nothing unless consumed"]
345pub struct RangeQuery {
346 store: Arc<EventStoreInner>,
347 event_id: String,
348 time_unit: TimeUnit,
349 range: Range<usize>,
350}
351
352impl RangeQuery {
353 fn new(
355 store: Arc<EventStoreInner>,
356 event_id: String,
357 time_unit: TimeUnit,
358 range: Range<usize>,
359 ) -> Self {
360 Self {
361 store,
362 event_id,
363 time_unit,
364 range,
365 }
366 }
367
368 pub fn take(mut self, n: usize) -> Self {
370 self.range.end = self.range.start + n;
371 self
372 }
373
374 pub fn sum(self) -> Option<u32> {
390 let clock_now = self.store.clock_now();
391 let counter_arc = self.store.get_counter_for_query(&self.event_id)?;
392 let time_unit = self.store.config.specified_time_unit(self.time_unit);
393 let mut counter = counter_arc.lock().unwrap();
394 counter.advance_if_needed(clock_now);
395 counter.sum_range(time_unit, self.range)
396 }
397
398 pub fn average(self) -> Option<f64> {
415 let count = (self.range.end - self.range.start) as f64;
416 let sum = self.sum()?;
417 match sum {
418 0 => Some(0.0),
419 s => {
420 if count > 0.0 {
421 Some(s as f64 / count)
422 } else {
423 None
424 }
425 }
426 }
427 }
428
429 pub fn average_nonzero(self) -> Option<f64> {
434 let buckets = self.into_buckets();
435 if buckets.is_empty() {
436 return None;
437 }
438
439 let non_zero: Vec<u32> = buckets.iter().copied().filter(|&x| x > 0).collect();
440 if non_zero.is_empty() {
441 return None;
442 }
443
444 let sum: u64 = non_zero
445 .iter()
446 .fold(0u64, |acc, &val| acc.saturating_add(val as u64));
447 let avg = sum as f64 / non_zero.len() as f64;
448 Some(avg)
449 }
450
451 pub fn count_nonzero(self) -> Option<usize> {
455 let buckets = self.into_buckets();
456 if buckets.is_empty() {
457 return None;
458 }
459
460 let count = buckets.iter().filter(|&&x| x > 0).count();
461 Some(count)
462 }
463
464 pub fn last_seen(self) -> Option<usize> {
469 let clock_now = self.store.clock_now();
470 let time_unit = self.store.config.specified_time_unit(self.time_unit);
471 let counter_arc = self.store.get_counter_for_query(&self.event_id)?;
472 let mut counter = counter_arc.lock().unwrap();
473 counter.advance_if_needed(clock_now);
474 counter.last_seen_in(time_unit)
475 }
476
477 pub fn first_seen(self) -> Option<usize> {
482 let clock_now = self.store.clock_now();
483 let time_unit = self.store.config.specified_time_unit(self.time_unit);
484 let counter_arc = self.store.get_counter_for_query(&self.event_id)?;
485 let mut counter = counter_arc.lock().unwrap();
486 counter.advance_if_needed(clock_now);
487 counter.first_seen_in(time_unit)
488 }
489
490 pub fn into_buckets(self) -> Vec<u32> {
492 let clock_now = self.store.clock_now();
493 let time_unit = self.store.config.specified_time_unit(self.time_unit);
494
495 let counter_arc = match self.store.get_counter_for_query(&self.event_id) {
496 Some(arc) => arc,
497 None => return Vec::new(),
498 };
499
500 let mut counter = counter_arc.lock().unwrap();
501 counter.advance_if_needed(clock_now);
502
503 let interval = match counter.interval(time_unit) {
504 Some(interval) => interval,
505 None => return Vec::new(),
506 };
507
508 let end = self.range.end.min(interval.bucket_count());
509
510 (self.range.start..end)
511 .filter_map(|i| interval.bucket_value(i))
512 .collect()
513 }
514}
515
516#[must_use = "query builders do nothing unless consumed"]
518pub struct MultiQuery {
519 store: Arc<EventStoreInner>,
520 event_ids: Vec<String>,
521}
522
523impl MultiQuery {
524 pub(crate) fn new(store: Arc<EventStoreInner>, event_ids: Vec<String>) -> Self {
526 Self { store, event_ids }
527 }
528
529 pub(crate) fn last(self, n: usize, unit: TimeUnit) -> MultiRangeQuery {
530 MultiRangeQuery::new(self.store, self.event_ids, unit, 0..n)
531 }
532
533 pub fn last_seconds(self, n: usize) -> MultiRangeQuery {
535 self.last(n, TimeUnit::Seconds)
536 }
537
538 pub fn last_minutes(self, n: usize) -> MultiRangeQuery {
540 self.last(n, TimeUnit::Minutes)
541 }
542
543 pub fn last_hours(self, n: usize) -> MultiRangeQuery {
545 self.last(n, TimeUnit::Hours)
546 }
547
548 pub fn last_days(self, n: usize) -> MultiRangeQuery {
550 self.last(n, TimeUnit::Days)
551 }
552
553 pub fn last_weeks(self, n: usize) -> MultiRangeQuery {
555 self.last(n, TimeUnit::Weeks)
556 }
557
558 pub fn last_months(self, n: usize) -> MultiRangeQuery {
560 self.last(n, TimeUnit::Months)
561 }
562
563 pub fn last_years(self, n: usize) -> MultiRangeQuery {
565 self.last(n, TimeUnit::Years)
566 }
567
568 pub fn ever(self) -> MultiRangeQuery {
570 self.last(usize::MAX, TimeUnit::Ever)
573 }
574}
575
576#[must_use = "query builders do nothing unless consumed"]
578pub struct MultiRangeQuery {
579 store: Arc<EventStoreInner>,
580 event_ids: Vec<String>,
581 time_unit: TimeUnit,
582 range: Range<usize>,
583}
584
585impl MultiRangeQuery {
586 fn new(
587 store: Arc<EventStoreInner>,
588 event_ids: Vec<String>,
589 time_unit: TimeUnit,
590 range: Range<usize>,
591 ) -> Self {
592 Self {
593 store,
594 event_ids,
595 time_unit,
596 range,
597 }
598 }
599
600 pub fn sum(self) -> Option<u32> {
604 let clock_now = self.store.clock_now();
605
606 let mut total = 0u32;
607 let mut found_any = false;
608 let time_unit = self.store.config.specified_time_unit(self.time_unit);
609 for event_id in &self.event_ids {
610 if let Some(counter_arc) = self.store.get_counter_for_query(event_id) {
611 let mut counter = counter_arc.lock().unwrap();
612 counter.advance_if_needed(clock_now);
613 if let Some(sum) = counter.sum_range(time_unit, self.range.clone()) {
614 total = total.saturating_add(sum);
615 found_any = true;
616 }
617 }
618 }
619
620 if found_any {
621 Some(total)
622 } else {
623 None
624 }
625 }
626
627 pub fn average(self) -> Option<f64> {
629 let count = (self.range.end - self.range.start) as f64;
630 let sum = self.sum();
631 match sum {
632 None => None,
633 Some(0) => Some(0.0),
634 Some(s) => {
635 if count > 0.0 {
636 Some(s as f64 / count)
637 } else {
638 None
639 }
640 }
641 }
642 }
643}
644
645#[must_use = "query builders do nothing unless consumed"]
647pub struct RatioQuery {
648 store: Arc<EventStoreInner>,
649 numerator_id: String,
650 denominator_id: String,
651}
652
653impl RatioQuery {
654 pub(crate) fn new(
656 store: Arc<EventStoreInner>,
657 numerator_id: String,
658 denominator_id: String,
659 ) -> Self {
660 Self {
661 store,
662 numerator_id,
663 denominator_id,
664 }
665 }
666
667 pub(crate) fn last(self, n: usize, unit: TimeUnit) -> Option<f64> {
668 self.calculate_ratio(unit, 0..n)
669 }
670
671 pub fn last_seconds(self, n: usize) -> Option<f64> {
673 self.last(n, TimeUnit::Seconds)
674 }
675
676 pub fn last_minutes(self, n: usize) -> Option<f64> {
678 self.last(n, TimeUnit::Minutes)
679 }
680
681 pub fn last_hours(self, n: usize) -> Option<f64> {
683 self.last(n, TimeUnit::Hours)
684 }
685
686 pub fn last_days(self, n: usize) -> Option<f64> {
688 self.last(n, TimeUnit::Days)
689 }
690
691 pub fn last_weeks(self, n: usize) -> Option<f64> {
693 self.last(n, TimeUnit::Weeks)
694 }
695
696 pub fn last_months(self, n: usize) -> Option<f64> {
698 self.last(n, TimeUnit::Months)
699 }
700
701 pub fn last_years(self, n: usize) -> Option<f64> {
703 self.last(n, TimeUnit::Years)
704 }
705
706 pub fn ever(self) -> Option<f64> {
708 self.last(usize::MAX, TimeUnit::Ever)
710 }
711
712 fn calculate_ratio(self, time_unit: TimeUnit, range: Range<usize>) -> Option<f64> {
713 let clock_now = self.store.clock_now();
714
715 let time_unit = self.store.config.specified_time_unit(time_unit);
717
718 let numerator = self.store.get_counter_for_query(&self.numerator_id)?;
720 let mut numerator_counter = numerator.lock().unwrap();
721 numerator_counter.advance_if_needed(clock_now);
722
723 let denominator = self.store.get_counter_for_query(&self.denominator_id)?;
724 let mut denominator_counter = denominator.lock().unwrap();
725 denominator_counter.advance_if_needed(clock_now);
726
727 let numerator_sum = numerator_counter
728 .sum_range(time_unit, range.clone())
729 .unwrap_or(0);
730 let denominator_sum = denominator_counter
731 .sum_range(time_unit, range.clone())
732 .unwrap_or(0);
733
734 if denominator_sum == 0 {
735 None } else {
737 Some(numerator_sum as f64 / denominator_sum as f64)
738 }
739 }
740}
741
742#[must_use = "query builders do nothing unless consumed"]
744pub struct DeltaQuery {
745 store: Arc<EventStoreInner>,
746 positive_id: String,
747 negative_id: String,
748}
749
750impl DeltaQuery {
751 pub(crate) fn new(
753 store: Arc<EventStoreInner>,
754 positive_id: String,
755 negative_id: String,
756 ) -> Self {
757 Self {
758 store,
759 positive_id,
760 negative_id,
761 }
762 }
763
764 pub(crate) fn last(self, n: usize, unit: TimeUnit) -> DeltaRangeQuery {
765 DeltaRangeQuery::new(self.store, self.positive_id, self.negative_id, unit, 0..n)
766 }
767
768 pub fn last_seconds(self, n: usize) -> DeltaRangeQuery {
770 self.last(n, TimeUnit::Seconds)
771 }
772
773 pub fn last_minutes(self, n: usize) -> DeltaRangeQuery {
775 self.last(n, TimeUnit::Minutes)
776 }
777
778 pub fn last_hours(self, n: usize) -> DeltaRangeQuery {
780 self.last(n, TimeUnit::Hours)
781 }
782
783 pub fn last_days(self, n: usize) -> DeltaRangeQuery {
785 self.last(n, TimeUnit::Days)
786 }
787
788 pub fn last_weeks(self, n: usize) -> DeltaRangeQuery {
790 self.last(n, TimeUnit::Weeks)
791 }
792
793 pub fn last_months(self, n: usize) -> DeltaRangeQuery {
795 self.last(n, TimeUnit::Months)
796 }
797
798 pub fn last_years(self, n: usize) -> DeltaRangeQuery {
800 self.last(n, TimeUnit::Years)
801 }
802
803 pub fn ever(self) -> DeltaRangeQuery {
805 self.last(usize::MAX, TimeUnit::Ever)
807 }
808}
809
810#[must_use = "query builders do nothing unless consumed"]
812pub struct DeltaRangeQuery {
813 store: Arc<EventStoreInner>,
814 positive_id: String,
815 negative_id: String,
816 time_unit: TimeUnit,
817 range: Range<usize>,
818}
819
820impl DeltaRangeQuery {
821 fn new(
822 store: Arc<EventStoreInner>,
823 positive_id: String,
824 negative_id: String,
825 time_unit: TimeUnit,
826 range: Range<usize>,
827 ) -> Self {
828 Self {
829 store,
830 positive_id,
831 negative_id,
832 time_unit,
833 range,
834 }
835 }
836
837 pub fn sum(self) -> i64 {
842 let clock_now = self.store.clock_now();
843
844 let positive_counter = self.store.get_counter_for_query(&self.positive_id);
846 let negative_counter = self.store.get_counter_for_query(&self.negative_id);
847
848 let time_unit = self.store.config.specified_time_unit(self.time_unit);
849
850 let positive_sum = match positive_counter {
851 Some(counter_arc) => {
852 let mut counter = counter_arc.lock().unwrap();
853 counter.advance_if_needed(clock_now);
854 counter
855 .sum_range(time_unit, self.range.clone())
856 .unwrap_or(0) as i64
857 }
858 None => 0,
859 };
860
861 let negative_sum = match negative_counter {
862 Some(counter_arc) => {
863 let mut counter = counter_arc.lock().unwrap();
864 counter.advance_if_needed(clock_now);
865 counter
866 .sum_range(time_unit, self.range.clone())
867 .unwrap_or(0) as i64
868 }
869 None => 0,
870 };
871
872 positive_sum - negative_sum
873 }
874
875 pub fn average(self) -> f64 {
877 let count = (self.range.end - self.range.start) as f64;
878 let sum = self.sum();
879 if count > 0.0 {
880 sum as f64 / count
881 } else {
882 0.0
883 }
884 }
885}
886
887#[cfg(test)]
888mod tests {
889
890 use std::sync::Arc;
891
892 use chrono::{DateTime, Datelike, Duration, TimeZone, Utc};
893
894 use crate::{Clock, EventStore, TestClock, TimeUnit};
895
896 #[test]
897 fn test_query_last_days_sum() {
898 let store = EventStore::new();
899 store.record_count("event", 5);
900 store.record_count("event", 3);
901
902 let sum = store.query("event").last_days(1).sum();
903 assert_eq!(sum, Some(8));
904 }
905
906 #[test]
907 fn test_query_nonexistent_event() {
908 let store = EventStore::new();
909 let sum = store.query("nonexistent").last_days(1).sum();
910 assert_eq!(sum, None);
911 }
912
913 #[test]
914 fn test_range_query_average() {
915 let store = EventStore::new();
916 store.record_count("event", 10);
917
918 let avg = store.query("event").last_days(2).average();
919 assert_eq!(avg, Some(5.0));
921 }
922
923 #[test]
924 fn test_range_query_take() {
925 let store = EventStore::new();
926 store.record_count("event", 5);
927
928 let sum = store.query("event").days_from(0).take(1).sum();
929 assert_eq!(sum, Some(5));
930 }
931
932 #[test]
933 fn test_multi_query_sum() {
934 let store = EventStore::new();
935 store.record_count("event1", 5);
936 store.record_count("event2", 3);
937
938 let sum = store.query_many(&["event1", "event2"]).last_days(1).sum();
939 assert_eq!(sum, Some(8));
940 }
941
942 #[test]
943 fn test_ratio_query() {
944 let store = EventStore::new();
945 store.record_count("num", 6);
946 store.record_count("denom", 3);
947
948 let ratio = store.query_ratio("num", "denom").last_days(1);
949 assert_eq!(ratio, Some(2.0));
950 }
951
952 #[test]
953 fn test_ratio_query_division_by_zero() {
954 let store = EventStore::new();
955 store.record_count("num", 6);
956 let ratio = store.query_ratio("num", "denom").last_days(1);
959 assert_eq!(ratio, None); }
961
962 #[test]
963 fn test_delta_query_positive() {
964 let store = EventStore::new();
965 store.record_count("pos", 10);
966 store.record_count("neg", 3);
967
968 let delta = store.query_delta("pos", "neg").last_days(1).sum();
969 assert_eq!(delta, 7);
970 }
971
972 #[test]
973 fn test_delta_query_negative() {
974 let store = EventStore::new();
975 store.record_count("pos", 3);
976 store.record_count("neg", 10);
977
978 let delta = store.query_delta("pos", "neg").last_days(1).sum();
979 assert_eq!(delta, -7); }
981
982 #[test]
983 fn test_delta_query_zero() {
984 let store = EventStore::new();
985 let delta = store.query_delta("pos", "neg").last_days(1).sum();
986 assert_eq!(delta, 0);
987 }
988
989 #[test]
990 fn test_count_nonzero() {
991 let store = EventStore::new();
992 store.record_count("event", 5);
993 let count = store.query("event").last_days(7).count_nonzero();
996 assert_eq!(count, Some(1));
997 }
998
999 #[test]
1000 fn test_average_nonzero() {
1001 let store = EventStore::new();
1002 store.record_count("event", 10);
1003
1004 let avg = store.query("event").last_days(7).average_nonzero();
1005 assert_eq!(avg, Some(10.0));
1007 }
1008
1009 #[test]
1010 fn test_ever_uses_longest_time_unit() {
1011 let store = EventStore::new();
1013 store.record_count("event", 100);
1014
1015 let sum = store.query("event").ever().sum();
1017 assert_eq!(sum, Some(100));
1018 }
1019
1020 #[test]
1021 fn test_ever_with_nonexistent_event() {
1022 let store = EventStore::new();
1024
1025 let sum = store.query("nonexistent").ever().sum();
1026 assert_eq!(sum, None);
1027 }
1028
1029 #[test]
1030 fn test_ever_uses_time_unit_ever_variant() {
1031 let store = EventStore::new();
1033 store.record_count("event", 100);
1034
1035 let query = store.query("event").ever();
1037
1038 let sum = query.sum();
1041 assert_eq!(sum, Some(100));
1042 }
1043
1044 #[test]
1045 fn test_multi_query_ever() {
1046 let store = EventStore::new();
1047 store.record_count("event1", 50);
1048 store.record_count("event2", 75);
1049
1050 let sum = store.query_many(&["event1", "event2"]).ever().sum();
1051 assert_eq!(sum, Some(125));
1052 }
1053
1054 #[test]
1055 fn test_ratio_query_ever() {
1056 let store = EventStore::new();
1057 store.record_count("num", 100);
1058 store.record_count("denom", 25);
1059
1060 let ratio = store.query_ratio("num", "denom").ever();
1061 assert_eq!(ratio, Some(4.0));
1062 }
1063
1064 #[test]
1065 fn test_delta_query_ever() {
1066 let store = EventStore::new();
1067 store.record_count("pos", 150);
1068 store.record_count("neg", 50);
1069
1070 let delta = store.query_delta("pos", "neg").ever().sum();
1071 assert_eq!(delta, 100);
1072 }
1073
1074 #[test]
1075 fn test_multi_range_query_handles_large_values() {
1076 let store = EventStore::new();
1077
1078 store.record_count("event1", u32::MAX);
1080 store.record_count("event2", u32::MAX);
1081 store.record_count("event3", u32::MAX);
1082
1083 let sum = store
1084 .query_many(&["event1", "event2", "event3"])
1085 .last_days(1)
1086 .sum();
1087
1088 assert_eq!(sum, Some(u32::MAX));
1090 }
1091
1092 #[test]
1093 fn test_average_nonzero_handles_large_values() {
1094 use crate::store::builder::EventStoreBuilder;
1095
1096 let store = EventStoreBuilder::new().track_days(5).build().unwrap();
1097
1098 store.record_count("event", u32::MAX / 3);
1101 store.record_count("event", u32::MAX / 3);
1102 store.record_count("event", u32::MAX / 3);
1103
1104 let avg = store.query("event").last_days(5).average_nonzero();
1105
1106 assert!(avg.is_some());
1108 let avg_value = avg.unwrap();
1109 assert_eq!(avg_value, u32::MAX as f64);
1111 }
1112
1113 #[test]
1114 fn test_first_seen_returns_oldest_event_touching_buckets() {
1115 use chrono::{TimeZone, Utc};
1116 let fixed_time = Utc.with_ymd_and_hms(2025, 12, 5, 12, 0, 0).unwrap();
1118 let clock = TestClock::build_for_testing_at(fixed_time);
1119 let store = EventStore::builder()
1120 .track_days(7)
1121 .track_hours(24)
1122 .track_minutes(60)
1123 .with_clock(Arc::new(clock.clone()))
1124 .build()
1125 .unwrap();
1126
1127 store.record("event");
1128 clock.advance(Duration::hours(5));
1129 store.record("event");
1130
1131 let first = store.query("event").first_seen();
1132 let last = store.query("event").last_seen();
1133
1134 assert_eq!(first, Some(Duration::hours(5) + Duration::minutes(30)));
1135
1136 assert!(last.is_some());
1138 assert!(last.unwrap() == Duration::minutes(0));
1139 }
1140
1141 #[test]
1142 fn test_first_seen_returns_oldest_event_overlapping_buckets() {
1143 use chrono::{TimeZone, Utc};
1144 let fixed_time = Utc.with_ymd_and_hms(2025, 12, 5, 12, 0, 0).unwrap();
1146 let clock = TestClock::build_for_testing_at(fixed_time);
1147 let store = EventStore::builder()
1148 .track_days(7)
1149 .track_hours(24)
1150 .track_minutes(60 * 24)
1151 .with_clock(Arc::new(clock.clone()))
1152 .build()
1153 .unwrap();
1154
1155 store.record("event");
1156 clock.advance(Duration::hours(5));
1157 store.record("event");
1158
1159 let first = store.query("event").first_seen();
1160 let last = store.query("event").last_seen();
1161
1162 assert_eq!(first, Some(Duration::hours(5) + Duration::seconds(30)));
1163
1164 assert!(last.is_some());
1166 assert!(last.unwrap() == Duration::minutes(0));
1167 }
1168
1169 #[test]
1170 fn test_first_seen_returns_oldest_event_disjoint_buckets() {
1171 use chrono::{TimeZone, Utc};
1172 let fixed_time = Utc.with_ymd_and_hms(2025, 12, 5, 12, 0, 0).unwrap();
1174 let clock = TestClock::build_for_testing_at(fixed_time);
1175 let store = EventStore::builder()
1176 .track_days(7)
1177 .track_minutes(60)
1178 .with_clock(Arc::new(clock.clone()))
1179 .build()
1180 .unwrap();
1181
1182 store.record("event");
1183 clock.advance(Duration::hours(5)); store.record("event");
1185
1186 let first = store.query("event").first_seen();
1188 let last = store.query("event").last_seen();
1189
1190 let expected = ago(clock.now(), 0, 8, 00);
1191 eprintln!("Expected at {}, {expected:?} ago", clock.now() - expected);
1192 eprintln!(
1193 "Actual at {}, {:?} ago",
1194 clock.now() - first.unwrap(),
1195 first.unwrap()
1196 );
1197 assert_eq!(first, Some(expected));
1199
1200 assert!(last.is_some());
1202 assert!(last.unwrap() == Duration::minutes(0));
1203 }
1204
1205 fn ago(now: DateTime<Utc>, days_ago: i64, hours: u32, minutes: u32) -> Duration {
1206 use chrono::{TimeZone, Utc};
1207 let then = Utc
1208 .with_ymd_and_hms(now.year(), now.month(), now.day(), hours, minutes, 0)
1209 .unwrap()
1210 - Duration::days(days_ago);
1211 now - then
1212 }
1213
1214 #[test]
1215 fn test_first_seen_returns_oldest_event_disjoint_muilti_buckets_under() {
1216 use chrono::{TimeZone, Utc};
1217 let fixed_time = Utc.with_ymd_and_hms(2025, 12, 5, 12, 0, 0).unwrap();
1219 let clock = TestClock::build_for_testing_at(fixed_time);
1220 let store = EventStore::builder()
1221 .track_days(7)
1222 .track_hours(36)
1223 .with_clock(Arc::new(clock.clone()))
1224 .build()
1225 .unwrap();
1226
1227 store.record("event");
1228 clock.advance(Duration::hours(25));
1229 store.record("event");
1230
1231 let first = store.query("event").first_seen();
1232 let last = store.query("event").last_seen();
1233
1234 assert_eq!(first, Some(Duration::hours(25) + Duration::minutes(30)));
1235
1236 assert!(last.is_some());
1238 assert!(last.unwrap() == Duration::minutes(0));
1239 }
1240
1241 #[test]
1242 fn test_first_seen_returns_oldest_event_disjoint_muilti_buckets_over() {
1243 use chrono::{TimeZone, Utc};
1244 let fixed_time = Utc.with_ymd_and_hms(2025, 12, 5, 10, 0, 0).unwrap();
1246 let clock = TestClock::build_for_testing_at(fixed_time);
1247 let store = EventStore::builder()
1248 .track_days(7)
1249 .track_hours(36)
1250 .with_clock(Arc::new(clock.clone()))
1251 .build()
1252 .unwrap();
1253
1254 store.record("event");
1255 clock.advance(Duration::hours(37)); store.record("event");
1257
1258 let first = store.query("event").first_seen();
1261 let last = store.query("event").last_seen();
1262
1263 let expected = ago(clock.now(), 1, 5, 30);
1266 eprintln!("Expected at {}, {expected:?} ago", clock.now() - expected);
1267 eprintln!(
1268 "Actual at {}, {:?} ago",
1269 clock.now() - first.unwrap(),
1270 first.unwrap()
1271 );
1272 assert_eq!(first, Some(expected));
1273
1274 assert_eq!(last, Some(Duration::minutes(0)));
1276
1277 clock.advance(Duration::hours(2)); let first = store.query("event").first_seen();
1279 let last = store.query("event").last_seen();
1280 let expected = ago(clock.now(), 2, 6, 30);
1283 eprintln!("Expected at {}, {expected:?} ago", clock.now() - expected);
1284 eprintln!(
1285 "Actual at {}, {:?} ago",
1286 clock.now() - first.unwrap(),
1287 first.unwrap()
1288 );
1289 assert_eq!(first, Some(expected));
1290 assert_eq!(last, Some(Duration::hours(2) + Duration::minutes(30)));
1291 }
1292
1293 #[test]
1294 fn test_first_seen_with_no_events() {
1295 let store = EventStore::new();
1296 let first = store.query("nonexistent").first_seen();
1297 assert_eq!(first, None);
1298 }
1299
1300 #[test]
1301 fn test_first_seen_in_specific_time_unit() {
1302 let clock = TestClock::build_for_testing();
1303 let store = EventStore::builder()
1304 .with_clock(Arc::new(clock.clone()))
1305 .build()
1306 .unwrap();
1307
1308 store.record("event");
1310 clock.advance(Duration::hours(5));
1312 store.record("event");
1314
1315 let first_hours = store.query("event").first_seen_in(TimeUnit::Hours);
1316
1317 assert_eq!(first_hours, Some(Duration::hours(5)));
1319 }
1320
1321 #[test]
1322 fn test_first_seen_with_single_event_touching_intervals() {
1323 let clock = TestClock::build_for_testing();
1324 let store = EventStore::builder()
1325 .with_clock(Arc::new(clock.clone()))
1326 .track_hours(24)
1327 .track_minutes(60)
1328 .build()
1329 .unwrap();
1330
1331 store.record("event");
1332
1333 let first = store.query("event").first_seen();
1334 let last = store.query("event").last_seen();
1335
1336 assert!(first.is_some());
1338 assert!(last.is_some());
1339 eprintln!("first = {:?}", first.unwrap());
1340 eprintln!("last = {:?}", last.unwrap());
1341 assert!(
1342 first.unwrap() < Duration::hours(1),
1343 "Expected first < 1 hour, got {:?}",
1344 first.unwrap()
1345 );
1346 assert!(last.unwrap() < Duration::hours(1));
1347 }
1348
1349 #[test]
1350 fn test_first_seen_with_single_event_disjoint_intervals() {
1351 let fixed_time = Utc.with_ymd_and_hms(2025, 12, 5, 12, 0, 0).unwrap();
1352 let clock = TestClock::build_for_testing_at(fixed_time);
1353 let store = EventStore::builder()
1354 .with_clock(Arc::new(clock.clone()))
1355 .track_years(2)
1356 .track_months(12)
1358 .track_days(28)
1359 .track_hours(24)
1360 .build()
1361 .unwrap();
1362
1363 store.record("event");
1364
1365 let first = store.query("event").first_seen();
1366 let last = store.query("event").last_seen();
1367
1368 assert!(first.is_some());
1371 assert!(last.is_some());
1372
1373 assert_eq!(first, Some(Duration::zero()));
1375 assert_eq!(last, Some(Duration::zero()));
1376 }
1377
1378 #[test]
1379 #[cfg(not(feature = "calendar"))]
1380 fn test_first_seen_across_multiple_time_units() {
1381 let clock = TestClock::build_for_testing();
1382 let store = EventStore::builder()
1383 .with_clock(Arc::new(clock.clone()))
1384 .build()
1385 .unwrap();
1386
1387 store.record("event");
1389
1390 clock.advance(Duration::days(10));
1392 store.record("event");
1394
1395 let first = store.query("event").first_seen();
1396 let expected = ago(clock.now(), 11, 12, 00);
1397 eprintln!("Expected at {}, {expected:?} ago", clock.now() - expected);
1398 eprintln!(
1399 "Actual at {}, {:?} ago",
1400 clock.now() - first.unwrap(),
1401 first.unwrap()
1402 );
1403 assert_eq!(first, Some(expected));
1405 }
1406
1407 #[test]
1408 fn test_range_query_first_seen() {
1409 let clock = TestClock::build_for_testing();
1410 let store = EventStore::builder()
1411 .with_clock(Arc::new(clock.clone()))
1412 .build()
1413 .unwrap();
1414
1415 store.record("event");
1416 clock.advance(Duration::hours(5));
1417 store.record("event");
1418
1419 let first_bucket = store.query("event").last_hours(24).first_seen();
1421
1422 assert_eq!(first_bucket, Some(5));
1424 }
1425
1426 #[test]
1427 fn test_first_seen_consistency_with_last_seen() {
1428 let clock = TestClock::build_for_testing();
1429 let store = EventStore::builder()
1430 .with_clock(Arc::new(clock.clone()))
1431 .build()
1432 .unwrap();
1433
1434 store.record("event"); clock.advance(Duration::hours(10));
1436 store.record("event"); let first_seen = store.query("event").first_seen();
1439 let last_seen = store.query("event").last_seen();
1440
1441 assert!(first_seen.is_some());
1443 assert!(last_seen.is_some());
1444 assert!(first_seen.unwrap() > last_seen.unwrap());
1445 }
1446
1447 #[test]
1448 fn test_ever_into_buckets_does_not_hang() {
1449 let store = EventStore::new();
1452 store.record_count("event", 100);
1453
1454 let buckets = store.query("event").ever().into_buckets();
1456
1457 assert!(!buckets.is_empty());
1460 assert!(buckets.len() < 1000); }
1462
1463 #[test]
1464 fn test_last_seen_returns_recent_event_touching_intervals() {
1465 use chrono::{TimeZone, Utc};
1466 let fixed_time = Utc.with_ymd_and_hms(2025, 12, 5, 12, 0, 0).unwrap();
1468 let clock = TestClock::build_for_testing_at(fixed_time);
1469 let store = EventStore::builder()
1470 .track_days(7)
1471 .track_hours(24)
1472 .track_minutes(60)
1473 .with_clock(Arc::new(clock.clone()))
1474 .build()
1475 .unwrap();
1476
1477 store.record("event");
1479 clock.advance(Duration::minutes(10));
1481 store.record("event");
1482
1483 let last = store.query("event").last_seen();
1484
1485 assert!(last.is_some());
1488 assert!(
1489 last.unwrap() < Duration::minutes(1),
1490 "Expected last < 1 minute, got {:?}",
1491 last.unwrap()
1492 );
1493 }
1494
1495 #[test]
1496 fn test_last_seen_returns_recent_event_overlapping_intervals() {
1497 use chrono::{TimeZone, Utc};
1498 let fixed_time = Utc.with_ymd_and_hms(2025, 12, 5, 12, 0, 0).unwrap();
1500 let clock = TestClock::build_for_testing_at(fixed_time);
1501 let store = EventStore::builder()
1502 .track_days(7)
1503 .track_hours(24)
1504 .track_minutes(60 * 24)
1505 .with_clock(Arc::new(clock.clone()))
1506 .build()
1507 .unwrap();
1508
1509 store.record("event");
1510 clock.advance(Duration::minutes(10));
1511 store.record("event");
1512
1513 let last = store.query("event").last_seen();
1514
1515 assert!(last.is_some());
1517 assert!(
1518 last.unwrap() < Duration::minutes(1),
1519 "Expected last < 1 minute, got {:?}",
1520 last.unwrap()
1521 );
1522 }
1523
1524 #[test]
1525 fn test_last_seen_gap_event_disjoint_intervals() {
1526 use chrono::{TimeZone, Utc};
1527 let fixed_time = Utc.with_ymd_and_hms(2025, 12, 5, 12, 0, 0).unwrap();
1529 let clock = TestClock::build_for_testing_at(fixed_time);
1530 let store = EventStore::builder()
1531 .track_days(7)
1532 .track_hours(24)
1533 .track_minutes(45)
1534 .with_clock(Arc::new(clock.clone()))
1535 .build()
1536 .unwrap();
1537
1538 store.record("event");
1539 clock.advance(Duration::minutes(50));
1543 let last = store.query("event").last_seen();
1546
1547 let expected = Duration::minutes(52) + Duration::seconds(30);
1551 assert_eq!(
1552 last,
1553 Some(expected),
1554 "Gap event should be estimated at gap midpoint"
1555 );
1556 }
1557
1558 #[test]
1559 fn test_last_seen_recent_event_in_smallest_bucket() {
1560 use chrono::{TimeZone, Utc};
1561 let fixed_time = Utc.with_ymd_and_hms(2025, 12, 5, 12, 0, 0).unwrap();
1562 let clock = TestClock::build_for_testing_at(fixed_time);
1563 let store = EventStore::builder()
1564 .track_days(7)
1565 .track_hours(24)
1566 .track_minutes(60)
1567 .with_clock(Arc::new(clock.clone()))
1568 .build()
1569 .unwrap();
1570
1571 store.record("event");
1572
1573 let last = store.query("event").last_seen();
1574
1575 assert_eq!(last, Some(Duration::zero()));
1577 }
1578
1579 #[test]
1580 fn test_last_seen_event_in_gap_multiple_intervals() {
1581 use chrono::{TimeZone, Utc};
1582 let fixed_time = Utc.with_ymd_and_hms(2025, 12, 5, 12, 0, 0).unwrap();
1584 let clock = TestClock::build_for_testing_at(fixed_time);
1585 let store = EventStore::builder()
1586 .track_days(7)
1587 .track_hours(24)
1588 .track_minutes(30)
1589 .with_clock(Arc::new(clock.clone()))
1590 .build()
1591 .unwrap();
1592
1593 store.record("event");
1594 clock.advance(Duration::minutes(40));
1596 let last = store.query("event").last_seen();
1599
1600 let expected = Duration::minutes(45);
1602 assert_eq!(
1603 last,
1604 Some(expected),
1605 "Gap event should be estimated at gap midpoint"
1606 );
1607 }
1608
1609 #[test]
1610 fn test_last_seen_with_bucket_midway_ago() {
1611 use chrono::{TimeZone, Utc};
1612 let fixed_time = Utc.with_ymd_and_hms(2025, 12, 5, 12, 0, 0).unwrap();
1613 let clock = TestClock::build_for_testing_at(fixed_time);
1614 let store = EventStore::builder()
1615 .track_hours(24)
1616 .with_clock(Arc::new(clock.clone()))
1617 .build()
1618 .unwrap();
1619
1620 store.record("event");
1621 clock.advance(Duration::minutes(30));
1623
1624 let last = store.query("event").last_seen();
1625
1626 assert!(last.is_some());
1629 let duration = last.unwrap();
1630
1631 assert!(
1633 duration >= Duration::minutes(14) && duration <= Duration::minutes(16),
1634 "Expected ~15 minutes, got {:?}",
1635 duration
1636 );
1637 }
1638}