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