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 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 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 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 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 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}