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