Skip to main content

tank_tests/
orders.rs

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