1#![allow(clippy::uninlined_format_args)]
2
3extern crate self as turso_mappers;
4
5use std::collections::HashMap;
6use std::future::Future;
7use turso::{Column, Connection, IntoParams};
8pub use turso_mappers_derive::{TryFromRowByIndex, TryFromRowByName};
9
10#[doc = include_str!("../README.md")]
11#[cfg(doctest)]
12pub struct ReadmeDocTests;
13
14#[derive(Debug)]
15pub enum TursoMapperError {
16 ColumnNotFound(String),
17 InvalidType(String),
18 NullValue(String),
19 ConversionError(String),
20 IoError(std::io::Error),
21 TursoError(turso::Error),
22}
23
24impl From<turso::Error> for TursoMapperError {
25 fn from(err: turso::Error) -> Self {
26 TursoMapperError::TursoError(err)
27 }
28}
29
30impl From<std::io::Error> for TursoMapperError {
31 fn from(err: std::io::Error) -> Self {
32 TursoMapperError::IoError(err)
33 }
34}
35
36impl std::fmt::Display for TursoMapperError {
37 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38 match self {
39 TursoMapperError::ColumnNotFound(msg) => write!(f, "Column not found: {}", msg),
40 TursoMapperError::InvalidType(msg) => write!(f, "Invalid type: {}", msg),
41 TursoMapperError::NullValue(msg) => write!(f, "Null value: {}", msg),
42 TursoMapperError::ConversionError(msg) => write!(f, "Conversion error: {}", msg),
43 TursoMapperError::IoError(err) => write!(f, "IO error: {}", err),
44 TursoMapperError::TursoError(err) => write!(f, "Turso error: {}", err),
45 }
46 }
47}
48
49impl std::error::Error for TursoMapperError {}
50
51pub type TursoMapperResult<T> = Result<T, TursoMapperError>;
52
53pub trait MapRows {
54 fn map_rows<F, T>(self, f: F) -> impl Future<Output = TursoMapperResult<Vec<T>>>
55 where
56 F: Fn(turso::Row) -> TursoMapperResult<T>,
57 T: Send;
58}
59
60impl MapRows for turso::Rows {
61 async fn map_rows<F, T>(mut self, f: F) -> TursoMapperResult<Vec<T>>
62 where
63 F: Fn(turso::Row) -> TursoMapperResult<T>,
64 T: Send,
65 {
66 let mut rows = vec![];
67
68 while let Some(row) = self.next().await? {
69 let t: T = f(row)?;
70 rows.push(t);
71 }
72
73 Ok(rows)
74 }
75}
76
77pub trait TryFromRow: Send {
78 fn try_from_row(row: turso::Row) -> TursoMapperResult<Self>
79 where
80 Self: Sized;
81}
82
83pub trait QueryAs {
84 fn query_as<T>(&self, sql: &str, params: impl IntoParams) -> impl Future<Output = TursoMapperResult<Vec<T>>>
85 where
86 T: TryFromRow + Send;
87}
88
89impl QueryAs for Connection {
90 async fn query_as<T>(&self, sql: &str, params: impl IntoParams) -> TursoMapperResult<Vec<T>>
91 where
92 T: TryFromRow + Send,
93 {
94 let rows = self.query(sql, params).await?;
95 rows.map_rows(T::try_from_row).await
96 }
97}
98
99pub trait TryFromRowByName: Send {
100 type Indices;
101 fn resolve_indices(column_indices: &ColumnIndices) -> TursoMapperResult<Self::Indices>;
102 fn try_from_row_by_name(row: turso::Row, indices: &Self::Indices) -> TursoMapperResult<Self>
103 where
104 Self: Sized;
105}
106
107pub trait QueryAsByName {
108 fn query_as_by_name<T>(&self, sql: &str, params: impl IntoParams) -> impl Future<Output = TursoMapperResult<Vec<T>>>
109 where
110 T: TryFromRowByName + Send;
111}
112
113impl QueryAsByName for Connection {
114 async fn query_as_by_name<T>(&self, sql: &str, params: impl IntoParams) -> TursoMapperResult<Vec<T>>
115 where
116 T: TryFromRowByName + Send,
117 {
118 let mut rows = self.query(sql, params).await?;
119 let column_indices = ColumnIndices::new(rows.columns());
120 let indices = T::resolve_indices(&column_indices)?;
121 let mut results = vec![];
122 while let Some(row) = rows.next().await? {
123 results.push(T::try_from_row_by_name(row, &indices)?);
124 }
125 Ok(results)
126 }
127}
128
129pub trait TryFromRowByIndex: Send {
130 fn try_from_row_by_index(row: turso::Row) -> TursoMapperResult<Self>
131 where
132 Self: Sized;
133}
134
135pub trait QueryAsByIndex {
136 fn query_as_by_index<T>(&self, sql: &str, params: impl IntoParams) -> impl Future<Output = TursoMapperResult<Vec<T>>>
137 where
138 T: TryFromRowByIndex + Send;
139}
140
141impl QueryAsByIndex for Connection {
142 async fn query_as_by_index<T>(&self, sql: &str, params: impl IntoParams) -> TursoMapperResult<Vec<T>>
143 where
144 T: TryFromRowByIndex + Send,
145 {
146 let rows = self.query(sql, params).await?;
147 rows.map_rows(T::try_from_row_by_index).await
148 }
149}
150
151pub struct ColumnIndices {
152 column_names: HashMap<String, usize>,
153}
154
155impl ColumnIndices {
156 pub fn new(columns: Vec<Column>) -> Self {
157 let column_names = columns
158 .iter()
159 .enumerate()
160 .map(|(i, column)| (column.name().to_string(), i))
161 .collect::<HashMap<String, usize>>();
162
163 ColumnIndices { column_names }
164 }
165
166 pub fn get_index(&self, column_name: &str) -> Result<usize, TursoMapperError> {
167 self.column_names
168 .get(column_name)
169 .cloned()
170 .ok_or_else(|| TursoMapperError::ColumnNotFound(column_name.to_string()))
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::{ColumnIndices, QueryAs, QueryAsByIndex, QueryAsByName, TryFromRow, TryFromRowByIndex, TryFromRowByName, TursoMapperResult};
177 use crate::{MapRows, TursoMapperError};
178 use turso::{Builder, Row};
179
180 struct CustomerWithManualTryFromRow {
181 id: i64,
182 name: String,
183 value: f64,
184 image: Vec<u8>,
185 }
186
187 impl TryFromRowByIndex for CustomerWithManualTryFromRow {
188 fn try_from_row_by_index(row: Row) -> TursoMapperResult<Self> {
189 Ok(CustomerWithManualTryFromRow {
190 id: *row
191 .get_value(0)?
192 .as_integer()
193 .ok_or_else(|| TursoMapperError::ConversionError("id is not an integer".to_string()))?,
194 name: row
195 .get_value(1)?
196 .as_text()
197 .ok_or_else(|| TursoMapperError::ConversionError("name is not a string".to_string()))?
198 .clone(),
199 value: *row
200 .get_value(2)?
201 .as_real()
202 .ok_or_else(|| TursoMapperError::ConversionError("value is not a real".to_string()))?,
203 image: row
204 .get_value(3)?
205 .as_blob()
206 .ok_or_else(|| TursoMapperError::ConversionError("image is not a blob".to_string()))?
207 .clone(),
208 })
209 }
210 }
211
212 #[derive(TryFromRowByIndex)]
213 struct Customer {
214 id: i64,
215 name: String,
216 value: f64,
217 image: Vec<u8>,
218 }
219
220 #[derive(TryFromRowByIndex)]
221 struct CustomerWithOptions {
222 id: i64,
223 name: String,
224 optional_value: Option<f64>,
225 optional_note: Option<String>,
226 optional_data: Option<Vec<u8>>,
227 optional_count: Option<i64>,
228 }
229
230 struct CustomerManual {
232 id: i64,
233 name: String,
234 value: f64,
235 image: Vec<u8>,
236 }
237
238 impl TryFromRow for CustomerManual {
239 fn try_from_row(row: Row) -> TursoMapperResult<Self> {
240 Ok(CustomerManual {
241 id: *row
242 .get_value(0)?
243 .as_integer()
244 .ok_or_else(|| TursoMapperError::ConversionError("id is not an integer".to_string()))?,
245 name: row
246 .get_value(1)?
247 .as_text()
248 .ok_or_else(|| TursoMapperError::ConversionError("name is not a string".to_string()))?
249 .clone(),
250 value: *row
251 .get_value(2)?
252 .as_real()
253 .ok_or_else(|| TursoMapperError::ConversionError("value is not a real".to_string()))?,
254 image: row
255 .get_value(3)?
256 .as_blob()
257 .ok_or_else(|| TursoMapperError::ConversionError("image is not a blob".to_string()))?
258 .clone(),
259 })
260 }
261 }
262
263 struct CustomerManualReordered {
265 id: i64,
266 name: String,
267 value: f64,
268 image: Vec<u8>,
269 }
270
271 impl TryFromRow for CustomerManualReordered {
272 fn try_from_row(row: Row) -> TursoMapperResult<Self> {
273 Ok(CustomerManualReordered {
274 image: row
275 .get_value(0)?
276 .as_blob()
277 .ok_or_else(|| TursoMapperError::ConversionError("image is not a blob".to_string()))?
278 .clone(),
279 value: *row
280 .get_value(1)?
281 .as_real()
282 .ok_or_else(|| TursoMapperError::ConversionError("value is not a real".to_string()))?,
283 name: row
284 .get_value(2)?
285 .as_text()
286 .ok_or_else(|| TursoMapperError::ConversionError("name is not a string".to_string()))?
287 .clone(),
288 id: *row
289 .get_value(3)?
290 .as_integer()
291 .ok_or_else(|| TursoMapperError::ConversionError("id is not an integer".to_string()))?,
292 })
293 }
294 }
295
296 #[tokio::test]
297 async fn can_get_values_using_map() -> TursoMapperResult<()> {
298 let db = Builder::new_local(":memory:").build().await?;
299 let conn = db.connect()?;
300
301 conn.execute(
302 "CREATE TABLE customer (id INTEGER PRIMARY KEY, name TEXT NOT NULL, value REAL NOT NULL, image BLOB NOT NULL);",
303 (),
304 )
305 .await?;
306 conn.execute("INSERT INTO customer (name, value, image) VALUES ('Charlie', 3.12, x'00010203');", ())
307 .await?;
308 conn.execute("INSERT INTO customer (name, value, image) VALUES ('Sarah', 0.99, x'09080706');", ())
309 .await?;
310
311 let rows = conn.query("SELECT id, name, value, image FROM customer;", ()).await?;
312
313 let customer_names = rows
314 .map_rows(|row| {
315
316
317
318 Ok(row
319 .get_value(1)?
320 .as_text()
321 .ok_or_else(|| TursoMapperError::ConversionError("name is not a string".to_string()))?
322 .clone())
323 })
324 .await?;
325
326 assert_eq!(customer_names.len(), 2);
327
328 assert_eq!(customer_names[0], "Charlie");
329 assert_eq!(customer_names[1], "Sarah");
330
331 Ok(())
332 }
333
334 #[tokio::test]
335 async fn can_get_values_using_map_with_names() -> TursoMapperResult<()> {
336 let db = Builder::new_local(":memory:").build().await?;
337 let conn = db.connect()?;
338
339 conn.execute(
340 "CREATE TABLE customer (id INTEGER PRIMARY KEY, name TEXT NOT NULL, value REAL NOT NULL, image BLOB NOT NULL);",
341 (),
342 )
343 .await?;
344
345 conn.execute("INSERT INTO customer (name, value, image) VALUES ('Charlie', 3.12, x'00010203');", ())
346 .await?;
347
348 conn.execute("INSERT INTO customer (name, value, image) VALUES ('Sarah', 0.99, x'09080706');", ())
349 .await?;
350
351 let mut statement = conn.prepare("SELECT id, name, value, image FROM customer;").await?;
352 let rows = statement.query(()).await?;
353
354 let column_indices = ColumnIndices::new(statement.columns());
355 let name_column_index = column_indices.get_index("name")?;
356
357 let customer_names = rows
358 .map_rows(|row| {
359 Ok(row
360 .get_value(name_column_index)?
361 .as_text()
362 .ok_or_else(|| TursoMapperError::ConversionError("name is not a string".to_string()))?
363 .clone())
364 })
365 .await?;
366
367 assert_eq!(customer_names.len(), 2);
368
369 assert_eq!(customer_names[0], "Charlie");
370 assert_eq!(customer_names[1], "Sarah");
371
372 Ok(())
373 }
374
375 #[tokio::test]
376 async fn manual_try_from_row_by_index_impl() -> TursoMapperResult<()> {
377 let db = Builder::new_local(":memory:").build().await?;
378 let conn = db.connect()?;
379 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT NOT NULL, value REAL NOT NULL, image BLOB NOT NULL);", ()).await?;
380 conn.execute("INSERT INTO t (name, value, image) VALUES ('Charlie', 3.12, x'01020300');", ()).await?;
381
382 let mut rows = conn.query("SELECT id, name, value, image FROM t;", ()).await?;
383 let row = rows.next().await?.unwrap();
384 let customer = CustomerWithManualTryFromRow::try_from_row_by_index(row)?;
385
386 assert_eq!(customer.id, 1);
387 assert_eq!(customer.name, "Charlie");
388 assert_eq!(customer.value, 3.12);
389 assert_eq!(customer.image, vec![1, 2, 3, 0]);
390
391 Ok(())
392 }
393
394 #[tokio::test]
395 async fn derived_try_from_row_by_index_impl() -> TursoMapperResult<()> {
396 let db = Builder::new_local(":memory:").build().await?;
397 let conn = db.connect()?;
398 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT NOT NULL, value REAL NOT NULL, image BLOB NOT NULL);", ()).await?;
399 conn.execute("INSERT INTO t (name, value, image) VALUES ('Charlie', 3.12, x'01020300');", ()).await?;
400
401 let mut rows = conn.query("SELECT id, name, value, image FROM t;", ()).await?;
402 let row = rows.next().await?.unwrap();
403 let customer = Customer::try_from_row_by_index(row)?;
404
405 assert_eq!(customer.id, 1);
406 assert_eq!(customer.name, "Charlie");
407 assert_eq!(customer.value, 3.12);
408 assert_eq!(customer.image, vec![1, 2, 3, 0]);
409
410 Ok(())
411 }
412
413 #[tokio::test]
414 async fn end_to_end_test_with_map_rows_and_try_from_row() -> TursoMapperResult<()> {
415 let db = Builder::new_local(":memory:").build().await?;
416 let conn = db.connect()?;
417
418 conn.execute(
419 "CREATE TABLE customer (id INTEGER PRIMARY KEY, name TEXT NOT NULL, value REAL NOT NULL, image BLOB NOT NULL);",
420 (),
421 )
422 .await?;
423
424 conn.execute("INSERT INTO customer (name, value, image) VALUES ('Charlie', 3.12, x'00010203');", ())
425 .await?;
426
427 conn.execute("INSERT INTO customer (name, value, image) VALUES ('Sarah', 0.99, x'09080706');", ())
428 .await?;
429
430 let customers = conn
431 .query("SELECT id, name, value, image FROM customer;", ())
432 .await?
433 .map_rows(Customer::try_from_row_by_index)
434 .await?;
435
436 assert_eq!(customers.len(), 2);
437
438 assert_eq!(customers[0].id, 1);
439 assert_eq!(customers[0].name, "Charlie");
440 assert_eq!(customers[0].value, 3.12);
441 assert_eq!(customers[0].image, vec![0, 1, 2, 3]);
442
443 assert_eq!(customers[1].id, 2);
444 assert_eq!(customers[1].name, "Sarah");
445 assert_eq!(customers[1].value, 0.99);
446 assert_eq!(customers[1].image, vec![9, 8, 7, 6]);
447
448 Ok(())
449 }
450
451 #[tokio::test]
452 async fn end_to_end_test_with_query_as_by_index() -> TursoMapperResult<()> {
453 let db = Builder::new_local(":memory:").build().await?;
454 let conn = db.connect()?;
455
456 conn.execute(
457 "CREATE TABLE customer (id INTEGER PRIMARY KEY, name TEXT NOT NULL, value REAL NOT NULL, image BLOB NOT NULL);",
458 (),
459 )
460 .await?;
461
462 conn.execute("INSERT INTO customer (name, value, image) VALUES ('Charlie', 3.12, x'00010203');", ())
463 .await?;
464
465 conn.execute("INSERT INTO customer (name, value, image) VALUES ('Sarah', 0.99, x'09080706');", ())
466 .await?;
467
468 let customers = conn.query_as_by_index::<Customer>("SELECT id, name, value, image FROM customer;", ()).await?;
469
470 assert_eq!(customers.len(), 2);
471
472 assert_eq!(customers[0].id, 1);
473 assert_eq!(customers[0].name, "Charlie");
474 assert_eq!(customers[0].value, 3.12);
475 assert_eq!(customers[0].image, vec![0, 1, 2, 3]);
476
477 assert_eq!(customers[1].id, 2);
478 assert_eq!(customers[1].name, "Sarah");
479 assert_eq!(customers[1].value, 0.99);
480 assert_eq!(customers[1].image, vec![9, 8, 7, 6]);
481
482 Ok(())
483 }
484
485 #[tokio::test]
486 async fn option_types_support_works() -> TursoMapperResult<()> {
487 let db = Builder::new_local(":memory:").build().await?;
488 let conn = db.connect()?;
489 conn.execute(
490 "CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT NOT NULL, optional_value REAL, optional_note TEXT, optional_data BLOB, optional_count INTEGER);",
491 (),
492 ).await?;
493
494 conn.execute("INSERT INTO t (name, optional_value, optional_data) VALUES ('Charlie', 3.12, x'010203');", ()).await?;
496 conn.execute("INSERT INTO t (name, optional_value, optional_note, optional_data, optional_count) VALUES ('Sarah', 0.99, 'Some note', x'09080706', 42);", ()).await?;
498
499 let customers = conn.query_as_by_index::<CustomerWithOptions>("SELECT id, name, optional_value, optional_note, optional_data, optional_count FROM t;", ()).await?;
500
501 assert_eq!(customers[0].id, 1);
502 assert_eq!(customers[0].name, "Charlie");
503 assert_eq!(customers[0].optional_value, Some(3.12));
504 assert_eq!(customers[0].optional_note, None);
505 assert_eq!(customers[0].optional_data, Some(vec![1, 2, 3]));
506 assert_eq!(customers[0].optional_count, None);
507
508 assert_eq!(customers[1].id, 2);
509 assert_eq!(customers[1].name, "Sarah");
510 assert_eq!(customers[1].optional_value, Some(0.99));
511 assert_eq!(customers[1].optional_note, Some("Some note".to_string()));
512 assert_eq!(customers[1].optional_data, Some(vec![9, 8, 7, 6]));
513 assert_eq!(customers[1].optional_count, Some(42));
514
515 Ok(())
516 }
517
518 #[tokio::test]
521 async fn try_from_row_manual_single_row() -> TursoMapperResult<()> {
522 let db = Builder::new_local(":memory:").build().await?;
523 let conn = db.connect()?;
524 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT NOT NULL, value REAL NOT NULL, image BLOB NOT NULL);", ()).await?;
525 conn.execute("INSERT INTO t (name, value, image) VALUES ('Charlie', 3.12, x'01020300');", ()).await?;
526
527 let mut rows = conn.query("SELECT id, name, value, image FROM t;", ()).await?;
528 let row = rows.next().await?.unwrap();
529 let customer = CustomerManual::try_from_row(row)?;
530
531 assert_eq!(customer.id, 1);
532 assert_eq!(customer.name, "Charlie");
533 assert_eq!(customer.value, 3.12);
534 assert_eq!(customer.image, vec![1, 2, 3, 0]);
535
536 Ok(())
537 }
538
539 #[tokio::test]
540 async fn query_as_end_to_end() -> TursoMapperResult<()> {
541 let db = Builder::new_local(":memory:").build().await?;
542 let conn = db.connect()?;
543 conn.execute("CREATE TABLE customer (id INTEGER PRIMARY KEY, name TEXT NOT NULL, value REAL NOT NULL, image BLOB NOT NULL);", ()).await?;
544 conn.execute("INSERT INTO customer (name, value, image) VALUES ('Charlie', 3.12, x'00010203');", ()).await?;
545 conn.execute("INSERT INTO customer (name, value, image) VALUES ('Sarah', 0.99, x'09080706');", ()).await?;
546
547 let customers = conn.query_as::<CustomerManual>("SELECT id, name, value, image FROM customer;", ()).await?;
548
549 assert_eq!(customers.len(), 2);
550 assert_eq!(customers[0].id, 1);
551 assert_eq!(customers[0].name, "Charlie");
552 assert_eq!(customers[0].value, 3.12);
553 assert_eq!(customers[0].image, vec![0, 1, 2, 3]);
554 assert_eq!(customers[1].id, 2);
555 assert_eq!(customers[1].name, "Sarah");
556 assert_eq!(customers[1].value, 0.99);
557 assert_eq!(customers[1].image, vec![9, 8, 7, 6]);
558
559 Ok(())
560 }
561
562 #[tokio::test]
563 async fn query_as_column_reorder() -> TursoMapperResult<()> {
564 let db = Builder::new_local(":memory:").build().await?;
565 let conn = db.connect()?;
566 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT NOT NULL, value REAL NOT NULL, image BLOB NOT NULL);", ()).await?;
567 conn.execute("INSERT INTO t (name, value, image) VALUES ('Charlie', 3.12, x'01020300');", ()).await?;
568
569 let customers = conn.query_as::<CustomerManualReordered>("SELECT image, value, name, id FROM t;", ()).await?;
571
572 assert_eq!(customers[0].id, 1);
573 assert_eq!(customers[0].name, "Charlie");
574 assert_eq!(customers[0].value, 3.12);
575 assert_eq!(customers[0].image, vec![1, 2, 3, 0]);
576
577 Ok(())
578 }
579
580 #[derive(TryFromRowByName)]
583 struct CustomerByName {
584 id: i64,
585 name: String,
586 value: f64,
587 image: Vec<u8>,
588 }
589
590 #[derive(TryFromRowByName)]
591 struct CustomerByNameWithOptions {
592 id: i64,
593 name: String,
594 optional_value: Option<f64>,
595 optional_note: Option<String>,
596 optional_data: Option<Vec<u8>>,
597 optional_count: Option<i64>,
598 }
599
600 #[tokio::test]
601 async fn derived_try_from_row_by_name_impl() -> TursoMapperResult<()> {
602 let db = Builder::new_local(":memory:").build().await?;
603 let conn = db.connect()?;
604 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT NOT NULL, value REAL NOT NULL, image BLOB NOT NULL);", ()).await?;
605 conn.execute("INSERT INTO t (name, value, image) VALUES ('Charlie', 3.12, x'01020300');", ()).await?;
606
607 let mut rows = conn.query("SELECT id, name, value, image FROM t;", ()).await?;
608 let column_indices = ColumnIndices::new(rows.columns());
609 let indices = CustomerByName::resolve_indices(&column_indices)?;
610 let row = rows.next().await?.unwrap();
611 let customer = CustomerByName::try_from_row_by_name(row, &indices)?;
612
613 assert_eq!(customer.id, 1);
614 assert_eq!(customer.name, "Charlie");
615 assert_eq!(customer.value, 3.12);
616 assert_eq!(customer.image, vec![1, 2, 3, 0]);
617
618 Ok(())
619 }
620
621 #[tokio::test]
622 async fn end_to_end_test_with_query_as_by_name() -> TursoMapperResult<()> {
623 let db = Builder::new_local(":memory:").build().await?;
624 let conn = db.connect()?;
625 conn.execute("CREATE TABLE customer (id INTEGER PRIMARY KEY, name TEXT NOT NULL, value REAL NOT NULL, image BLOB NOT NULL);", ()).await?;
626 conn.execute("INSERT INTO customer (name, value, image) VALUES ('Charlie', 3.12, x'00010203');", ()).await?;
627 conn.execute("INSERT INTO customer (name, value, image) VALUES ('Sarah', 0.99, x'09080706');", ()).await?;
628
629 let customers = conn.query_as_by_name::<CustomerByName>("SELECT id, name, value, image FROM customer;", ()).await?;
630
631 assert_eq!(customers.len(), 2);
632 assert_eq!(customers[0].id, 1);
633 assert_eq!(customers[0].name, "Charlie");
634 assert_eq!(customers[0].value, 3.12);
635 assert_eq!(customers[0].image, vec![0, 1, 2, 3]);
636 assert_eq!(customers[1].id, 2);
637 assert_eq!(customers[1].name, "Sarah");
638 assert_eq!(customers[1].value, 0.99);
639 assert_eq!(customers[1].image, vec![9, 8, 7, 6]);
640
641 Ok(())
642 }
643
644 #[tokio::test]
645 async fn query_as_with_column_reorder() -> TursoMapperResult<()> {
646 let db = Builder::new_local(":memory:").build().await?;
647 let conn = db.connect()?;
648 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT NOT NULL, value REAL NOT NULL, image BLOB NOT NULL);", ()).await?;
649 conn.execute("INSERT INTO t (name, value, image) VALUES ('Charlie', 3.12, x'01020300');", ()).await?;
650
651 let customers = conn.query_as_by_name::<CustomerByName>("SELECT image, value, name, id FROM t;", ()).await?;
653
654 assert_eq!(customers[0].id, 1);
655 assert_eq!(customers[0].name, "Charlie");
656 assert_eq!(customers[0].value, 3.12);
657 assert_eq!(customers[0].image, vec![1, 2, 3, 0]);
658
659 Ok(())
660 }
661
662 #[tokio::test]
663 async fn try_from_row_by_name_column_reorder() -> TursoMapperResult<()> {
664 let db = Builder::new_local(":memory:").build().await?;
665 let conn = db.connect()?;
666 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT NOT NULL, value REAL NOT NULL, image BLOB NOT NULL);", ()).await?;
667 conn.execute("INSERT INTO t (name, value, image) VALUES ('Charlie', 3.12, x'01020300');", ()).await?;
668
669 let mut rows = conn.query("SELECT image, value, name, id FROM t;", ()).await?;
671 let column_indices = ColumnIndices::new(rows.columns());
672 let indices = CustomerByName::resolve_indices(&column_indices)?;
673 let row = rows.next().await?.unwrap();
674 let customer = CustomerByName::try_from_row_by_name(row, &indices)?;
675
676 assert_eq!(customer.id, 1);
677 assert_eq!(customer.name, "Charlie");
678 assert_eq!(customer.value, 3.12);
679 assert_eq!(customer.image, vec![1, 2, 3, 0]);
680
681 Ok(())
682 }
683
684 #[tokio::test]
685 async fn query_as_with_options() -> TursoMapperResult<()> {
686 let db = Builder::new_local(":memory:").build().await?;
687 let conn = db.connect()?;
688 conn.execute(
689 "CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT NOT NULL, optional_value REAL, optional_note TEXT, optional_data BLOB, optional_count INTEGER);",
690 (),
691 ).await?;
692 conn.execute("INSERT INTO t (name, optional_value, optional_data) VALUES ('Charlie', 3.12, x'010203');", ()).await?;
693 conn.execute("INSERT INTO t (name, optional_value, optional_note, optional_data, optional_count) VALUES ('Sarah', 0.99, 'Some note', x'09080706', 42);", ()).await?;
694
695 let customers = conn.query_as_by_name::<CustomerByNameWithOptions>("SELECT id, name, optional_value, optional_note, optional_data, optional_count FROM t;", ()).await?;
696
697 assert_eq!(customers[0].id, 1);
698 assert_eq!(customers[0].name, "Charlie");
699 assert_eq!(customers[0].optional_value, Some(3.12));
700 assert_eq!(customers[0].optional_note, None);
701 assert_eq!(customers[0].optional_data, Some(vec![1, 2, 3]));
702 assert_eq!(customers[0].optional_count, None);
703
704 assert_eq!(customers[1].id, 2);
705 assert_eq!(customers[1].name, "Sarah");
706 assert_eq!(customers[1].optional_value, Some(0.99));
707 assert_eq!(customers[1].optional_note, Some("Some note".to_string()));
708 assert_eq!(customers[1].optional_data, Some(vec![9, 8, 7, 6]));
709 assert_eq!(customers[1].optional_count, Some(42));
710
711 Ok(())
712 }
713
714 #[tokio::test]
715 async fn query_as_missing_column_error() -> TursoMapperResult<()> {
716 let db = Builder::new_local(":memory:").build().await?;
717 let conn = db.connect()?;
718 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT NOT NULL, value REAL NOT NULL, image BLOB NOT NULL);", ()).await?;
719 conn.execute("INSERT INTO t (name, value, image) VALUES ('Charlie', 3.12, x'01020300');", ()).await?;
720
721 let result = conn.query_as_by_name::<CustomerByName>("SELECT id, name, value FROM t;", ()).await;
723 assert!(result.is_err());
724
725 Ok(())
726 }
727}