1use std::error::Error;
2
3use tokio_postgres::types::{FromSql, Type};
4use tracing::debug;
5
6use crate::pool::PgPool;
7
8#[derive(Debug, Clone, Hash, Eq, PartialEq)]
10struct RawValue {
11 pgtype: Type,
12 bytes: Vec<u8>,
13 null: bool,
14}
15
16impl<'a> FromSql<'a> for RawValue {
17 fn from_sql(ty: &Type, val: &'a [u8]) -> Result<Self, Box<dyn Error + Sync + Send>> {
18 Ok(RawValue {
19 pgtype: ty.clone(),
20 bytes: val.to_vec(),
21 null: false,
22 })
23 }
24
25 fn from_sql_null(ty: &Type) -> Result<Self, Box<dyn Error + Sync + Send>> {
26 Ok(RawValue {
27 pgtype: ty.clone(),
28 bytes: Vec::new(),
29 null: true,
30 })
31 }
32
33 fn accepts(_ty: &Type) -> bool {
34 true
36 }
37}
38
39impl tokio_postgres::types::ToSql for RawValue {
40 fn to_sql(
41 &self,
42 _ty: &Type,
43 out: &mut tokio_postgres::types::private::BytesMut,
44 ) -> Result<tokio_postgres::types::IsNull, Box<dyn std::error::Error + Sync + Send>> {
45 out.extend_from_slice(&self.bytes);
46 Ok(tokio_postgres::types::IsNull::No)
47 }
48
49 fn accepts(_ty: &Type) -> bool {
50 true
51 }
52
53 fn to_sql_checked(
54 &self,
55 _ty: &Type,
56 out: &mut tokio_postgres::types::private::BytesMut,
57 ) -> Result<tokio_postgres::types::IsNull, Box<dyn std::error::Error + Sync + Send>> {
58 self.to_sql(_ty, out)
59 }
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
64pub struct CellRef {
65 pub row_idx: usize,
66 pub col_idx: usize,
67}
68
69#[derive(Debug, Clone)]
71pub struct TextCaster {
72 pool: PgPool,
73}
74
75impl TextCaster {
76 pub fn new(pool: PgPool) -> Self {
78 Self { pool }
79 }
80
81 pub async fn cast_batch(
84 &self,
85 rows: &[tokio_postgres::Row],
86 cells: &[CellRef],
87 ) -> Vec<Result<String, Box<dyn std::error::Error + Send + Sync>>> {
88 const BATCH_SIZE: usize = 100;
89
90 let mut results = Vec::with_capacity(cells.len());
91 let num_chunks = cells.len().div_ceil(BATCH_SIZE);
92
93 debug!(
94 "Batch casting {} cells in {} chunk(s) of up to {} cells each",
95 cells.len(),
96 num_chunks,
97 BATCH_SIZE
98 );
99
100 for (chunk_idx, chunk) in cells.chunks(BATCH_SIZE).enumerate() {
101 debug!(
102 "Processing chunk {}/{} with {} cells",
103 chunk_idx + 1,
104 num_chunks,
105 chunk.len()
106 );
107 let chunk_results = self.cast_chunk(rows, chunk).await;
108 results.extend(chunk_results);
109 }
110
111 results
112 }
113
114 async fn cast_chunk(
115 &self,
116 rows: &[tokio_postgres::Row],
117 cells: &[CellRef],
118 ) -> Vec<Result<String, Box<dyn std::error::Error + Send + Sync>>> {
119 if cells.is_empty() {
120 return Vec::new();
121 }
122
123 let mut raw_values = Vec::with_capacity(cells.len());
125 let mut needs_cast = Vec::new(); for (idx, cell) in cells.iter().enumerate() {
128 let result: Result<RawValue, _> = rows[cell.row_idx].try_get(cell.col_idx);
129 match result {
130 Ok(raw) => {
131 if raw.null {
132 raw_values.push(Ok(raw));
133 } else {
134 needs_cast.push(idx);
135 raw_values.push(Ok(raw));
136 }
137 }
138 Err(e) => raw_values.push(Err(e)),
139 }
140 }
141
142 if needs_cast.is_empty() {
144 return raw_values
145 .into_iter()
146 .map(|r| match r {
147 Ok(raw) => {
148 if raw.null {
149 Ok("NULL".to_string())
150 } else {
151 Ok("(error)".to_string())
152 }
153 }
154 Err(e) => Err(Box::new(e) as Box<dyn Error + Send + Sync>),
155 })
156 .collect();
157 }
158
159 let client = match self.pool.get().await {
161 Ok(c) => c,
162 Err(_e) => {
163 return (0..cells.len())
164 .map(|_| {
165 Err(
166 Box::new(std::io::Error::other("Failed to get database connection"))
167 as Box<dyn Error + Send + Sync>,
168 )
169 })
170 .collect();
171 }
172 };
173
174 let oids: Vec<u32> = needs_cast
176 .iter()
177 .filter_map(|&idx| {
178 if let Ok(ref raw) = raw_values[idx] {
179 Some(raw.pgtype.oid())
180 } else {
181 None
182 }
183 })
184 .collect();
185
186 let mut type_names = Vec::with_capacity(oids.len());
187 for oid in oids {
188 match client
189 .query_one("SELECT typname FROM pg_type WHERE oid = $1", &[&oid])
190 .await
191 {
192 Ok(row) => type_names.push(row.get::<_, String>(0)),
193 Err(e) => {
194 return (0..cells.len())
196 .map(|_| {
197 Err(Box::new(std::io::Error::other(format!(
198 "Failed to get type name: {}",
199 e
200 ))) as Box<dyn Error + Send + Sync>)
201 })
202 .collect();
203 }
204 }
205 }
206
207 let mut query = String::from("SELECT ");
209 let mut params: Vec<&(dyn tokio_postgres::types::ToSql + Sync)> = Vec::new();
210 let mut cast_positions = Vec::new(); for (param_idx, &raw_idx) in needs_cast.iter().enumerate() {
213 if param_idx > 0 {
214 query.push_str(", ");
215 }
216
217 if let Ok(ref raw) = raw_values[raw_idx] {
218 let typename = &type_names[param_idx];
219 query.push_str(&format!("${}::{}::text", param_idx + 1, typename));
220 params.push(raw);
221 cast_positions.push(raw_idx);
222 }
223 }
224
225 debug!(
226 "Executing batch cast query with {} parameters: {}",
227 params.len(),
228 query
229 );
230
231 let cast_results = match client.query_one(&query, ¶ms).await {
233 Ok(row) => {
234 let mut results = Vec::new();
235 for col_idx in 0..row.len() {
236 match row.try_get::<_, String>(col_idx) {
237 Ok(text) => results.push(Ok(text)),
238 Err(e) => results.push(Err(Box::new(e) as Box<dyn Error + Send + Sync>)),
239 }
240 }
241 results
242 }
243 Err(e) => {
244 let error_msg = format!("Batch cast query failed: {}", e);
246 (0..needs_cast.len())
247 .map(|_| {
248 Err(Box::new(std::io::Error::other(error_msg.clone()))
249 as Box<dyn Error + Send + Sync>)
250 })
251 .collect()
252 }
253 };
254
255 let mut results = Vec::with_capacity(cells.len());
257 let mut cast_iter = cast_results.into_iter();
258
259 for (idx, raw_result) in raw_values.into_iter().enumerate() {
260 if needs_cast.contains(&idx) {
261 results.push(cast_iter.next().unwrap_or_else(|| {
263 Err(Box::new(std::io::Error::other("Missing cast result"))
264 as Box<dyn Error + Send + Sync>)
265 }));
266 } else {
267 match raw_result {
269 Ok(raw) => {
270 if raw.null {
271 results.push(Ok("NULL".to_string()));
272 } else {
273 results.push(Ok("(unexpected)".to_string()));
274 }
275 }
276 Err(e) => results.push(Err(Box::new(e) as Box<dyn Error + Send + Sync>)),
277 }
278 }
279 }
280
281 results
282 }
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288
289 #[tokio::test]
290 async fn basic_int() {
291 let connection_string = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
292 let pool = crate::pool::create_pool(&connection_string, "test")
293 .await
294 .expect("Failed to create pool");
295
296 let caster = TextCaster::new(pool.clone());
297
298 let client = pool.get().await.unwrap();
299 let rows = client
300 .query("SELECT 123::int4 as record_col", &[])
301 .await
302 .unwrap();
303
304 let cell = CellRef {
305 row_idx: 0,
306 col_idx: 0,
307 };
308 let results = caster.cast_batch(&rows, &[cell]).await;
309 assert_eq!(results.len(), 1);
310 let result = &results[0];
311 if let Err(e) = result {
312 eprintln!("Error casting to text: {:?}", e);
313 }
314 assert!(
315 result.is_ok(),
316 "Failed to cast to text: {:?}",
317 result.as_ref().err()
318 );
319 let text = result.as_ref().unwrap();
320 eprintln!("Got text: {}", text);
321 assert_eq!(text, "123", "Text doesn't match expected value: {}", text);
322 }
323
324 #[tokio::test]
325 async fn batch_multiple_values() {
326 let connection_string = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
327 let pool = crate::pool::create_pool(&connection_string, "test")
328 .await
329 .expect("Failed to create pool");
330
331 let caster = TextCaster::new(pool.clone());
332
333 let client = pool.get().await.unwrap();
334 let rows = client
335 .query(
336 "SELECT '$100.50'::money, '$200.75'::money UNION ALL SELECT '$300.25'::money, '$400.00'::money",
337 &[],
338 )
339 .await
340 .unwrap();
341
342 let cells = vec![
343 CellRef {
344 row_idx: 0,
345 col_idx: 0,
346 },
347 CellRef {
348 row_idx: 0,
349 col_idx: 1,
350 },
351 CellRef {
352 row_idx: 1,
353 col_idx: 0,
354 },
355 CellRef {
356 row_idx: 1,
357 col_idx: 1,
358 },
359 ];
360
361 let results = caster.cast_batch(&rows, &cells).await;
362
363 assert_eq!(results.len(), 4);
364 for result in &results {
365 assert!(result.is_ok());
366 }
367
368 let values: Vec<String> = results.into_iter().map(|r| r.unwrap()).collect();
369 assert!(values[0].contains("100"));
370 assert!(values[1].contains("200"));
371 assert!(values[2].contains("300"));
372 assert!(values[3].contains("400"));
373 }
374
375 #[tokio::test]
376 async fn batch_large_number() {
377 let connection_string = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
378 let pool = crate::pool::create_pool(&connection_string, "test")
379 .await
380 .expect("Failed to create pool");
381
382 let caster = TextCaster::new(pool.clone());
383
384 let client = pool.get().await.unwrap();
385 let rows = client
387 .query(
388 "SELECT generate_series(1, 150)::text::money as money_col",
389 &[],
390 )
391 .await
392 .unwrap();
393
394 let cells: Vec<CellRef> = (0..150)
395 .map(|i| CellRef {
396 row_idx: i,
397 col_idx: 0,
398 })
399 .collect();
400
401 let results = caster.cast_batch(&rows, &cells).await;
402
403 assert_eq!(results.len(), 150);
404 for (i, result) in results.iter().enumerate() {
405 assert!(
406 result.is_ok(),
407 "Failed to cast row {}: {:?}",
408 i,
409 result.as_ref().err()
410 );
411 }
412 }
413
414 #[tokio::test]
415 async fn batch_query_verification() {
416 let connection_string = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
417 let pool = crate::pool::create_pool(&connection_string, "test")
418 .await
419 .expect("Failed to create pool");
420
421 let caster = TextCaster::new(pool.clone());
422
423 let client = pool.get().await.unwrap();
424 let rows = client
426 .query(
427 "SELECT
428 '$100.50'::money as col1,
429 '$200.75'::money as col2,
430 '$300.25'::money as col3
431 UNION ALL SELECT
432 '$400.00'::money,
433 '$500.00'::money,
434 '$600.00'::money",
435 &[],
436 )
437 .await
438 .unwrap();
439
440 let cells: Vec<CellRef> = vec![
442 CellRef {
443 row_idx: 0,
444 col_idx: 0,
445 },
446 CellRef {
447 row_idx: 0,
448 col_idx: 1,
449 },
450 CellRef {
451 row_idx: 0,
452 col_idx: 2,
453 },
454 CellRef {
455 row_idx: 1,
456 col_idx: 0,
457 },
458 CellRef {
459 row_idx: 1,
460 col_idx: 1,
461 },
462 CellRef {
463 row_idx: 1,
464 col_idx: 2,
465 },
466 ];
467
468 let results = caster.cast_batch(&rows, &cells).await;
469
470 assert_eq!(results.len(), 6);
472 for result in &results {
473 assert!(result.is_ok(), "Cast failed: {:?}", result);
474 }
475
476 let values: Vec<String> = results.into_iter().map(|r| r.unwrap()).collect();
478 assert!(values[0].contains("100"));
479 assert!(values[1].contains("200"));
480 assert!(values[2].contains("300"));
481 assert!(values[3].contains("400"));
482 assert!(values[4].contains("500"));
483 assert!(values[5].contains("600"));
484
485 }
488
489 #[tokio::test]
490 async fn batch_mixed_types() {
491 let connection_string = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
492 let pool = crate::pool::create_pool(&connection_string, "test")
493 .await
494 .expect("Failed to create pool");
495
496 let caster = TextCaster::new(pool.clone());
497
498 let client = pool.get().await.unwrap();
499 let rows = client
501 .query(
502 "SELECT
503 '$99.99'::money as money_col,
504 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid as uuid_col,
505 point(3.14, 2.71) as point_col,
506 '192.168.1.100'::inet as inet_col",
507 &[],
508 )
509 .await
510 .unwrap();
511
512 let cells: Vec<CellRef> = vec![
514 CellRef {
515 row_idx: 0,
516 col_idx: 0,
517 }, CellRef {
519 row_idx: 0,
520 col_idx: 1,
521 }, CellRef {
523 row_idx: 0,
524 col_idx: 2,
525 }, CellRef {
527 row_idx: 0,
528 col_idx: 3,
529 }, ];
531
532 let results = caster.cast_batch(&rows, &cells).await;
533
534 assert_eq!(results.len(), 4);
536 for (idx, result) in results.iter().enumerate() {
537 assert!(result.is_ok(), "Cast {} failed: {:?}", idx, result);
538 }
539
540 let values: Vec<String> = results.into_iter().map(|r| r.unwrap()).collect();
542 assert!(values[0].contains("99")); assert_eq!(values[1], "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"); assert!(values[2].contains("3.14") && values[2].contains("2.71")); assert!(values[3].contains("192.168.1.100")); }
550
551 #[tokio::test]
552 #[ignore = "FIXME: figure out a workaround for (anonymous?) composite types"]
553 async fn composite() {
554 let connection_string = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
555 let pool = crate::pool::create_pool(&connection_string, "test")
556 .await
557 .expect("Failed to create pool");
558
559 let caster = TextCaster::new(pool.clone());
560
561 let client = pool.get().await.unwrap();
562 let rows = client
563 .query("SELECT row(1, 'test', true) as record_col", &[])
564 .await
565 .unwrap();
566
567 let cell = CellRef {
568 row_idx: 0,
569 col_idx: 0,
570 };
571 let results = caster.cast_batch(&rows, &[cell]).await;
572 assert_eq!(results.len(), 1);
573 let result = &results[0];
574 if let Err(e) = result {
575 eprintln!("Error casting to text: {:?}", e);
576 }
577 assert!(
578 result.is_ok(),
579 "Failed to cast to text: {:?}",
580 result.as_ref().err()
581 );
582 let text = result.as_ref().unwrap();
583 eprintln!("Got text: {}", text);
584 assert!(
585 text.contains("1") && text.contains("test"),
586 "Text doesn't contain expected values: {}",
587 text
588 );
589 }
590
591 #[tokio::test]
592 async fn money_type() {
593 let connection_string = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
594 let pool = crate::pool::create_pool(&connection_string, "test")
595 .await
596 .expect("Failed to create pool");
597
598 let caster = TextCaster::new(pool.clone());
599
600 let client = pool.get().await.unwrap();
601 let rows = client
602 .query("SELECT '$1,234.56'::money as money_col", &[])
603 .await
604 .unwrap();
605
606 let cell = CellRef {
607 row_idx: 0,
608 col_idx: 0,
609 };
610 let results = caster.cast_batch(&rows, &[cell]).await;
611 assert_eq!(results.len(), 1);
612 assert!(
613 results[0].is_ok(),
614 "Failed to cast money: {:?}",
615 results[0].as_ref().err()
616 );
617 let text = results[0].as_ref().unwrap();
618 eprintln!("Money as text: {}", text);
619 assert!(text.contains("1") && text.contains("234"));
620 }
621
622 #[tokio::test]
623 async fn uuid_type() {
624 let connection_string = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
625 let pool = crate::pool::create_pool(&connection_string, "test")
626 .await
627 .expect("Failed to create pool");
628
629 let caster = TextCaster::new(pool.clone());
630
631 let client = pool.get().await.unwrap();
632 let rows = client
633 .query(
634 "SELECT 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid as uuid_col",
635 &[],
636 )
637 .await
638 .unwrap();
639
640 let cell = CellRef {
641 row_idx: 0,
642 col_idx: 0,
643 };
644 let results = caster.cast_batch(&rows, &[cell]).await;
645 assert_eq!(results.len(), 1);
646 assert!(
647 results[0].is_ok(),
648 "Failed to cast uuid: {:?}",
649 results[0].as_ref().err()
650 );
651 let text = results[0].as_ref().unwrap();
652 eprintln!("UUID as text: {}", text);
653 assert_eq!(text, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11");
654 }
655
656 #[tokio::test]
657 async fn json_type() {
658 let connection_string = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
659 let pool = crate::pool::create_pool(&connection_string, "test")
660 .await
661 .expect("Failed to create pool");
662
663 let caster = TextCaster::new(pool.clone());
664
665 let client = pool.get().await.unwrap();
666 let rows = client
667 .query("SELECT '{\"key\": \"value\"}'::json as json_col", &[])
668 .await
669 .unwrap();
670
671 let cell = CellRef {
672 row_idx: 0,
673 col_idx: 0,
674 };
675 let results = caster.cast_batch(&rows, &[cell]).await;
676 assert_eq!(results.len(), 1);
677 assert!(
678 results[0].is_ok(),
679 "Failed to cast json: {:?}",
680 results[0].as_ref().err()
681 );
682 let text = results[0].as_ref().unwrap();
683 eprintln!("JSON as text: {}", text);
684 assert!(text.contains("key") && text.contains("value"));
685 }
686
687 #[tokio::test]
688 async fn jsonb_type() {
689 let connection_string = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
690 let pool = crate::pool::create_pool(&connection_string, "test")
691 .await
692 .expect("Failed to create pool");
693
694 let caster = TextCaster::new(pool.clone());
695
696 let client = pool.get().await.unwrap();
697 let rows = client
698 .query("SELECT '{\"foo\": \"bar\"}'::jsonb as jsonb_col", &[])
699 .await
700 .unwrap();
701
702 let cell = CellRef {
703 row_idx: 0,
704 col_idx: 0,
705 };
706 let results = caster.cast_batch(&rows, &[cell]).await;
707 assert_eq!(results.len(), 1);
708 assert!(
709 results[0].is_ok(),
710 "Failed to cast jsonb: {:?}",
711 results[0].as_ref().err()
712 );
713 let text = results[0].as_ref().unwrap();
714 eprintln!("JSONB as text: {}", text);
715 assert!(text.contains("foo") && text.contains("bar"));
716 }
717
718 #[tokio::test]
719 async fn array_type() {
720 let connection_string = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
721 let pool = crate::pool::create_pool(&connection_string, "test")
722 .await
723 .expect("Failed to create pool");
724
725 let caster = TextCaster::new(pool.clone());
726
727 let client = pool.get().await.unwrap();
728 let rows = client
729 .query("SELECT ARRAY[1, 2, 3, 4]::int[] as array_col", &[])
730 .await
731 .unwrap();
732
733 let cell = CellRef {
734 row_idx: 0,
735 col_idx: 0,
736 };
737 let results = caster.cast_batch(&rows, &[cell]).await;
738 assert_eq!(results.len(), 1);
739 assert!(
740 results[0].is_ok(),
741 "Failed to cast array: {:?}",
742 results[0].as_ref().err()
743 );
744 let text = results[0].as_ref().unwrap();
745 eprintln!("Array as text: {}", text);
746 assert!(text.contains("1") && text.contains("2") && text.contains("3"));
747 }
748
749 #[tokio::test]
750 async fn bytea_type() {
751 let connection_string = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
752 let pool = crate::pool::create_pool(&connection_string, "test")
753 .await
754 .expect("Failed to create pool");
755
756 let caster = TextCaster::new(pool.clone());
757
758 let client = pool.get().await.unwrap();
759 let rows = client
760 .query("SELECT '\\xDEADBEEF'::bytea as bytea_col", &[])
761 .await
762 .unwrap();
763
764 let cell = CellRef {
765 row_idx: 0,
766 col_idx: 0,
767 };
768 let results = caster.cast_batch(&rows, &[cell]).await;
769 assert_eq!(results.len(), 1);
770 assert!(
771 results[0].is_ok(),
772 "Failed to cast bytea: {:?}",
773 results[0].as_ref().err()
774 );
775 let text = results[0].as_ref().unwrap();
776 eprintln!("Bytea as text: {}", text);
777 assert!(text.contains("\\x") || text.contains("de") || text.contains("ad"));
778 }
779
780 #[tokio::test]
781 async fn inet_type() {
782 let connection_string = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
783 let pool = crate::pool::create_pool(&connection_string, "test")
784 .await
785 .expect("Failed to create pool");
786
787 let caster = TextCaster::new(pool.clone());
788
789 let client = pool.get().await.unwrap();
790 let rows = client
791 .query("SELECT '192.168.1.1/24'::inet as inet_col", &[])
792 .await
793 .unwrap();
794
795 let cell = CellRef {
796 row_idx: 0,
797 col_idx: 0,
798 };
799 let results = caster.cast_batch(&rows, &[cell]).await;
800 assert_eq!(results.len(), 1);
801 assert!(
802 results[0].is_ok(),
803 "Failed to cast inet: {:?}",
804 results[0].as_ref().err()
805 );
806 let text = results[0].as_ref().unwrap();
807 eprintln!("Inet as text: {}", text);
808 assert!(text.contains("192.168.1.1"));
809 }
810
811 #[tokio::test]
812 async fn interval_type() {
813 let connection_string = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
814 let pool = crate::pool::create_pool(&connection_string, "test")
815 .await
816 .expect("Failed to create pool");
817
818 let caster = TextCaster::new(pool.clone());
819
820 let client = pool.get().await.unwrap();
821 let rows = client
822 .query("SELECT '2 days 3 hours'::interval as interval_col", &[])
823 .await
824 .unwrap();
825
826 let cell = CellRef {
827 row_idx: 0,
828 col_idx: 0,
829 };
830 let results = caster.cast_batch(&rows, &[cell]).await;
831 assert_eq!(results.len(), 1);
832 assert!(
833 results[0].is_ok(),
834 "Failed to cast interval: {:?}",
835 results[0].as_ref().err()
836 );
837 let text = results[0].as_ref().unwrap();
838 eprintln!("Interval as text: {}", text);
839 assert!(text.contains("day") || text.contains("hour") || text.contains("2"));
840 }
841
842 #[tokio::test]
843 async fn null_value() {
844 let connection_string = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
845 let pool = crate::pool::create_pool(&connection_string, "test")
846 .await
847 .expect("Failed to create pool");
848
849 let caster = TextCaster::new(pool.clone());
850
851 let client = pool.get().await.unwrap();
852 let rows = client
853 .query("SELECT NULL::int as null_col", &[])
854 .await
855 .unwrap();
856
857 let cell = CellRef {
858 row_idx: 0,
859 col_idx: 0,
860 };
861 let results = caster.cast_batch(&rows, &[cell]).await;
862 assert_eq!(results.len(), 1);
863 assert!(
864 results[0].is_ok(),
865 "Failed to cast null: {:?}",
866 results[0].as_ref().err()
867 );
868 let text = results[0].as_ref().unwrap();
869 eprintln!("NULL as text: {}", text);
870 assert_eq!(text, "NULL");
871 }
872
873 #[tokio::test]
874 async fn point_type() {
875 let connection_string = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
876 let pool = crate::pool::create_pool(&connection_string, "test")
877 .await
878 .expect("Failed to create pool");
879
880 let caster = TextCaster::new(pool.clone());
881
882 let client = pool.get().await.unwrap();
883 let rows = client
884 .query("SELECT point(1.5, 2.5) as point_col", &[])
885 .await
886 .unwrap();
887
888 let cell = CellRef {
889 row_idx: 0,
890 col_idx: 0,
891 };
892 let results = caster.cast_batch(&rows, &[cell]).await;
893 assert_eq!(results.len(), 1);
894 assert!(
895 results[0].is_ok(),
896 "Failed to cast point: {:?}",
897 results[0].as_ref().err()
898 );
899 let text = results[0].as_ref().unwrap();
900 eprintln!("Point as text: {}", text);
901 assert!(text.contains("1.5") && text.contains("2.5"));
902 }
903
904 #[tokio::test]
905 async fn box_type() {
906 let connection_string = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
907 let pool = crate::pool::create_pool(&connection_string, "test")
908 .await
909 .expect("Failed to create pool");
910
911 let caster = TextCaster::new(pool.clone());
912
913 let client = pool.get().await.unwrap();
914 let rows = client
915 .query("SELECT box(point(0,0), point(1,1)) as box_col", &[])
916 .await
917 .unwrap();
918
919 let cell = CellRef {
920 row_idx: 0,
921 col_idx: 0,
922 };
923 let results = caster.cast_batch(&rows, &[cell]).await;
924 assert_eq!(results.len(), 1);
925 assert!(
926 results[0].is_ok(),
927 "Failed to cast box: {:?}",
928 results[0].as_ref().err()
929 );
930 let text = results[0].as_ref().unwrap();
931 eprintln!("Box as text: {}", text);
932 assert!(text.contains("0") && text.contains("1"));
933 }
934}