Skip to main content

tank_tests/
orders.rs

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