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