tank_tests/
orders.rs

1use rust_decimal::Decimal;
2use std::sync::LazyLock;
3use tank::{DataSet, Entity, Executor, FixedDecimal, Passive, cols, expr, stream::TryStreamExt};
4use tokio::sync::Mutex;
5use uuid::Uuid;
6
7#[derive(Entity, Debug, Clone, PartialEq, Eq, Hash)]
8#[tank(schema = "testing", name = "orders")]
9pub struct Order {
10    #[tank(primary_key)]
11    pub id: Passive<Uuid>,
12    pub customer_id: Uuid,
13    pub country: String,
14    pub total: FixedDecimal<16, 2>,
15    pub status: String,
16    pub created_at: chrono::DateTime<chrono::Utc>,
17}
18static MUTEX: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));
19
20pub async fn orders<E: Executor>(executor: &mut E) {
21    let _lock = MUTEX.lock().await;
22
23    // Setup
24    Order::drop_table(executor, true, false)
25        .await
26        .expect("Failed to drop Order table");
27    Order::create_table(executor, false, true)
28        .await
29        .expect("Failed to create Order table");
30
31    // Data
32    let orders = vec![
33        Order {
34            id: Uuid::new_v4().into(),
35            customer_id: Uuid::parse_str("11111111-1111-1111-1111-111111111111").unwrap(),
36            country: "Germany".into(),
37            total: Decimal::new(12999, 2).into(),
38            status: "paid".into(),
39            created_at: chrono::Utc::now() - chrono::Duration::days(5),
40        },
41        Order {
42            id: Uuid::new_v4().into(),
43            customer_id: Uuid::parse_str("22222222-2222-2222-2222-222222222222").unwrap(),
44            country: "Italy".into(),
45            total: Decimal::new(8990, 2).into(),
46            status: "shipped".into(),
47            created_at: chrono::Utc::now() - chrono::Duration::hours(3),
48        },
49        Order {
50            id: Uuid::new_v4().into(),
51            customer_id: Uuid::parse_str("11111111-1111-1111-1111-111111111111").unwrap(),
52            country: "Germany".into(),
53            total: Decimal::new(45900, 2).into(),
54            status: "shipped".into(),
55            created_at: chrono::Utc::now() - chrono::Duration::days(9),
56        },
57        Order {
58            id: Uuid::new_v4().into(),
59            customer_id: Uuid::parse_str("33333333-3333-3333-3333-333333333333").unwrap(),
60            country: "Spain".into(),
61            total: Decimal::new(22950, 2).into(),
62            status: "paid".into(),
63            created_at: chrono::Utc::now() - chrono::Duration::days(1),
64        },
65        Order {
66            id: Uuid::new_v4().into(),
67            customer_id: Uuid::parse_str("44444444-4444-4444-4444-444444444444").unwrap(),
68            country: "Germany".into(),
69            total: Decimal::new(50, 2).into(),
70            status: "paid".into(),
71            created_at: chrono::Utc::now() - chrono::Duration::days(30),
72        },
73        Order {
74            id: Uuid::new_v4().into(),
75            customer_id: Uuid::parse_str("55555555-5555-5555-5555-555555555555").unwrap(),
76            country: "Germany".into(),
77            total: Decimal::new(111899, 2).into(),
78            status: "shipped".into(),
79            created_at: chrono::Utc::now() - chrono::Duration::days(30),
80        },
81        Order {
82            id: Uuid::new_v4().into(),
83            customer_id: Uuid::parse_str("22222222-2222-2222-2222-222222222222").unwrap(),
84            country: "Italy".into(),
85            total: Decimal::new(4445, 2).into(),
86            status: "paid".into(),
87            created_at: chrono::Utc::now() - chrono::Duration::hours(2),
88        },
89    ];
90    let result = Order::insert_many(executor, orders.iter())
91        .await
92        .expect("Failed to insert orders");
93    if let Some(affected) = result.rows_affected {
94        assert_eq!(affected, 7);
95    }
96
97    // Prepare
98    let mut query = Order::table()
99        .prepare(
100            executor,
101            cols!(
102                Order::id,
103                Order::customer_id,
104                Order::country,
105                Order::total DESC,
106                Order::status,
107                Order::created_at
108            ),
109            &expr!(
110                Order::status == (?, ?) as IN &&
111                Order::created_at >= ? &&
112                Order::total >= ? &&
113                Order::country == (?, ?) as IN
114            ),
115            None,
116        )
117        .await
118        .expect("Failed to prepare the query");
119    assert!(query.is_prepared(), "Query should be marked as prepared");
120
121    // 100+€ orders from last 10 days from Germany or Spain
122    query
123        .bind("paid")
124        .unwrap()
125        .bind("shipped")
126        .unwrap()
127        .bind(chrono::Utc::now() - chrono::Duration::days(10))
128        .unwrap()
129        .bind(99.99)
130        .unwrap()
131        .bind("Germany")
132        .unwrap()
133        .bind("Spain")
134        .unwrap();
135    let orders = executor
136        .fetch(&mut query)
137        .and_then(|v| async { Order::from_row(v) })
138        .map_ok(|v| format!("{}, {}, {:.2}€", v.country, v.status, v.total.0))
139        .try_collect::<Vec<_>>()
140        .await
141        .expect("Could not run query 1");
142    assert_eq!(
143        orders,
144        [
145            "Germany, shipped, 459.00€".to_string(),
146            "Spain, paid, 229.50€".to_string(),
147            "Germany, paid, 129.99€".to_string(),
148        ]
149    );
150    assert!(query.is_prepared());
151
152    // All orders above 1€ from Germany or Italy
153    query.clear_bindings().expect("Failed to clear bindings");
154    query
155        .bind("paid")
156        .unwrap()
157        .bind("shipped")
158        .unwrap()
159        .bind(chrono::Utc::now() - chrono::Duration::days(365))
160        .unwrap()
161        .bind(1)
162        .unwrap()
163        .bind("Germany")
164        .unwrap()
165        .bind("Italy")
166        .unwrap();
167    let orders: Vec<String> = executor
168        .fetch(&mut query)
169        .and_then(|v| async { Order::from_row(v) })
170        .map_ok(|v| format!("{}, {}, {:.2}€", v.country, v.status, v.total.0))
171        .try_collect()
172        .await
173        .expect("Could not run query 2");
174    assert_eq!(
175        orders,
176        [
177            "Germany, shipped, 1118.99€".to_string(),
178            "Germany, shipped, 459.00€".to_string(),
179            "Germany, paid, 129.99€".to_string(),
180            "Italy, shipped, 89.90€".to_string(),
181            "Italy, paid, 44.45€".to_string(),
182        ]
183    );
184    assert!(query.is_prepared());
185}