1#![allow(unused_imports)]
2use std::{collections::HashSet, pin::pin, sync::LazyLock};
3use tank::{
4 DynQuery, AsValue, Dataset, Driver, Entity, Executor, Query, QueryBuilder, QueryResult,
5 Row, 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, Clone, PartialEq, Debug)]
13#[tank(schema = "testing", name = "authors")]
14pub struct Author {
15 #[tank(primary_key, name = "author_id")]
16 pub id: Uuid,
17 pub name: String,
18 pub country: String,
19 pub books_published: Option<u16>,
20}
21
22#[derive(Entity, Clone, PartialEq, Debug)]
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(executor: &mut impl Executor) {
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").unwrap(),
58 name: "J.K. Rowling".into(),
59 country: "UK".into(),
60 books_published: 24.into(),
61 },
62 Author {
63 id: Uuid::parse_str("a73bc06a-ff89-44b9-a62f-416ebe976285").unwrap(),
64 name: "J.R.R. Tolkien".into(),
65 country: "USA".into(),
66 books_published: 6.into(),
67 },
68 Author {
69 id: Uuid::parse_str("6b2f56a1-316d-42b9-a8ba-baca42c5416c").unwrap(),
70 name: "Dmitrij Gluchovskij".into(),
71 country: "Russia".into(),
72 books_published: 7.into(),
73 },
74 Author {
75 id: Uuid::parse_str("d3d3d3d3-d3d3-d3d3-d3d3-d3d3d3d3d3d3").unwrap(),
76 name: "Linus Torvalds".into(),
77 country: "Finland".into(),
78 books_published: None,
79 },
80 ];
81 let rowling_id = authors[0].id.clone();
82 let tolkien_id = authors[1].id.clone();
83 let gluchovskij_id = authors[2].id.clone();
84
85 let books = vec![
87 Book {
88 #[cfg(not(feature = "disable-arrays"))]
89 isbn: [9, 7, 8, 0, 7, 4, 7, 5, 3, 2, 6, 9, 9],
90 title: "Harry Potter and the Philosopher's Stone".into(),
91 author: rowling_id,
92 co_author: None,
93 year: 1937,
94 },
95 Book {
96 #[cfg(not(feature = "disable-arrays"))]
97 isbn: [9, 7, 8, 0, 7, 4, 7, 5, 9, 1, 0, 5, 4],
98 title: "Harry Potter and the Deathly Hallows".into(),
99 author: rowling_id,
100 co_author: None,
101 year: 2007,
102 },
103 Book {
104 #[cfg(not(feature = "disable-arrays"))]
105 isbn: [9, 7, 8, 0, 6, 1, 8, 2, 6, 0, 3, 0, 0],
106 title: "The Hobbit".into(),
107 author: tolkien_id,
108 co_author: None,
109 year: 1996,
110 },
111 Book {
112 #[cfg(not(feature = "disable-arrays"))]
113 isbn: [9, 7, 8, 5, 1, 7, 0, 5, 9, 6, 7, 8, 2],
114 title: "Metro 2033".into(),
115 author: gluchovskij_id,
116 co_author: None,
117 year: 2002,
118 },
119 Book {
120 #[cfg(not(feature = "disable-arrays"))]
121 isbn: [9, 7, 8, 0, 0, 2, 3, 4, 5, 6, 7, 8, 9],
122 title: "Hogwarts 2033".into(),
123 author: rowling_id,
124 co_author: gluchovskij_id.into(),
125 year: 2026,
126 },
127 ];
128
129 let result = Author::insert_many(executor, authors.iter())
131 .await
132 .expect("Failed to insert authors");
133 if let Some(affected) = result.rows_affected {
134 assert_eq!(affected, 4);
135 }
136 let result = Book::insert_many(executor, books.iter())
137 .await
138 .expect("Failed to insert books");
139 if let Some(affected) = result.rows_affected {
140 assert_eq!(affected, 5);
141 }
142
143 let id = Uuid::parse_str("f938f818-0a40-4ce3-8fbc-259ac252a1b5").unwrap().as_value();
145 let author = Author::find_one(
146 executor,
147 expr!(Author::id == #id)
148 )
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
175 #[cfg(not(feature = "disable-joins"))]
176 {
177 #[derive(Entity, PartialEq, Debug)]
179 struct BookAuthorResult {
180 #[tank(name = "title")]
181 book: String,
182 #[tank(name = "name")]
183 author: String,
184 }
185 let result = executor
186 .fetch(
187 QueryBuilder::new()
188 .select(cols!(B.title, A.name))
189 .from(join!(Book B JOIN Author A ON B.author == A.author_id))
190 .where_expr(expr!(B.year < 2000))
191 .order_by(cols!(B.title DESC))
192 .build(&executor.driver()),
193 )
194 .map_ok(BookAuthorResult::from_row)
195 .map(Result::flatten)
196 .try_collect::<Vec<_>>()
197 .await
198 .expect("Failed to query books and authors joined");
199 assert_eq!(
200 result,
201 [
202 BookAuthorResult{
203 book: "The Hobbit".into(),
204 author: "J.R.R. Tolkien".into()
205 },
206 BookAuthorResult{
207 book: "Harry Potter and the Philosopher's Stone".into(),
208 author: "J.K. Rowling".into(),
209 },
210 ]
211 );
212
213 let dataset = join!(
215 Book B LEFT JOIN Author A1 ON B.author == A1.author_id
216 LEFT JOIN Author A2 ON B.co_author == A2.author_id
217 );
218 let result = executor.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.fetch(
283 QueryBuilder::new()
284 .select(cols!(Book::title, Author::name as author, Book::year))
285 .from(join!(Book JOIN Author ON Book::author == Author::id))
286 .where_expr(true)
287 .build(&executor.driver())
288 )
289 .and_then(|row| async { Books::from_row(row) })
290 .try_collect::<HashSet<_>>()
291 .await
292 .expect("Could not return the books");
293 assert_eq!(
294 books,
295 HashSet::from_iter([
296 Books {
297 title: Some("Harry Potter and the Philosopher's Stone".into()),
298 author: Some("J.K. Rowling".into())
299 },
300 Books {
301 title: Some("Harry Potter and the Deathly Hallows".into()),
302 author: Some("J.K. Rowling".into())
303 },
304 Books {
305 title: Some("The Hobbit".into()),
306 author: Some("J.R.R. Tolkien".into())
307 },
308 Books {
309 title: Some("Metro 2033".into()),
310 author: Some("Dmitrij Gluchovskij".into())
311 },
312 Books {
313 title: Some("Hogwarts 2033".into()),
314 author: Some("J.K. Rowling".into())
315 },
316 ])
317 );
318 }
319
320 #[cfg(not(feature = "disable-references"))]
321 {
322 use crate::silent_logs;
324 let book = Book {
325 #[cfg(not(feature = "disable-arrays"))]
326 isbn: [9, 7, 8, 1, 7, 3, 3, 5, 6, 1, 0, 8, 0],
327 title: "My book".into(),
328 author: Uuid::parse_str("c18c04b4-1aae-48a3-9814-9b70f7a38315").unwrap(),
329 co_author: None,
330 year: 2025,
331 };
332 silent_logs! {
333 assert!(
334 book.save(executor).await.is_err(),
335 "Must fail to save book violating referential integrity"
336 );
337 }
338 }
339
340 #[cfg(not(feature = "disable-ordering"))]
341 {
342 let authors = executor.fetch(
344 QueryBuilder::new()
345 .select([Author::name])
346 .from(Author::table())
347 .where_expr(true)
348 .order_by(cols!(Author::name ASC))
349 .build(&executor.driver())
350 )
351 .and_then(|row| async move { AsValue::try_from_value((*row.values)[0].clone()) })
352 .try_collect::<Vec<String>>()
353 .await
354 .expect("Could not return the ordered names of the authors");
355 assert_eq!(
356 authors,
357 vec![
358 "Dmitrij Gluchovskij".to_string(),
359 "J.K. Rowling".to_string(),
360 "J.R.R. Tolkien".to_string(),
361 "Linus Torvalds".to_string(),
362 ]
363 )
364 }
365
366 #[cfg(not(feature = "disable-multiple-statements"))]
368 {
369 let mut query = DynQuery::default();
370 let writer = executor.driver().sql_writer();
371 writer.write_select(
372 &mut query,
373 &QueryBuilder::new()
374 .select(Book::columns())
375 .from(Book::table())
376 .where_expr(expr!(Book::title == "Metro 2033"))
377 .limit(Some(1))
378 );
379 writer.write_select(
380 &mut query,
381 &QueryBuilder::new()
382 .select(Book::columns())
383 .from(Book::table())
384 .where_expr(expr!(Book::title == "Harry Potter and the Deathly Hallows"))
385 .limit(Some(1))
386 );
387 let mut stream = pin!(executor.run(query));
388 let Some(Ok(QueryResult::Row(row))) = stream.next().await else {
389 panic!("Could not get the first row")
390 };
391 let book = Book::from_row(row).expect("Could not get the book from row");
392 assert_eq!(
393 book,
394 Book {
395 #[cfg(not(feature = "disable-arrays"))]
396 isbn: [9, 7, 8, 5, 1, 7, 0, 5, 9, 6, 7, 8, 2],
397 title: "Metro 2033".into(),
398 author: gluchovskij_id,
399 co_author: None,
400 year: 2002,
401 }
402 );
403 let Some(Ok(QueryResult::Row(row))) = stream.next().await else {
404 panic!("Could not get the second 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, 0, 7, 4, 7, 5, 9, 1, 0, 5, 4],
412 title: "Harry Potter and the Deathly Hallows".into(),
413 author: rowling_id,
414 co_author: None,
415 year: 2007,
416 }
417 );
418 assert!(
419 stream.next().await.is_none(),
420 "The stream should return only two rows"
421 )
422 }
423}