1#![allow(unused_imports)]
2use std::{future, sync::LazyLock};
3use tank::{
4 Entity, Executor, QueryBuilder, cols, expr,
5 stream::{StreamExt, TryStreamExt},
6};
7use time::{Date, macros::date};
8use tokio::sync::Mutex;
9
10static MUTEX: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));
11
12#[derive(Entity)]
13#[tank(primary_key = (name, country, value, date))]
14pub struct Metric {
15 person: String,
16 country: String,
17 #[tank(clustering_key)]
18 date: Date,
19 name: String,
20 #[tank(clustering_key)]
21 value: f64,
22}
23
24#[derive(Entity)]
25struct MetricValue {
26 pub value: f64,
27}
28
29pub async fn metrics(executor: &mut impl Executor) {
30 let _lock = MUTEX.lock().await;
31
32 Metric::drop_table(executor, true, false)
34 .await
35 .expect("Failed to drop Metric table");
36 Metric::create_table(executor, true, true)
37 .await
38 .expect("Failed to create Metric table");
39
40 let values = vec![
42 Metric {
44 person: "alice".into(),
45 country: "IT".into(),
46 date: date!(2024 - 1 - 1),
47 name: "height_cm".into(),
48 value: 165.0,
49 },
50 Metric {
51 person: "alice".into(),
52 country: "IT".into(),
53 date: date!(2024 - 1 - 1),
54 name: "weight_kg".into(),
55 value: 62.5,
56 },
57 Metric {
58 person: "alice".into(),
59 country: "IT".into(),
60 date: date!(2024 - 6 - 1),
61 name: "weight_kg".into(),
62 value: 64.2,
63 },
64 Metric {
65 person: "alice".into(),
66 country: "IT".into(),
67 date: date!(2024 - 6 - 1),
68 name: "income_eur".into(),
69 value: 56000.0,
70 },
71 Metric {
72 person: "alice".into(),
73 country: "IT".into(),
74 date: date!(2025 - 4 - 1),
75 name: "income_eur".into(),
76 value: 68000.0,
77 },
78 Metric {
80 person: "bob".into(),
81 country: "NL".into(),
82 date: date!(2024 - 1 - 1),
83 name: "height_cm".into(),
84 value: 188.0,
85 },
86 Metric {
87 person: "bob".into(),
88 country: "NL".into(),
89 date: date!(2024 - 1 - 1),
90 name: "weight_kg".into(),
91 value: 81.0,
92 },
93 Metric {
94 person: "bob".into(),
95 country: "NL".into(),
96 date: date!(2025 - 6 - 12),
97 name: "weight_kg".into(),
98 value: 82.5,
99 },
100 Metric {
101 person: "bob".into(),
102 country: "NL".into(),
103 date: date!(2024 - 12 - 1),
104 name: "income_eur".into(),
105 value: 88000.0,
106 },
107 Metric {
108 person: "bob".into(),
109 country: "NL".into(),
110 date: date!(2025 - 3 - 2),
111 name: "income_eur".into(),
112 value: 120000.0,
113 },
114 Metric {
115 person: "bob".into(),
116 country: "NL".into(),
117 date: date!(2026 - 2 - 2),
118 name: "income_eur".into(),
119 value: 130000.0,
120 },
121 Metric {
123 person: "clara".into(),
124 country: "DE".into(),
125 date: date!(2024 - 2 - 1),
126 name: "height_cm".into(),
127 value: 170.0,
128 },
129 Metric {
130 person: "clara".into(),
131 country: "DE".into(),
132 date: date!(2024 - 2 - 1),
133 name: "weight_kg".into(),
134 value: 60.0,
135 },
136 Metric {
137 person: "clara".into(),
138 country: "DE".into(),
139 date: date!(2025 - 1 - 1),
140 name: "income_eur".into(),
141 value: 72000.0,
142 },
143 Metric {
145 person: "david".into(),
146 country: "UK".into(),
147 date: date!(2024 - 3 - 10),
148 name: "height_cm".into(),
149 value: 182.0,
150 },
151 Metric {
152 person: "david".into(),
153 country: "UK".into(),
154 date: date!(2024 - 3 - 10),
155 name: "weight_kg".into(),
156 value: 88.5,
157 },
158 Metric {
159 person: "david".into(),
160 country: "UK".into(),
161 date: date!(2024 - 3 - 10),
162 name: "weight_kg".into(),
163 value: 86.0,
164 },
165 Metric {
166 person: "david".into(),
167 country: "UK".into(),
168 date: date!(2024 - 11 - 1),
169 name: "income_eur".into(),
170 value: 69000.0,
171 },
172 Metric {
173 person: "david".into(),
174 country: "UK".into(),
175 date: date!(2025 - 11 - 1),
176 name: "income_eur".into(),
177 value: 102000.0,
178 },
179 Metric {
181 person: "eva".into(),
182 country: "ES".into(),
183 date: date!(2024 - 4 - 5),
184 name: "height_cm".into(),
185 value: 162.0,
186 },
187 Metric {
188 person: "eva".into(),
189 country: "ES".into(),
190 date: date!(2024 - 4 - 5),
191 name: "weight_kg".into(),
192 value: 58.0,
193 },
194 Metric {
195 person: "eva".into(),
196 country: "ES".into(),
197 date: date!(2024 - 12 - 1),
198 name: "income_eur".into(),
199 value: 42000.0,
200 },
201 Metric {
202 person: "eva".into(),
203 country: "ES".into(),
204 date: date!(2025 - 12 - 1),
205 name: "income_eur".into(),
206 value: 47000.0,
207 },
208 Metric {
210 person: "marco".into(),
211 country: "IT".into(),
212 date: date!(2024 - 1 - 15),
213 name: "height_cm".into(),
214 value: 178.0,
215 },
216 Metric {
217 person: "marco".into(),
218 country: "IT".into(),
219 date: date!(2024 - 1 - 15),
220 name: "weight_kg".into(),
221 value: 78.0,
222 },
223 Metric {
224 person: "marco".into(),
225 country: "IT".into(),
226 date: date!(2024 - 10 - 1),
227 name: "weight_kg".into(),
228 value: 80.5,
229 },
230 Metric {
231 person: "marco".into(),
232 country: "IT".into(),
233 date: date!(2024 - 10 - 1),
234 name: "income_eur".into(),
235 value: 61000.0,
236 },
237 Metric {
238 person: "marco".into(),
239 country: "IT".into(),
240 date: date!(2025 - 10 - 1),
241 name: "income_eur".into(),
242 value: 72000.0,
243 },
244 Metric {
246 person: "sophie".into(),
247 country: "UK".into(),
248 date: date!(2024 - 2 - 20),
249 name: "height_cm".into(),
250 value: 168.0,
251 },
252 Metric {
253 person: "sophie".into(),
254 country: "UK".into(),
255 date: date!(2024 - 2 - 20),
256 name: "weight_kg".into(),
257 value: 61.0,
258 },
259 Metric {
260 person: "sophie".into(),
261 country: "UK".into(),
262 date: date!(2024 - 12 - 1),
263 name: "income_eur".into(),
264 value: 58000.0,
265 },
266 Metric {
267 person: "sophie".into(),
268 country: "UK".into(),
269 date: date!(2024 - 12 - 2),
270 name: "weight_kg".into(),
271 value: 67.5,
272 },
273 Metric {
274 person: "sophie".into(),
275 country: "UK".into(),
276 date: date!(2025 - 12 - 1),
277 name: "income_eur".into(),
278 value: 66000.0,
279 },
280 Metric {
282 person: "alice".into(),
283 country: "CA".into(),
284 date: date!(2024 - 6 - 1),
285 name: "income_eur".into(),
286 value: 77000.0,
287 },
288 ];
289 Metric::insert_many(executor, &values)
327 .await
328 .expect("Could not insert the entities");
329
330 let height = 170;
331 let heights = executor
332 .fetch(
333 QueryBuilder::new()
334 .select([Metric::value, Metric::date])
335 .from(Metric::table())
336 .where_expr(expr!(
337 Metric::name == "height_cm"
338 && Metric::country == "IT"
339 && Metric::value >= #height
340 ))
341 .order_by(cols!(Metric::value DESC, Metric::date DESC))
342 .build(&executor.driver()),
343 )
344 .and_then(|v| future::ready(MetricValue::from_row(v).map(|v| v.value)))
345 .try_collect::<Vec<_>>()
346 .await
347 .expect("Coult not get the Italy height values");
348 assert_eq!(heights, [178.0]);
349
350 let italy_incomes = executor
352 .fetch(
353 QueryBuilder::new()
354 .select([Metric::value])
355 .from(Metric::table())
356 .where_expr(expr!(
357 Metric::name == "income_eur" && Metric::country == "IT"
358 ))
359 .order_by(cols!(Metric::value ASC))
360 .build(&executor.driver()),
361 )
362 .and_then(|v| future::ready(MetricValue::from_row(v).map(|v| v.value)))
363 .try_collect::<Vec<_>>()
364 .await
365 .expect("Could not get IT incomes");
366 assert_eq!(italy_incomes, [56000.0, 61000.0, 68000.0, 72000.0]);
367
368 let latest_income = executor
370 .fetch(
371 QueryBuilder::new()
372 .select(cols!(MAX(Metric::value) as value))
373 .from(Metric::table())
374 .where_expr(expr!(
375 Metric::name == "income_eur" && Metric::country == "UK"
376 ))
377 .build(&executor.driver()),
378 )
379 .and_then(|v| future::ready(MetricValue::from_row(v).map(|v| v.value)))
380 .try_collect::<Vec<_>>()
381 .await
382 .expect("Could not get latest UK income");
383 assert_eq!(latest_income, [102000.0]);
384
385 let mut prepared = executor
387 .prepare(
388 QueryBuilder::new()
389 .select([Metric::value, Metric::date])
390 .from(Metric::table())
391 .where_expr(expr!(Metric::country == ? && Metric::name == ?))
392 .order_by(cols!(Metric::value DESC, Metric::date DESC))
393 .build(&executor.driver()),
394 )
395 .await
396 .expect("Failed to prepare metric query");
397
398 prepared.bind("ES").unwrap().bind("height_cm").unwrap();
399 let spain_heights = executor
400 .fetch(&mut prepared)
401 .and_then(|v| future::ready(MetricValue::from_row(v).map(|v| v.value)))
402 .try_collect::<Vec<_>>()
403 .await
404 .expect("Could not fetch sophie heights");
405 assert_eq!(spain_heights, [162.0]);
406
407 prepared.bind("NL").unwrap().bind("weight_kg").unwrap();
408 let netherlands_weights = executor
409 .fetch(&mut prepared)
410 .and_then(|v| future::ready(MetricValue::from_row(v).map(|v| v.value)))
411 .try_collect::<Vec<_>>()
412 .await
413 .expect("Could not fetch sophie heights");
414 assert_eq!(netherlands_weights, [82.5, 81.0]);
415
416 prepared.bind("IT").unwrap().bind("weight_kg").unwrap();
417 let italy_weights = executor
418 .fetch(&mut prepared)
419 .and_then(|v| future::ready(MetricValue::from_row(v).map(|v| v.value)))
420 .try_collect::<Vec<_>>()
421 .await
422 .expect("Could not fetch sophie heights");
423 assert_eq!(italy_weights, [80.5, 78.0, 64.2, 62.5]);
424
425 #[cfg(not(feature = "disable-groups"))]
426 {
427 #[derive(Entity, PartialEq, Debug)]
428 struct AverageMetrics {
429 name: String,
430 country: String,
431 avg: f32,
432 }
433 let averages = executor
434 .fetch(
435 QueryBuilder::new()
436 .select(cols!(
437 Metric::name,
438 Metric::country,
439 AVG(Metric::value) as avg,
440 ))
441 .from(Metric::table())
442 .group_by([Metric::name, Metric::country])
443 .order_by(cols!(Metric::name ASC, avg DESC))
444 .build(&executor.driver()),
445 )
446 .map_ok(AverageMetrics::from_row)
447 .map(Result::flatten)
448 .try_collect::<Vec<_>>()
449 .await
450 .expect("Could not get the products ordered by increasing price");
451 assert_eq!(
452 averages,
453 [
454 AverageMetrics {
455 name: "height_cm".into(),
456 country: "NL".into(),
457 avg: 188.0,
458 },
459 AverageMetrics {
460 name: "height_cm".into(),
461 country: "UK".into(),
462 avg: 175.0,
463 },
464 AverageMetrics {
465 name: "height_cm".into(),
466 country: "IT".into(),
467 avg: 171.5,
468 },
469 AverageMetrics {
470 name: "height_cm".into(),
471 country: "DE".into(),
472 avg: 170.0,
473 },
474 AverageMetrics {
475 name: "height_cm".into(),
476 country: "ES".into(),
477 avg: 162.0,
478 },
479 AverageMetrics {
480 name: "income_eur".into(),
481 country: "NL".into(),
482 avg: 112666.664,
483 },
484 AverageMetrics {
485 name: "income_eur".into(),
486 country: "CA".into(),
487 avg: 77000.0,
488 },
489 AverageMetrics {
490 name: "income_eur".into(),
491 country: "UK".into(),
492 avg: 73750.0,
493 },
494 AverageMetrics {
495 name: "income_eur".into(),
496 country: "DE".into(),
497 avg: 72000.0,
498 },
499 AverageMetrics {
500 name: "income_eur".into(),
501 country: "IT".into(),
502 avg: 64250.0,
503 },
504 AverageMetrics {
505 name: "income_eur".into(),
506 country: "ES".into(),
507 avg: 44500.0,
508 },
509 AverageMetrics {
510 name: "weight_kg".into(),
511 country: "NL".into(),
512 avg: 81.75,
513 },
514 AverageMetrics {
515 name: "weight_kg".into(),
516 country: "UK".into(),
517 avg: 75.75,
518 },
519 AverageMetrics {
520 name: "weight_kg".into(),
521 country: "IT".into(),
522 avg: 71.3,
523 },
524 AverageMetrics {
525 name: "weight_kg".into(),
526 country: "DE".into(),
527 avg: 60.0,
528 },
529 AverageMetrics {
530 name: "weight_kg".into(),
531 country: "ES".into(),
532 avg: 58.0,
533 }
534 ]
535 );
536 }
537
538 #[cfg(not(feature = "disable-groups"))]
539 {
540 #[derive(Entity, PartialEq, Debug)]
541 struct CountryMaxIncome {
542 country: String,
543 max_income: f64,
544 }
545
546 let max_incomes = executor
547 .fetch(
548 QueryBuilder::new()
549 .select(cols!(Metric::country, MAX(value) as max_income,))
550 .from(Metric::table())
551 .where_expr(expr!(name == "income_eur"))
552 .group_by([Metric::country])
553 .order_by(cols!(max_income DESC, Metric::country ASC))
554 .build(&executor.driver()),
555 )
556 .map_ok(CountryMaxIncome::from_row)
557 .map(Result::flatten)
558 .try_collect::<Vec<_>>()
559 .await
560 .expect("Could not get max incomes per country");
561
562 assert_eq!(
563 max_incomes,
564 [
565 CountryMaxIncome {
566 country: "NL".into(),
567 max_income: 130000.0,
568 },
569 CountryMaxIncome {
570 country: "UK".into(),
571 max_income: 102000.0,
572 },
573 CountryMaxIncome {
574 country: "CA".into(),
575 max_income: 77000.0,
576 },
577 CountryMaxIncome {
578 country: "DE".into(),
579 max_income: 72000.0,
580 },
581 CountryMaxIncome {
582 country: "IT".into(),
583 max_income: 72000.0,
584 },
585 CountryMaxIncome {
586 country: "ES".into(),
587 max_income: 47000.0,
588 },
589 ]
590 );
591 }
592
593 #[cfg(not(feature = "disable-groups"))]
594 {
595 #[derive(Entity, PartialEq, Debug)]
596 struct MetricGlobalStats {
597 name: String,
598 min_val: f64,
599 max_val: f64,
600 total_val: f64,
601 }
602
603 let global_stats = executor
604 .fetch(
605 QueryBuilder::new()
606 .select(cols!(
607 Metric::name,
608 MIN(Metric::value) as min_val,
609 MAX(Metric::value) as max_val,
610 SUM(Metric::value) as total_val,
611 ))
612 .from(Metric::table())
613 .group_by([Metric::name])
614 .order_by(cols!(name ASC))
615 .build(&executor.driver()),
616 )
617 .map_ok(MetricGlobalStats::from_row)
618 .map(Result::flatten)
619 .try_collect::<Vec<_>>()
620 .await
621 .expect("Could not get global metric stats");
622
623 assert_eq!(
624 global_stats,
625 [
626 MetricGlobalStats {
627 name: "height_cm".into(),
628 min_val: 162.0,
629 max_val: 188.0,
630 total_val: 1213.0,
631 },
632 MetricGlobalStats {
633 name: "income_eur".into(),
634 min_val: 42000.0,
635 max_val: 130000.0,
636 total_val: 1128000.0,
637 },
638 MetricGlobalStats {
639 name: "weight_kg".into(),
640 min_val: 58.0,
641 max_val: 88.5,
642 total_val: 869.7, },
644 ]
645 );
646 }
647}