1use super::{AppenderParams, Connection, Result, ValueRef, ffi};
2use std::{ffi::c_void, fmt, os::raw::c_char};
3
4use crate::{
5 Error,
6 error::{error_from_appender_code, result_from_duckdb_appender},
7 types::{ToSql, ToSqlOutput, to_duckdb_hugeint, to_duckdb_uhugeint, value_ref_from_value},
8};
9
10pub struct Appender<'conn> {
76 conn: &'conn Connection,
77 app: ffi::duckdb_appender,
78}
79
80unsafe impl Sync for Appender<'_> {}
88
89#[cfg(feature = "appender-arrow")]
90mod arrow;
91
92impl Appender<'_> {
93 #[inline]
110 pub fn append_rows<P, I>(&mut self, rows: I) -> Result<()>
111 where
112 I: IntoIterator<Item = P>,
113 P: AppenderParams,
114 {
115 for row in rows {
116 self.append_row(row)?;
117 }
118 Ok(())
119 }
120
121 #[inline]
138 pub fn append_row<P: AppenderParams>(&mut self, params: P) -> Result<()> {
139 params.__bind_in(self)
143 }
144
145 #[inline]
146 pub(crate) fn append_parameter_row<P>(&mut self, params: P) -> Result<()>
147 where
148 P: IntoIterator,
149 P::Item: ToSql,
150 {
151 let params = params.into_iter().collect::<Vec<_>>();
152 let values = params
153 .iter()
154 .map(ToSql::to_sql)
155 .collect::<Result<Vec<ToSqlOutput<'_>>>>()?;
156
157 self.validate_parameter_values(&values)?;
158
159 if let Err(err) = self.bind_parameter_values(&values) {
160 if !self.app.is_null() {
164 let rc = unsafe { ffi::duckdb_appender_end_row(self.app) };
165 if rc != ffi::DuckDBSuccess {
166 unsafe {
167 ffi::duckdb_appender_destroy(&mut self.app);
168 }
169 }
170 }
171 return Err(err);
172 }
173 let rc = unsafe { ffi::duckdb_appender_end_row(self.app) };
174 result_from_duckdb_appender(rc, &mut self.app)
175 }
176
177 fn validate_parameter_values(&self, values: &[ToSqlOutput<'_>]) -> Result<()> {
178 for value in values {
179 let value = to_value_ref(value)?;
180 validate_appender_value_ref(value)?;
181 }
182 Ok(())
183 }
184
185 fn bind_parameter_values(&mut self, values: &[ToSqlOutput<'_>]) -> Result<()> {
186 for value in values {
187 self.bind_parameter(value)?;
188 }
189 Ok(())
190 }
191
192 fn bind_parameter(&mut self, value: &ToSqlOutput<'_>) -> Result<()> {
193 let ptr = self.app;
194 let value = to_value_ref(value)?;
195 let rc = match value {
197 ValueRef::Null => unsafe { ffi::duckdb_append_null(ptr) },
198 ValueRef::Boolean(i) => unsafe { ffi::duckdb_append_bool(ptr, i) },
199 ValueRef::TinyInt(i) => unsafe { ffi::duckdb_append_int8(ptr, i) },
200 ValueRef::SmallInt(i) => unsafe { ffi::duckdb_append_int16(ptr, i) },
201 ValueRef::Int(i) => unsafe { ffi::duckdb_append_int32(ptr, i) },
202 ValueRef::BigInt(i) => unsafe { ffi::duckdb_append_int64(ptr, i) },
203 ValueRef::UTinyInt(i) => unsafe { ffi::duckdb_append_uint8(ptr, i) },
204 ValueRef::USmallInt(i) => unsafe { ffi::duckdb_append_uint16(ptr, i) },
205 ValueRef::UInt(i) => unsafe { ffi::duckdb_append_uint32(ptr, i) },
206 ValueRef::UBigInt(i) => unsafe { ffi::duckdb_append_uint64(ptr, i) },
207 ValueRef::HugeInt(i) => unsafe { ffi::duckdb_append_hugeint(ptr, to_duckdb_hugeint(i)) },
208 ValueRef::UHugeInt(i) => unsafe { ffi::duckdb_append_uhugeint(ptr, to_duckdb_uhugeint(i)) },
209
210 ValueRef::Float(r) => unsafe { ffi::duckdb_append_float(ptr, r) },
211 ValueRef::Double(r) => unsafe { ffi::duckdb_append_double(ptr, r) },
212 ValueRef::Text(s) => unsafe {
213 ffi::duckdb_append_varchar_length(ptr, s.as_ptr() as *const c_char, s.len() as u64)
214 },
215 ValueRef::Timestamp(u, i) => unsafe {
216 ffi::duckdb_append_timestamp(ptr, ffi::duckdb_timestamp { micros: u.to_micros(i) })
217 },
218 ValueRef::Blob(b) | ValueRef::Geometry(b) => unsafe {
219 ffi::duckdb_append_blob(ptr, b.as_ptr() as *const c_void, b.len() as u64)
220 },
221 ValueRef::Date32(d) => unsafe { ffi::duckdb_append_date(ptr, ffi::duckdb_date { days: d }) },
222 ValueRef::Time64(u, v) => unsafe {
223 ffi::duckdb_append_time(ptr, ffi::duckdb_time { micros: u.to_micros(v) })
224 },
225 ValueRef::Interval { months, days, nanos } => unsafe {
226 ffi::duckdb_append_interval(
227 ptr,
228 ffi::duckdb_interval {
229 months,
230 days,
231 micros: nanos / 1000,
232 },
233 )
234 },
235 ValueRef::Decimal(d) => unsafe {
236 let decimal = crate::types::to_duckdb_decimal(d);
237 append_decimal(ptr, decimal)?
238 },
239 _ => {
240 return Err(Error::ToSqlConversionFailure(
241 appending_unsupported_value(value.data_type()).into(),
242 ));
243 }
244 };
245 if rc != 0 {
246 return Err(error_from_appender_code(rc, self.app));
247 }
248 Ok(())
249 }
250
251 #[inline]
252 pub(super) fn new(conn: &Connection, app: ffi::duckdb_appender) -> Appender<'_> {
253 Appender { conn, app }
254 }
255
256 #[inline]
271 pub fn flush(&mut self) -> Result<()> {
272 unsafe {
273 let res = ffi::duckdb_appender_flush(self.app);
274 result_from_duckdb_appender(res, &mut self.app)
275 }
276 }
277
278 #[inline]
285 pub fn add_column(&mut self, name: &str) -> Result<()> {
286 let c_name = std::ffi::CString::new(name)?;
287 let rc = unsafe { ffi::duckdb_appender_add_column(self.app, c_name.as_ptr() as *const c_char) };
288 result_from_duckdb_appender(rc, &mut self.app)
289 }
290
291 #[inline]
298 pub fn clear_columns(&mut self) -> Result<()> {
299 let rc = unsafe { ffi::duckdb_appender_clear_columns(self.app) };
300 result_from_duckdb_appender(rc, &mut self.app)
301 }
302}
303
304impl Drop for Appender<'_> {
305 fn drop(&mut self) {
306 if !self.app.is_null() {
307 unsafe {
308 ffi::duckdb_appender_destroy(&mut self.app);
309 }
310 }
311 }
312}
313
314impl fmt::Debug for Appender<'_> {
315 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
316 f.debug_struct("Appender").field("conn", self.conn).finish()
317 }
318}
319
320fn appending_unsupported_value(value_type: impl fmt::Display) -> String {
321 format!("appending {value_type} values is not yet supported")
322}
323
324unsafe fn append_decimal(ptr: ffi::duckdb_appender, decimal: ffi::duckdb_decimal) -> Result<ffi::duckdb_state> {
325 let mut value = unsafe { ffi::duckdb_create_decimal(decimal) };
326 if value.is_null() {
327 return Err(Error::AppendError);
328 }
329 let res = unsafe { ffi::duckdb_append_value(ptr, value) };
330 unsafe {
331 ffi::duckdb_destroy_value(&mut value);
332 }
333 Ok(res)
334}
335
336fn to_value_ref<'value, 'output>(value: &'value ToSqlOutput<'output>) -> Result<ValueRef<'value>>
337where
338 'output: 'value,
339{
340 match *value {
341 ToSqlOutput::Borrowed(v) => Ok(v),
342 ToSqlOutput::Owned(ref v) => value_ref_from_value(v, appending_unsupported_value),
343 }
344}
345
346fn validate_appender_value_ref(value: ValueRef<'_>) -> Result<()> {
347 match value {
348 ValueRef::Null
349 | ValueRef::Boolean(_)
350 | ValueRef::TinyInt(_)
351 | ValueRef::SmallInt(_)
352 | ValueRef::Int(_)
353 | ValueRef::BigInt(_)
354 | ValueRef::HugeInt(_)
355 | ValueRef::UHugeInt(_)
356 | ValueRef::UTinyInt(_)
357 | ValueRef::USmallInt(_)
358 | ValueRef::UInt(_)
359 | ValueRef::UBigInt(_)
360 | ValueRef::Float(_)
361 | ValueRef::Double(_)
362 | ValueRef::Text(_)
363 | ValueRef::Timestamp(_, _)
364 | ValueRef::Blob(_)
365 | ValueRef::Geometry(_)
366 | ValueRef::Date32(_)
367 | ValueRef::Time64(_, _)
368 | ValueRef::Interval { .. }
369 | ValueRef::Decimal(_) => Ok(()),
370 _ => Err(Error::ToSqlConversionFailure(
371 appending_unsupported_value(value.data_type()).into(),
372 )),
373 }
374}
375
376#[cfg(test)]
377mod test {
378 use super::Appender;
379 use crate::{
380 Connection, Error, Result, params,
381 types::{Decimal, Value},
382 };
383
384 #[test]
385 fn appender_is_sync() {
386 fn assert_sync<T: Sync>() {}
387
388 assert_sync::<Appender<'_>>();
389 }
390
391 #[test]
392 fn test_append_one_row() -> Result<()> {
393 let db = Connection::open_in_memory()?;
394 db.execute_batch("CREATE TABLE foo(x INTEGER)")?;
395
396 {
397 let mut app = db.appender("foo")?;
398 app.append_row([42])?;
399 }
400
401 let val = db.query_row("SELECT x FROM foo", [], |row| <(i32,)>::try_from(row))?;
402 assert_eq!(val, (42,));
403 Ok(())
404 }
405
406 #[test]
407 fn test_append_geometry_value_as_wkb_blob() -> Result<()> {
408 let db = Connection::open_in_memory()?;
409 db.execute_batch("CREATE TABLE foo(wkb BLOB)")?;
410
411 let wkb: Vec<u8> = db.query_row("SELECT ST_AsWKB('POINT EMPTY'::GEOMETRY)", [], |row| row.get(0))?;
412 let mut app = db.appender("foo")?;
413 app.append_row([Value::Geometry(wkb.clone())])?;
414 app.flush()?;
415
416 let got: Vec<u8> = db.query_row("SELECT wkb FROM foo", [], |row| row.get(0))?;
417 assert_eq!(got, wkb);
418 Ok(())
419 }
420
421 #[test]
422 fn test_append_geometry_value_to_geometry_column_fails() -> Result<()> {
423 let db = Connection::open_in_memory()?;
424 db.execute_batch("CREATE TABLE foo(geom GEOMETRY)")?;
425
426 let wkb: Vec<u8> = db.query_row("SELECT ST_AsWKB('POINT EMPTY'::GEOMETRY)", [], |row| row.get(0))?;
427 let mut app = db.appender("foo")?;
428 let err = app.append_row([Value::Geometry(wkb)]).unwrap_err();
429 let message = err.to_string();
430 assert!(
431 message.contains("BLOB") && message.contains("GEOMETRY"),
432 "expected BLOB/GEOMETRY type mismatch, got: {message}"
433 );
434 Ok(())
435 }
436
437 #[test]
438 fn test_appender_variant_columns_delegate_to_duckdb() -> Result<()> {
439 let db = Connection::open_in_memory()?;
440 db.execute_batch("CREATE TABLE foo(id INTEGER, variant_col VARIANT)")?;
441
442 let mut app = db.appender("foo")?;
443 app.append_row(params![1, "hello"])?;
444 app.flush()?;
445
446 let mut app = db.appender_with_columns("foo", &["variant_col"])?;
447 app.append_row(params!["column_subset"])?;
448 app.flush()?;
449
450 let mut app = db.appender_with_columns("foo", &["id"])?;
451 app.add_column("variant_col")?;
452 app.append_row(params![3, "added_column"])?;
453 app.flush()?;
454
455 let mut app = db.appender_with_columns("foo", &["id"])?;
456 app.clear_columns()?;
457 app.append_row(params![4, "cleared_columns"])?;
458 app.flush()?;
459
460 let count: i64 = db.query_row("SELECT COUNT(*) FROM foo WHERE variant_col IS NOT NULL", [], |row| {
461 row.get(0)
462 })?;
463 assert_eq!(count, 4);
464 Ok(())
465 }
466
467 #[test]
468 fn test_append_unsupported_container_type_returns_error() -> Result<()> {
469 use arrow::{array::ListArray, datatypes::Int32Type};
470
471 use crate::{
472 ToSql,
473 types::{ListType, ToSqlOutput, Value, ValueRef},
474 };
475
476 struct OwnedList;
477 impl ToSql for OwnedList {
478 fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
479 Ok(ToSqlOutput::Owned(Value::List(vec![Value::Int(1), Value::Int(2)])))
480 }
481 }
482
483 struct BorrowedList(ListArray);
484 impl BorrowedList {
485 fn new() -> Self {
486 Self(ListArray::from_iter_primitive::<Int32Type, _, _>(vec![Some(vec![
487 Some(1),
488 Some(2),
489 ])]))
490 }
491 }
492 impl ToSql for BorrowedList {
493 fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
494 Ok(ToSqlOutput::Borrowed(ValueRef::List(ListType::Regular(&self.0), 0)))
495 }
496 }
497
498 fn assert_unsupported_list_error(err: Error) {
499 match err {
500 Error::ToSqlConversionFailure(e) => {
501 assert!(
502 e.to_string().contains("appending List values is not yet supported"),
503 "unexpected message: {e}"
504 );
505 }
506 other => panic!("expected ToSqlConversionFailure, got {other:?}"),
507 }
508 }
509
510 let db = Connection::open_in_memory()?;
511 db.execute_batch("CREATE TABLE foo(id INTEGER, name TEXT)")?;
512
513 let list = OwnedList;
514 let mut app = db.appender("foo")?;
515 app.append_row(params![10, "before"])?;
516 app.append_row(params![11, "also before"])?;
517 let err = app.append_row(params![1, list]).unwrap_err();
518 assert_unsupported_list_error(err);
519
520 let borrowed_list = BorrowedList::new();
521 let err = app.append_row(params![3, borrowed_list]).unwrap_err();
522 assert_unsupported_list_error(err);
523 app.append_row(params![2, "ok"])?;
524 app.flush()?;
525
526 let rows = db
527 .prepare("SELECT id, name FROM foo ORDER BY id")?
528 .query_map([], |row| Ok((row.get::<_, i32>(0)?, row.get::<_, String>(1)?)))?
529 .collect::<Result<Vec<_>>>()?;
530 assert_eq!(
531 rows,
532 vec![
533 (2, "ok".to_string()),
534 (10, "before".to_string()),
535 (11, "also before".to_string())
536 ]
537 );
538 let count: i32 = db.query_row("SELECT COUNT(*) FROM foo", [], |row| row.get(0))?;
539 assert_eq!(count, 3);
540 Ok(())
541 }
542
543 #[test]
544 fn test_append_bind_failure_preserves_bind_error() -> Result<()> {
545 let db = Connection::open_in_memory()?;
546 db.execute_batch("CREATE TABLE foo(id INTEGER, value UUID)")?;
547
548 let mut app = db.appender("foo")?;
549 let err = app.append_row(params![1, 2]).unwrap_err();
550
551 match err {
552 Error::DuckDBFailure(_, Some(msg)) => {
553 assert_eq!(
554 msg,
555 "Failed to cast value: Unimplemented type for cast (INTEGER -> UUID)"
556 );
557 }
558 other => panic!("expected DuckDBFailure from appender bind, got {other:?}"),
559 }
560 assert!(app.app.is_null());
561 Ok(())
562 }
563
564 #[test]
565 fn test_append_rows() -> Result<()> {
566 let db = Connection::open_in_memory()?;
567 db.execute_batch("CREATE TABLE foo(x INTEGER, y INTEGER)")?;
568
569 {
570 let mut app = db.appender("foo")?;
571 app.append_rows([[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]])?;
572 }
573
574 let val = db.query_row("SELECT sum(x), sum(y) FROM foo", [], |row| <(i32, i32)>::try_from(row))?;
575 assert_eq!(val, (25, 30));
576 Ok(())
577 }
578
579 #[test]
580 fn test_append_u128_as_uhugeint() -> Result<()> {
581 let db = Connection::open_in_memory()?;
582 db.execute("CREATE TABLE t(v UHUGEINT)", [])?;
583
584 let mut appender = db.appender("t")?;
585 appender.append_row([0u128])?;
586 appender.append_row([u128::MAX])?;
587 drop(appender);
588
589 let values: Vec<u128> = db
590 .prepare("SELECT v FROM t ORDER BY v")?
591 .query_map([], |row| row.get(0))?
592 .collect::<Result<Vec<_>>>()?;
593
594 assert_eq!(values, vec![0, u128::MAX]);
595
596 Ok(())
597 }
598
599 #[cfg(feature = "uuid")]
600 #[test]
601 fn test_append_uuid() -> Result<()> {
602 use uuid::Uuid;
603
604 let db = Connection::open_in_memory()?;
605 db.execute_batch("CREATE TABLE foo(x UUID)")?;
606
607 let id = Uuid::new_v4();
608 {
609 let mut app = db.appender("foo")?;
610 app.append_row([id])?;
611 }
612
613 let val = db.query_row("SELECT x FROM foo", [], |row| <(Uuid,)>::try_from(row))?;
614 assert_eq!(val, (id,));
615 Ok(())
616 }
617
618 #[test]
619 fn test_append_string_as_ts_row() -> Result<()> {
620 let db = Connection::open_in_memory()?;
621 db.execute_batch("CREATE TABLE foo(x TIMESTAMP)")?;
622
623 {
624 let mut app = db.appender("foo")?;
625 app.append_row(["2022-04-09 15:56:37.544"])?;
626 }
627
628 let val = db.query_row("SELECT x FROM foo", [], |row| <(i64,)>::try_from(row))?;
629 assert_eq!(val, (1649519797544000,));
630 Ok(())
631 }
632
633 #[test]
634 fn test_append_timestamp() -> Result<()> {
635 use std::time::Duration;
636 let db = Connection::open_in_memory()?;
637 db.execute_batch("CREATE TABLE foo(x TIMESTAMP)")?;
638
639 let d = Duration::from_secs(1);
640 {
641 let mut app = db.appender("foo")?;
642 app.append_row([d])?;
643 }
644
645 let val = db.query_row("SELECT x FROM foo where x=?", [d], |row| <(i32,)>::try_from(row))?;
646 assert_eq!(val, (d.as_micros() as i32,));
647 Ok(())
648 }
649
650 #[test]
651 #[cfg(feature = "chrono")]
652 fn test_append_datetime() -> Result<()> {
653 use chrono::{NaiveDate, NaiveDateTime};
654
655 let db = Connection::open_in_memory()?;
656 db.execute_batch("CREATE TABLE foo(x DATE, y TIMESTAMP)")?;
657
658 let date = NaiveDate::from_ymd_opt(2024, 6, 5).unwrap();
659 let timestamp = date.and_hms_opt(18, 26, 53).unwrap();
660 {
661 let mut app = db.appender("foo")?;
662 app.append_row(params![date, timestamp])?;
663 }
664 let (date2, timestamp2) = db.query_row("SELECT x, y FROM foo", [], |row| {
665 Ok((row.get::<_, NaiveDate>(0)?, row.get::<_, NaiveDateTime>(1)?))
666 })?;
667 assert_eq!(date, date2);
668 assert_eq!(timestamp, timestamp2);
669 Ok(())
670 }
671
672 #[test]
673 #[cfg(feature = "chrono")]
674 fn test_append_struct_with_params() -> Result<()> {
675 use chrono::NaiveDate;
676
677 struct Person {
678 first_name: String,
679 last_name: String,
680 dob: NaiveDate,
681 }
682
683 let db = Connection::open_in_memory()?;
684
685 db.execute_batch("CREATE TABLE foo(first_name VARCHAR, last_name VARCHAR, dob DATE);")?;
686
687 let person1 = Person {
688 first_name: String::from("John"),
689 last_name: String::from("Smith"),
690 dob: NaiveDate::from_ymd_opt(1970, 1, 1).unwrap(),
691 };
692
693 let person2 = Person {
694 first_name: String::from("Jane"),
695 last_name: String::from("Smith"),
696 dob: NaiveDate::from_ymd_opt(1975, 1, 1).unwrap(),
697 };
698
699 {
701 let persons = vec![&person1, &person2];
702 let mut app = db.appender("foo")?;
703 for p in &persons {
704 app.append_row(params![&p.first_name, &p.last_name, p.dob])?;
705 }
706 }
707
708 let count: i64 = db.query_row("SELECT count(*) FROM foo", [], |row| row.get(0))?;
709 assert_eq!(count, 2);
710
711 Ok(())
712 }
713
714 #[test]
715 fn test_appender_error() -> Result<()> {
716 let conn = Connection::open_in_memory()?;
717 conn.execute(
718 r"CREATE TABLE foo (
719 foobar TEXT,
720 foobar_int INT,
721 foobar_split TEXT[] AS (split(trim(foobar), ','))
722 );",
723 [],
724 )?;
725 let mut appender = conn.appender("foo")?;
726 match appender.append_row(params!["foo"]) {
727 Err(Error::DuckDBFailure(.., Some(msg))) => {
728 assert_eq!(msg, "Call to EndRow before all columns have been appended to!")
729 }
730 Err(err) => panic!("unexpected error: {err:?}"),
731 Ok(_) => panic!("expected an error but got Ok"),
732 }
733 Ok(())
734 }
735
736 #[test]
737 fn test_appender_foreign_key_constraint() -> Result<()> {
738 let conn = Connection::open_in_memory()?;
739 conn.execute_batch(
740 r"
741 CREATE TABLE parent (id INTEGER PRIMARY KEY);
742 CREATE TABLE child (
743 id INTEGER,
744 parent_id INTEGER,
745 FOREIGN KEY (parent_id) REFERENCES parent(id)
746 );",
747 )?;
748 conn.execute("INSERT INTO parent VALUES (1)", [])?;
749
750 let mut appender = conn.appender("child")?;
751 appender.append_row(params![1, 999])?; match appender.flush() {
755 Err(Error::DuckDBFailure(_, Some(msg))) => {
756 assert_eq!(
757 msg,
758 "Failed to append: Violates foreign key constraint because key \"id: 999\" does not exist in the referenced table"
759 );
760 }
761 Err(e) => panic!("Expected foreign key constraint error, got: {e:?}"),
762 Ok(_) => panic!("Expected foreign key constraint error, but flush succeeded"),
763 }
764
765 Ok(())
766 }
767
768 #[test]
769 fn test_appender_defaults_and_column_switching() -> Result<()> {
770 let db = Connection::open_in_memory()?;
771 db.execute_batch("CREATE TABLE foo(a INT DEFAULT 99, b INT, c INT DEFAULT 7)")?;
772
773 {
775 let mut app = db.appender_with_columns("foo", &["b"])?;
776 app.append_row([Some(1)])?;
777 app.append_row([Option::<i32>::None])?;
778 }
779
780 {
782 let mut app = db.appender("foo")?;
783 app.add_column("c")?;
784 app.add_column("a")?;
785 app.append_row([10, 1])?; app.clear_columns()?; app.append_row([2, 3, 4])?;
789 }
790
791 let rows: Vec<(i32, Option<i32>, i32)> = db
792 .prepare("SELECT a, b, c FROM foo ORDER BY a, b NULLS LAST")?
793 .query_map([], |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)))?
794 .collect::<Result<_>>()?;
795
796 assert_eq!(
797 rows,
798 vec![
799 (1, None, 10), (2, Some(3), 4), (99, Some(1), 7), (99, None, 7) ]
804 );
805
806 Ok(())
807 }
808
809 #[test]
810 fn test_appender_with_columns_sequence_default() -> Result<()> {
811 let db = Connection::open_in_memory()?;
812 db.execute_batch(
813 "CREATE SEQUENCE seq START 1;
814 CREATE TABLE foo(id INTEGER DEFAULT nextval('seq'), name TEXT)",
815 )?;
816
817 {
818 let mut app = db.appender_with_columns("foo", &["name"])?;
819 app.append_row(["Alice"])?;
820 app.append_row(["Bob"])?;
821 app.append_row(["Charlie"])?;
822 }
823
824 let rows: Vec<(i32, String)> = db
825 .prepare("SELECT id, name FROM foo ORDER BY id")?
826 .query_map([], |row| Ok((row.get(0)?, row.get(1)?)))?
827 .collect::<Result<_>>()?;
828
829 assert_eq!(
830 rows,
831 vec![(1, "Alice".into()), (2, "Bob".into()), (3, "Charlie".into())]
832 );
833
834 Ok(())
835 }
836
837 #[test]
838 fn test_appender_with_columns_to_db_schema() -> Result<()> {
839 let db = Connection::open_in_memory()?;
840 db.execute_batch(
841 "CREATE SCHEMA s;
842 CREATE TABLE s.foo(a INTEGER DEFAULT 5, b INTEGER)",
843 )?;
844
845 {
846 let mut app = db.appender_with_columns_to_db("foo", "s", &["b"])?;
847 app.append_row([7])?;
848 }
849
850 let (a, b): (i32, i32) = db.query_row("SELECT a, b FROM s.foo", [], |row| Ok((row.get(0)?, row.get(1)?)))?;
851 assert_eq!((a, b), (5, 7));
852 Ok(())
853 }
854
855 #[test]
856 fn test_appender_with_columns_to_catalog_and_db() -> Result<()> {
857 let db = Connection::open_in_memory()?;
858 db.execute_batch(
859 "CREATE SCHEMA s;
860 CREATE TABLE s.bar(a INTEGER DEFAULT 11, b INTEGER)",
861 )?;
862
863 {
864 let mut app = db.appender_with_columns_to_catalog_and_db("bar", "memory", "s", &["b"])?;
866 app.append_row([9])?;
867 }
868
869 let (a, b): (i32, i32) = db.query_row("SELECT a, b FROM s.bar", [], |row| Ok((row.get(0)?, row.get(1)?)))?;
870 assert_eq!((a, b), (11, 9));
871 Ok(())
872 }
873
874 #[test]
875 fn test_appender_decimal() -> Result<()> {
876 let d1 = Decimal::new(20, 4, 11344)?;
877 let d2 = Decimal::new(20, 3, 12312)?;
878 let d3 = Decimal::new(20, 5, -98765)?;
879
880 let conn = Connection::open_in_memory()?;
881 conn.execute_batch("CREATE TABLE decimals (value DECIMAL(20, 10));")?;
882
883 let mut appender = conn.appender("decimals")?;
884 appender.append_row(params![d1])?;
885 appender.append_row(params![d2])?;
886 appender.append_row(params![d3])?;
887 appender.flush()?;
888
889 let results: Vec<Decimal> = conn
890 .prepare("SELECT value FROM decimals ORDER BY value ASC")?
891 .query_map([], |row| row.get(0))?
892 .collect::<Result<Vec<Decimal>>>()?;
893
894 assert_eq!(
895 results,
896 vec![
897 Decimal::new(20, 10, -9876500000)?,
898 Decimal::new(20, 10, 11344000000)?,
899 Decimal::new(20, 10, 123120000000)?,
900 ]
901 );
902
903 Ok(())
904 }
905
906 #[test]
907 fn test_appender_decimal_hugeint_upper_bits() -> Result<()> {
908 let negative = Decimal::new(28, 10, -7922816251426433759354395033_i128)?;
909 let positive = Decimal::new(28, 10, 7922816251426433759354395033_i128)?;
910
911 let conn = Connection::open_in_memory()?;
912 conn.execute_batch("CREATE TABLE decimals (value DECIMAL(28, 10));")?;
913
914 let mut appender = conn.appender("decimals")?;
915 appender.append_row(params![negative])?;
916 appender.append_row(params![positive])?;
917 appender.flush()?;
918
919 let results: Vec<Decimal> = conn
920 .prepare("SELECT value FROM decimals ORDER BY value ASC")?
921 .query_map([], |row| row.get(0))?
922 .collect::<Result<Vec<Decimal>>>()?;
923
924 assert_eq!(results, vec![negative, positive]);
925 Ok(())
926 }
927
928 #[test]
929 fn test_appender_decimal_boundary_values() -> Result<()> {
930 let conn = Connection::open_in_memory()?;
931 conn.execute_batch("CREATE TABLE decimals (value DECIMAL(29, 0));")?;
932
933 let zero = Decimal::new(29, 0, 0)?;
934 let max = Decimal::new(29, 0, 79_228_162_514_264_337_593_543_950_335)?;
935
936 let mut appender = conn.appender("decimals")?;
937 appender.append_row(params![zero])?;
938 appender.append_row(params![max])?;
939 appender.flush()?;
940
941 let results: Vec<Decimal> = conn
942 .prepare("SELECT value FROM decimals ORDER BY value ASC")?
943 .query_map([], |row| row.get(0))?
944 .collect::<Result<Vec<Decimal>>>()?;
945
946 assert_eq!(results, vec![zero, max]);
947 Ok(())
948 }
949
950 #[test]
951 fn test_appender_native_decimal() -> Result<()> {
952 let conn = Connection::open_in_memory()?;
953 conn.execute_batch("CREATE TABLE decimals (value DECIMAL(38, 2));")?;
954
955 let direct = Decimal::new(38, 2, 12_345_678_901_234_567_890_123_456_789_012)?;
956 let owned = Decimal::new(38, 2, -12_345_678_901_234_567_890_123_456_789_012)?;
957
958 let mut appender = conn.appender("decimals")?;
959 appender.append_row(params![direct])?;
960 appender.append_row(params![Value::Decimal(owned)])?;
961 appender.flush()?;
962
963 let results: Vec<Decimal> = conn
964 .prepare("SELECT value FROM decimals ORDER BY value ASC")?
965 .query_map([], |row| row.get(0))?
966 .collect::<Result<Vec<Decimal>>>()?;
967
968 assert_eq!(results, vec![owned, direct]);
969 Ok(())
970 }
971}