1#![allow(unused_imports)]
2use std::{collections::HashSet, pin::pin, sync::LazyLock};
3use tank::{
4 DynQuery, AsValue, Dataset, Driver, Entity, Executor, Passive, Query, QueryBuilder, QueryResult,
5 RowLabeled, SqlWriter, Value, cols, expr, join, stream::{StreamExt, TryStreamExt}
6};
7use tokio::sync::Mutex;
8use uuid::Uuid;
9
10static MUTEX: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));
11
12#[derive(Entity, Debug, Clone, PartialEq)]
13#[tank(schema = "testing", name = "authors")]
14pub struct Author {
15 #[tank(primary_key, name = "author_id")]
16 pub id: Passive<Uuid>,
17 pub name: String,
18 pub country: String,
19 pub books_published: Option<u16>,
20}
21
22#[derive(Entity, Debug, Clone, PartialEq)]
23#[tank(schema = "testing", name = "books", primary_key = (Self::title, Self::author))]
24pub struct Book {
25 #[cfg(not(feature = "disable-arrays"))]
26 pub isbn: [u8; 13],
27 #[tank(column_type = (mysql = "VARCHAR(255)"))]
28 pub title: String,
29 #[tank(references = Author::id)]
31 pub author: Uuid,
32 #[tank(references = Author::id)]
33 pub co_author: Option<Uuid>,
34 pub year: i32,
35}
36
37pub async fn books<E: Executor>(executor: &mut E) {
38 let _lock = MUTEX.lock().await;
39
40 Book::drop_table(executor, true, false)
42 .await
43 .expect("Failed to drop Book table");
44 Author::drop_table(executor, true, false)
45 .await
46 .expect("Failed to drop Author table");
47 Author::create_table(executor, false, true)
48 .await
49 .expect("Failed to create Author table");
50 Book::create_table(executor, false, true)
51 .await
52 .expect("Failed to create Book table");
53
54 let authors = vec![
56 Author {
57 id: Uuid::parse_str("f938f818-0a40-4ce3-8fbc-259ac252a1b5")
58 .unwrap()
59 .into(),
60 name: "J.K. Rowling".into(),
61 country: "UK".into(),
62 books_published: 24.into(),
63 },
64 Author {
65 id: Uuid::parse_str("a73bc06a-ff89-44b9-a62f-416ebe976285")
66 .unwrap()
67 .into(),
68 name: "J.R.R. Tolkien".into(),
69 country: "USA".into(),
70 books_published: 6.into(),
71 },
72 Author {
73 id: Uuid::parse_str("6b2f56a1-316d-42b9-a8ba-baca42c5416c")
74 .unwrap()
75 .into(),
76 name: "Dmitrij Gluchovskij".into(),
77 country: "Russia".into(),
78 books_published: 7.into(),
79 },
80 Author {
81 id: Uuid::parse_str("d3d3d3d3-d3d3-d3d3-d3d3-d3d3d3d3d3d3")
82 .unwrap()
83 .into(),
84 name: "Linus Torvalds".into(),
85 country: "Finland".into(),
86 books_published: None,
87 },
88 ];
89 let rowling_id = authors[0].id.clone().unwrap();
90 let tolkien_id = authors[1].id.clone().unwrap();
91 let gluchovskij_id = authors[2].id.clone().unwrap();
92
93 let books = vec![
95 Book {
96 #[cfg(not(feature = "disable-arrays"))]
97 isbn: [9, 7, 8, 0, 7, 4, 7, 5, 3, 2, 6, 9, 9],
98 title: "Harry Potter and the Philosopher's Stone".into(),
99 author: rowling_id,
100 co_author: None,
101 year: 1937,
102 },
103 Book {
104 #[cfg(not(feature = "disable-arrays"))]
105 isbn: [9, 7, 8, 0, 7, 4, 7, 5, 9, 1, 0, 5, 4],
106 title: "Harry Potter and the Deathly Hallows".into(),
107 author: rowling_id,
108 co_author: None,
109 year: 2007,
110 },
111 Book {
112 #[cfg(not(feature = "disable-arrays"))]
113 isbn: [9, 7, 8, 0, 6, 1, 8, 2, 6, 0, 3, 0, 0],
114 title: "The Hobbit".into(),
115 author: tolkien_id,
116 co_author: None,
117 year: 1996,
118 },
119 Book {
120 #[cfg(not(feature = "disable-arrays"))]
121 isbn: [9, 7, 8, 5, 1, 7, 0, 5, 9, 6, 7, 8, 2],
122 title: "Metro 2033".into(),
123 author: gluchovskij_id,
124 co_author: None,
125 year: 2002,
126 },
127 Book {
128 #[cfg(not(feature = "disable-arrays"))]
129 isbn: [9, 7, 8, 0, 0, 2, 3, 4, 5, 6, 7, 8, 9],
130 title: "Hogwarts 2033".into(),
131 author: rowling_id,
132 co_author: gluchovskij_id.into(),
133 year: 2026,
134 },
135 ];
136
137 let result = Author::insert_many(executor, authors.iter())
139 .await
140 .expect("Failed to insert authors");
141 if let Some(affected) = result.rows_affected {
142 assert_eq!(affected, 4);
143 }
144 let result = Book::insert_many(executor, books.iter())
145 .await
146 .expect("Failed to insert books");
147 if let Some(affected) = result.rows_affected {
148 assert_eq!(affected, 5);
149 }
150
151 let id = Uuid::parse_str("f938f818-0a40-4ce3-8fbc-259ac252a1b5").unwrap().as_value();
153 let author = Author::find_one(
154 executor,
155 expr!(Author::id == #id)
156 )
157 .await
158 .expect("Failed to query author by pk");
159 assert_eq!(
160 author,
161 Some(Author {
162 id: Uuid::parse_str("f938f818-0a40-4ce3-8fbc-259ac252a1b5")
163 .unwrap()
164 .into(),
165 name: "J.K. Rowling".into(),
166 country: "UK".into(),
167 books_published: 24.into(),
168 })
169 );
170
171 let author = Author::find_one(executor, expr!(Author::name == "Linus Torvalds"))
172 .await
173 .expect("Failed to query author by pk");
174 assert_eq!(
175 author,
176 Some(Author {
177 id: Uuid::parse_str("d3d3d3d3-d3d3-d3d3-d3d3-d3d3d3d3d3d3")
178 .unwrap()
179 .into(),
180 name: "Linus Torvalds".into(),
181 country: "Finland".into(),
182 books_published: None,
183 })
184 );
185
186 #[cfg(not(feature = "disable-multiple-statements"))]
188 {
189 let result = executor
190 .fetch(
191 QueryBuilder::new()
192 .select(cols!(B.title, A.name))
193 .from(join!(Book B JOIN Author A ON B.author == A.author_id))
194 .where_expr(expr!(B.year < 2000))
195 .build(&executor.driver()),
196 )
197 .try_collect::<Vec<RowLabeled>>()
198 .await
199 .expect("Failed to query books and authors joined")
200 .into_iter()
201 .map(|row| {
202 let mut iter = row.values.into_iter();
203 (
204 match iter.next().unwrap() {
205 Value::Varchar(Some(v)) => v,
206 Value::Unknown(Some(v)) => v.into(),
207 v => panic!("Expected first value to be non null varchar, found {v:?}"),
208 },
209 match iter.next().unwrap() {
210 Value::Varchar(Some(v)) => v,
211 Value::Unknown(Some(v)) => v.into(),
212 v => panic!("Expected second value to be non null varchar, found {v:?}"),
213 },
214 )
215 })
216 .collect::<HashSet<_>>();
217 assert_eq!(
218 result,
219 HashSet::from_iter([
220 (
221 "Harry Potter and the Philosopher's Stone".into(),
222 "J.K. Rowling".into()
223 ),
224 ("The Hobbit".into(), "J.R.R. Tolkien".into()),
225 ])
226 );
227
228 let dataset = join!(
230 Book B LEFT JOIN Author A1 ON B.author == A1.author_id
231 LEFT JOIN Author A2 ON B.co_author == A2.author_id
232 );
233 let result = executor.fetch(
234 QueryBuilder::new()
235 .select(cols!(B.title, A1.name as author, A2.name as co_author))
236 .from(dataset)
237 .where_expr(true)
238 .build(&executor.driver())
239 )
240 .try_collect::<Vec<RowLabeled>>()
241 .await
242 .expect("Failed to query books and authors joined")
243 .into_iter()
244 .map(|row| {
245 let mut iter = row.values.into_iter();
246 (
247 match iter.next().unwrap() {
248 Value::Varchar(Some(v)) => v,
249 Value::Unknown(Some(v)) => v.into(),
250 v => panic!("Expected 1st value to be non null varchar, found {v:?}"),
251 },
252 match iter.next().unwrap() {
253 Value::Varchar(Some(v)) => v,
254 Value::Unknown(Some(v)) => v.into(),
255 v => panic!("Expected 2nd value to be non null varchar, found {v:?}"),
256 },
257 match iter.next().unwrap() {
258 Value::Varchar(Some(v)) => Some(v),
259 Value::Unknown(Some(v)) => Some(v.into()),
260 Value::Varchar(None) | Value::Null => None,
261 v => panic!(
262 "Expected 3rd value to be a Some(Value::Varchar(..)) | Value::Unknown(Some(..)) | Some(Value::Null)), found {v:?}",
263 ),
264 },
265 )
266 })
267 .collect::<HashSet<_>>();
268 assert_eq!(
269 result,
270 HashSet::from_iter([
271 (
272 "Harry Potter and the Philosopher's Stone".into(),
273 "J.K. Rowling".into(),
274 None
275 ),
276 (
277 "Harry Potter and the Deathly Hallows".into(),
278 "J.K. Rowling".into(),
279 None
280 ),
281 ("The Hobbit".into(), "J.R.R. Tolkien".into(), None),
282 ("Metro 2033".into(), "Dmitrij Gluchovskij".into(), None),
283 (
284 "Hogwarts 2033".into(),
285 "J.K. Rowling".into(),
286 Some("Dmitrij Gluchovskij".into())
287 ),
288 ])
289 );
290
291 #[derive(Debug, Entity, PartialEq, Eq, Hash)]
293 struct Books {
294 pub title: Option<String>,
295 pub author: Option<String>,
296 }
297 let books = executor.fetch(
298 QueryBuilder::new()
299 .select(cols!(Book::title, Author::name as author, Book::year))
300 .from(join!(Book JOIN Author ON Book::author == Author::id))
301 .where_expr(true)
302 .build(&executor.driver())
303 )
304 .and_then(|row| async { Books::from_row(row) })
305 .try_collect::<HashSet<_>>()
306 .await
307 .expect("Could not return the books");
308 assert_eq!(
309 books,
310 HashSet::from_iter([
311 Books {
312 title: Some("Harry Potter and the Philosopher's Stone".into()),
313 author: Some("J.K. Rowling".into())
314 },
315 Books {
316 title: Some("Harry Potter and the Deathly Hallows".into()),
317 author: Some("J.K. Rowling".into())
318 },
319 Books {
320 title: Some("The Hobbit".into()),
321 author: Some("J.R.R. Tolkien".into())
322 },
323 Books {
324 title: Some("Metro 2033".into()),
325 author: Some("Dmitrij Gluchovskij".into())
326 },
327 Books {
328 title: Some("Hogwarts 2033".into()),
329 author: Some("J.K. Rowling".into())
330 },
331 ])
332 );
333
334 #[cfg(not(feature = "disable-references"))]
335 {
336 use crate::silent_logs;
338 let book = Book {
339 #[cfg(not(feature = "disable-arrays"))]
340 isbn: [9, 7, 8, 1, 7, 3, 3, 5, 6, 1, 0, 8, 0],
341 title: "My book".into(),
342 author: Uuid::parse_str("c18c04b4-1aae-48a3-9814-9b70f7a38315").unwrap(),
343 co_author: None,
344 year: 2025,
345 };
346 silent_logs! {
347 assert!(
348 book.save(executor).await.is_err(),
349 "Must fail to save book violating referential integrity"
350 );
351 }
352 }
353 }
354
355 #[cfg(not(feature = "disable-ordering"))]
356 {
357 let authors = executor.fetch(
359 QueryBuilder::new()
360 .select([Author::name])
361 .from(Author::table())
362 .where_expr(true)
363 .order_by(cols!(Author::name ASC))
364 .build(&executor.driver())
365 )
366 .and_then(|row| async move { AsValue::try_from_value((*row.values)[0].clone()) })
367 .try_collect::<Vec<String>>()
368 .await
369 .expect("Could not return the ordered names of the authors");
370 assert_eq!(
371 authors,
372 vec![
373 "Dmitrij Gluchovskij".to_string(),
374 "J.K. Rowling".to_string(),
375 "J.R.R. Tolkien".to_string(),
376 "Linus Torvalds".to_string(),
377 ]
378 )
379 }
380
381 #[cfg(not(feature = "disable-multiple-statements"))]
383 {
384 let mut query = DynQuery::default();
385 let writer = executor.driver().sql_writer();
386 writer.write_select(
387 &mut query,
388 &QueryBuilder::new()
389 .select(Book::columns())
390 .from(Book::table())
391 .where_expr(expr!(Book::title == "Metro 2033"))
392 .limit(Some(1))
393 );
394 writer.write_select(
395 &mut query,
396 &QueryBuilder::new()
397 .select(Book::columns())
398 .from(Book::table())
399 .where_expr(expr!(Book::title == "Harry Potter and the Deathly Hallows"))
400 .limit(Some(1))
401 );
402 let mut stream = pin!(executor.run(query));
403 let Some(Ok(QueryResult::Row(row))) = stream.next().await else {
404 panic!("Could not get the first row")
405 };
406 let book = Book::from_row(row).expect("Could not get the book from row");
407 assert_eq!(
408 book,
409 Book {
410 #[cfg(not(feature = "disable-arrays"))]
411 isbn: [9, 7, 8, 5, 1, 7, 0, 5, 9, 6, 7, 8, 2],
412 title: "Metro 2033".into(),
413 author: gluchovskij_id,
414 co_author: None,
415 year: 2002,
416 }
417 );
418 let Some(Ok(QueryResult::Row(row))) = stream.next().await else {
419 panic!("Could not get the second row")
420 };
421 let book = Book::from_row(row).expect("Could not get the book from row");
422 assert_eq!(
423 book,
424 Book {
425 #[cfg(not(feature = "disable-arrays"))]
426 isbn: [9, 7, 8, 0, 7, 4, 7, 5, 9, 1, 0, 5, 4],
427 title: "Harry Potter and the Deathly Hallows".into(),
428 author: rowling_id,
429 co_author: None,
430 year: 2007,
431 }
432 );
433 assert!(
434 stream.next().await.is_none(),
435 "The stream should return only two rows"
436 )
437 }
438}