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