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, Debug, Entity)]
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(Debug, Entity)]
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(Debug, Entity)]
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<E: Executor>(executor: &mut E) {
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).count().await;
105 assert_eq!(total_products, 4);
106 let ordered_products = executor
107 .fetch(
108 QueryBuilder::new()
109 .select(cols!(Product::id, Product::name, Product::price ASC))
110 .from(Product::table())
111 .where_condition(expr!(Product::stock > 0))
112 .build(&executor.driver()),
113 )
114 .map(|r| r.and_then(Product::from_row))
115 .try_collect::<Vec<Product>>()
116 .await
117 .expect("Could not get the products ordered by increasing price");
118 assert!(
119 ordered_products.iter().map(|v| &v.name).eq([
120 "Rust-Proof Coffee Mug",
121 "Async Teapot",
122 "Zero-Cost Abstraction Hoodie"
123 ]
124 .into_iter())
125 );
126 let zero_stock = Product::find_one(executor, expr!(Product::stock == 0))
127 .await
128 .expect("Failed to query product with zero stock")
129 .expect("Expected a product with zero stock");
130 assert_eq!(zero_stock.id, 3);
131
132 let mut prod4 = Product::find_one(executor, expr!(Product::id == 4))
134 .await
135 .expect("Failed to query product 4")
136 .expect("Product 4 expected");
137 let old_stock = prod4.stock.unwrap_or(0);
138 prod4.stock = Some(old_stock - 1);
139 prod4
140 .save(executor)
141 .await
142 .expect("Failed to save updated product 4");
143 let prod4_after = Product::find_one(executor, expr!(Product::id == 4))
144 .await
145 .expect("Failed to query product 4 after update")
146 .expect("Product 4 expected after update");
147 assert_eq!(prod4_after.stock, Some(old_stock - 1));
148
149 User::drop_table(executor, true, false)
151 .await
152 .expect("Failed to drop user table");
153 User::create_table(executor, false, false)
154 .await
155 .expect("Failed to create the user table");
156 let users = vec![
157 User {
158 id: Uuid::new_v4(),
159 name: "Alice Compiler".into(),
160 email: "alice@example.com".into(),
161 birthday: Date::from_calendar_date(1995, Month::May, 17).unwrap(),
162 #[cfg(not(feature = "disable-lists"))]
163 preferences: Some(vec!["dark_mode".into(), "express_shipping".into()].into()),
164 registered: PrimitiveDateTime::new(
165 Date::from_calendar_date(2023, Month::January, 2).unwrap(),
166 Time::from_hms(10, 30, 0).unwrap(),
167 ),
168 },
169 User {
170 id: Uuid::new_v4(),
171 name: "Bob Segfault".into(),
172 email: "bob@crashmail.net".into(),
173 birthday: Date::from_calendar_date(1988, Month::March, 12).unwrap(),
174 #[cfg(not(feature = "disable-lists"))]
175 preferences: None,
176 registered: PrimitiveDateTime::new(
177 Date::from_calendar_date(2024, Month::June, 8).unwrap(),
178 Time::from_hms(22, 15, 0).unwrap(),
179 ),
180 },
181 ];
182 User::insert_many(executor, &users)
183 .await
184 .expect("Could not insert the users");
185 let row = pin!(
186 executor.fetch(
187 QueryBuilder::new()
188 .select(cols!(COUNT(*)))
189 .from(User::table())
190 .where_condition(true)
191 .limit(Some(1))
192 .build(&executor.driver())
193 )
194 )
195 .try_next()
196 .await
197 .expect("Failed to query for count")
198 .expect("Did not return some value");
199 assert_eq!(i64::try_from_value(row.values[0].clone()).unwrap(), 2);
200
201 Cart::drop_table(executor, true, false)
203 .await
204 .expect("Failed to drop cart table");
205 Cart::create_table(executor, false, false)
206 .await
207 .expect("Failed to create the cart table");
208 let carts = vec![
209 Cart {
210 user: users[0].id,
211 product: 1,
212 price: Decimal::new(12_99, 2).into(),
213 timestamp: PrimitiveDateTime::new(
214 Date::from_calendar_date(2025, Month::March, 1).unwrap(),
215 Time::from_hms(9, 0, 0).unwrap(),
216 ),
217 },
218 Cart {
219 user: users[0].id,
220 product: 2,
221 price: Decimal::new(49_95, 2).into(),
222 timestamp: PrimitiveDateTime::new(
223 Date::from_calendar_date(2025, Month::March, 1).unwrap(),
224 Time::from_hms(9, 5, 0).unwrap(),
225 ),
226 },
227 Cart {
228 user: users[1].id,
229 product: 4,
230 price: Decimal::new(23_50, 2).into(),
231 timestamp: PrimitiveDateTime::new(
232 Date::from_calendar_date(2025, Month::March, 3).unwrap(),
233 Time::from_hms(14, 12, 0).unwrap(),
234 ),
235 },
236 ];
237 Cart::insert_many(executor, &carts)
238 .await
239 .expect("Could not insert the carts");
240 let cart_count = Cart::find_many(executor, &true, None).count().await;
241 assert_eq!(cart_count, 3);
242
243 let cart_for_4 = Cart::find_one(executor, expr!(Cart::product == 4))
245 .await
246 .expect("Failed to query cart for product 4");
247 let cart_for_4 = cart_for_4.expect("Expected a cart for product 4");
248 let product4 = Product::find_one(executor, expr!(Product::id == 4))
249 .await
250 .expect("Failed to query product 4 for price check")
251 .expect("Expected product 4");
252 assert_eq!(cart_for_4.price.0, Decimal::new(23_50, 2));
253 assert_eq!(product4.price.0, Decimal::new(25_00, 2));
254
255 let cart_for_2 = Cart::find_one(executor, expr!(Cart::product == 2))
257 .await
258 .expect("Failed to query cart for product 2")
259 .expect("Expected a cart for product 2");
260 cart_for_2
261 .delete(executor)
262 .await
263 .expect("Failed to delete cart for product 2");
264 let cart_count_after = Cart::find_many(executor, &true, None).count().await;
265 assert_eq!(cart_count_after, 2);
266
267 #[derive(Debug, Entity, PartialEq)]
269 struct Carts {
270 user: String,
271 product: String,
272 price: Decimal,
273 }
274 let carts: Vec<Carts> = executor
275 .fetch(
276 QueryBuilder::new()
277 .select(cols!(Product::name as product ASC, User::name as user ASC, Cart::price))
278 .from(join!(
279 User INNER JOIN Cart ON User::id == Cart::user
280 JOIN Product ON Cart::product == Product::id
281 ))
282 .where_condition(true)
283 .build(&executor.driver()),
284 )
285 .map_ok(Carts::from_row)
286 .map(Result::flatten)
287 .try_collect::<Vec<_>>()
288 .await
289 .expect("Could not get the products ordered by increasing price");
290 assert_eq!(
291 carts,
292 &[
293 Carts {
294 user: "Bob Segfault".into(),
295 product: "Async Teapot".into(),
296 price: Decimal::new(23_50, 2),
297 },
298 Carts {
299 user: "Alice Compiler".into(),
300 product: "Rust-Proof Coffee Mug".into(),
301 price: Decimal::new(12_99, 2),
302 },
303 ]
304 )
305}