1#![allow(unused_imports)]
2use rust_decimal::Decimal;
3use std::collections::HashMap;
4use std::pin::pin;
5use std::{str::FromStr, sync::Arc, sync::LazyLock};
6use tank::QueryBuilder;
7use tank::{
8 AsValue, Dataset, Entity, Executor, FixedDecimal, cols, expr, join,
9 stream::{StreamExt, TryStreamExt},
10};
11use time::{Date, Month, PrimitiveDateTime, Time};
12use tokio::sync::Mutex;
13use uuid::Uuid;
14
15static MUTEX: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));
16
17#[derive(Default, Entity, Debug)]
18#[tank(schema = "shopping", primary_key = Self::id)]
19struct Product {
20 id: usize,
21 name: String,
22 price: FixedDecimal<8, 2>,
23 desc: Option<String>,
24 stock: Option<isize>,
25 #[cfg(not(feature = "disable-lists"))]
26 tags: Vec<String>,
27}
28
29#[derive(Entity, Debug)]
30#[tank(schema = "shopping", primary_key = Self::id)]
31struct User {
32 id: Uuid,
33 name: String,
34 email: String,
35 birthday: Date,
36 #[cfg(not(feature = "disable-lists"))]
37 preferences: Option<Arc<Vec<String>>>,
38 registered: PrimitiveDateTime,
39}
40
41#[derive(Entity, Debug)]
42#[tank(schema = "shopping", primary_key = (user, product))]
43struct Cart {
44 #[tank(references = User::id)]
45 user: Uuid,
46 #[tank(references = Product::id)]
47 product: usize,
48 price: FixedDecimal<8, 2>,
50 timestamp: PrimitiveDateTime,
51}
52
53pub async fn shopping(executor: &mut impl Executor) {
54 let _lock = MUTEX.lock().await;
55
56 Product::drop_table(executor, true, false)
58 .await
59 .expect("Failed to drop product table");
60 Product::create_table(executor, false, true)
61 .await
62 .expect("Failed to create the product table");
63 let products = [
64 Product {
65 id: 1,
66 name: "Rust-Proof Coffee Mug".into(),
67 price: Decimal::new(12_99, 2).into(),
68 desc: Some("Keeps your coffee warm and your compiler calm.".into()),
69 stock: 42.into(),
70 #[cfg(not(feature = "disable-lists"))]
71 tags: vec!["kitchen".into(), "coffee".into(), "metal".into()].into(),
72 },
73 Product {
74 id: 2,
75 name: "Zero-Cost Abstraction Hoodie".into(),
76 price: Decimal::new(49_95, 2).into(),
77 desc: Some("For developers who think runtime overhead is a moral failure.".into()),
78 stock: 10.into(),
79 #[cfg(not(feature = "disable-lists"))]
80 tags: vec!["clothing".into(), "nerdwear".into()].into(),
81 },
82 Product {
83 id: 3,
84 name: "Thread-Safe Notebook".into(),
85 price: Decimal::new(7_50, 2).into(),
86 desc: None,
87 stock: 0.into(),
88 #[cfg(not(feature = "disable-lists"))]
89 tags: vec!["stationery".into()].into(),
90 },
91 Product {
92 id: 4,
93 name: "Async Teapot".into(),
94 price: Decimal::new(25_00, 2).into(),
95 desc: Some("Returns 418 on brew() call.".into()),
96 stock: 3.into(),
97 #[cfg(not(feature = "disable-lists"))]
98 tags: vec!["kitchen".into(), "humor".into()].into(),
99 },
100 ];
101 Product::insert_many(executor, &products)
102 .await
103 .expect("Could not insert the products");
104 let total_products = Product::find_many(executor, true, None)
105 .map_err(|e| panic!("{e:#}"))
106 .count()
107 .await;
108 assert_eq!(total_products, 4);
109 let ordered_products = executor
110 .fetch(
111 QueryBuilder::new()
112 .select([Product::id, Product::name, Product::price])
113 .from(Product::table())
114 .where_expr(expr!(Product::stock > 0))
115 .order_by(cols!(Product::price ASC))
116 .build(&executor.driver()),
117 )
118 .map(|r| r.and_then(Product::from_row))
119 .try_collect::<Vec<Product>>()
120 .await
121 .expect("Could not get the products ordered by increasing price");
122 assert!(
123 ordered_products.iter().map(|v| &v.name).eq([
124 "Rust-Proof Coffee Mug",
125 "Async Teapot",
126 "Zero-Cost Abstraction Hoodie"
127 ]
128 .into_iter())
129 );
130 let zero_stock = Product::find_one(executor, expr!(Product::stock == 0))
131 .await
132 .expect("Failed to query product with zero stock")
133 .expect("Expected a product with zero stock");
134 assert_eq!(zero_stock.id, 3);
135
136 let mut prod4 = Product::find_one(executor, expr!(Product::id == 4))
138 .await
139 .expect("Failed to query product 4")
140 .expect("Product 4 expected");
141 let old_stock = prod4.stock.unwrap_or(0);
142 prod4.stock = Some(old_stock - 1);
143 prod4
144 .save(executor)
145 .await
146 .expect("Failed to save updated product 4");
147 let prod4_after = Product::find_one(executor, expr!(Product::id == 4))
148 .await
149 .expect("Failed to query product 4 after update")
150 .expect("Product 4 expected after update");
151 assert_eq!(prod4_after.stock, Some(old_stock - 1));
152
153 User::drop_table(executor, true, false)
155 .await
156 .expect("Failed to drop user table");
157 User::create_table(executor, false, false)
158 .await
159 .expect("Failed to create the user table");
160 let users = vec![
161 User {
162 id: Uuid::new_v4(),
163 name: "Alice Compiler".into(),
164 email: "alice@example.com".into(),
165 birthday: Date::from_calendar_date(1995, Month::May, 17).unwrap(),
166 #[cfg(not(feature = "disable-lists"))]
167 preferences: Some(vec!["dark_mode".into(), "express_shipping".into()].into()),
168 registered: PrimitiveDateTime::new(
169 Date::from_calendar_date(2023, Month::January, 2).unwrap(),
170 Time::from_hms(10, 30, 0).unwrap(),
171 ),
172 },
173 User {
174 id: Uuid::new_v4(),
175 name: "Bob Segfault".into(),
176 email: "bob@crashmail.net".into(),
177 birthday: Date::from_calendar_date(1988, Month::March, 12).unwrap(),
178 #[cfg(not(feature = "disable-lists"))]
179 preferences: None,
180 registered: PrimitiveDateTime::new(
181 Date::from_calendar_date(2024, Month::June, 8).unwrap(),
182 Time::from_hms(22, 15, 0).unwrap(),
183 ),
184 },
185 ];
186 User::insert_many(executor, &users)
187 .await
188 .expect("Could not insert the users");
189 let row = pin!(
190 executor.fetch(
191 QueryBuilder::new()
192 .select(cols!(COUNT(*)))
193 .from(User::table())
194 .where_expr(true)
195 .limit(Some(1))
196 .build(&executor.driver())
197 )
198 )
199 .try_next()
200 .await
201 .expect("Failed to query for count")
202 .expect("Did not return some value");
203 assert_eq!(i64::try_from_value(row.values[0].clone()).unwrap(), 2);
204
205 Cart::drop_table(executor, true, false)
207 .await
208 .expect("Failed to drop cart table");
209 Cart::create_table(executor, false, false)
210 .await
211 .expect("Failed to create the cart table");
212 let carts = vec![
213 Cart {
214 user: users[0].id,
215 product: 1,
216 price: Decimal::new(12_99, 2).into(),
217 timestamp: PrimitiveDateTime::new(
218 Date::from_calendar_date(2025, Month::March, 1).unwrap(),
219 Time::from_hms(9, 0, 0).unwrap(),
220 ),
221 },
222 Cart {
223 user: users[0].id,
224 product: 2,
225 price: Decimal::new(49_95, 2).into(),
226 timestamp: PrimitiveDateTime::new(
227 Date::from_calendar_date(2025, Month::March, 1).unwrap(),
228 Time::from_hms(9, 5, 0).unwrap(),
229 ),
230 },
231 Cart {
232 user: users[1].id,
233 product: 4,
234 price: Decimal::new(23_50, 2).into(),
235 timestamp: PrimitiveDateTime::new(
236 Date::from_calendar_date(2025, Month::March, 3).unwrap(),
237 Time::from_hms(14, 12, 0).unwrap(),
238 ),
239 },
240 ];
241 Cart::insert_many(executor, &carts)
242 .await
243 .expect("Could not insert the carts");
244 let cart_count = Cart::find_many(executor, true, None)
245 .map_err(|e| panic!("{e:#}"))
246 .count()
247 .await;
248 assert_eq!(cart_count, 3);
249
250 let cart_for_4 = Cart::find_one(executor, expr!(Cart::product == 4))
252 .await
253 .expect("Failed to query cart for product 4");
254 let cart_for_4 = cart_for_4.expect("Expected a cart for product 4");
255 let product4 = Product::find_one(executor, expr!(Product::id == 4))
256 .await
257 .expect("Failed to query product 4 for price check")
258 .expect("Expected product 4");
259 assert_eq!(cart_for_4.price.0, Decimal::new(23_50, 2));
260 assert_eq!(product4.price.0, Decimal::new(25_00, 2));
261
262 let cart_for_2 = Cart::find_one(executor, expr!(Cart::product == 2))
264 .await
265 .expect("Failed to query cart for product 2")
266 .expect("Expected a cart for product 2");
267 cart_for_2
268 .delete(executor)
269 .await
270 .expect("Failed to delete cart for product 2");
271 let cart_count_after = Cart::find_many(executor, true, None)
272 .map_err(|e| panic!("{e:#}"))
273 .count()
274 .await;
275 assert_eq!(cart_count_after, 2);
276
277 #[cfg(not(feature = "disable-joins"))]
278 {
279 #[derive(Entity, PartialEq, Debug)]
280 struct Carts {
281 user: String,
282 product: String,
283 price: Decimal,
284 }
285 let carts: Vec<Carts> = executor
286 .fetch(
287 QueryBuilder::new()
288 .select(cols!(
289 Product::name as product,
290 User::name as user,
291 Cart::price
292 ))
293 .from(join!(
294 User INNER JOIN Cart ON User::id == Cart::user
295 JOIN Product ON Cart::product == Product::id
296 ))
297 .where_expr(true)
298 .order_by(cols!(Product::name ASC, User::name ASC))
299 .build(&executor.driver()),
300 )
301 .map_ok(Carts::from_row)
302 .map(Result::flatten)
303 .try_collect::<Vec<_>>()
304 .await
305 .expect("Could not get the products ordered by increasing price");
306 assert_eq!(
307 carts,
308 &[
309 Carts {
310 user: "Bob Segfault".into(),
311 product: "Async Teapot".into(),
312 price: Decimal::new(23_50, 2),
313 },
314 Carts {
315 user: "Alice Compiler".into(),
316 product: "Rust-Proof Coffee Mug".into(),
317 price: Decimal::new(12_99, 2),
318 },
319 ]
320 )
321 }
322}