1#![warn(missing_docs)]
57
58pub use libduckdb_sys as ffi;
59
60use std::{
61 cell::RefCell,
62 convert,
63 ffi::CString,
64 fmt,
65 path::{Path, PathBuf},
66 result, str,
67};
68
69use crate::{cache::StatementCache, inner_connection::InnerConnection, raw_statement::RawStatement, types::ValueRef};
70
71#[cfg(feature = "r2d2")]
72pub use crate::r2d2::DuckdbConnectionManager;
73pub use crate::{
74 appender::Appender,
75 appender_params::{AppenderParams, AppenderParamsFromIter, appender_params_from_iter},
76 arrow_batch::{Arrow, ArrowStream},
77 cache::CachedStatement,
78 column::Column,
79 config::{AccessMode, Config, DefaultNullOrder, DefaultOrder},
80 error::Error,
81 ffi::ErrorCode,
82 inner_connection::InterruptHandle,
83 params::{Params, ParamsFromIter, params_from_iter},
84 row::{AndThenRows, Map, MappedRows, Row, RowIndex, Rows},
85 statement::Statement,
86 transaction::{DropBehavior, Transaction},
87 types::ToSql,
88};
89#[cfg(feature = "polars")]
90pub use polars_dataframe::Polars;
91
92pub use arrow;
94#[cfg(feature = "loadable-extension")]
95pub use duckdb_loadable_macros::duckdb_entrypoint_c_api;
96#[cfg(feature = "polars")]
97pub use polars;
98
99pub mod core;
101
102#[macro_use]
103mod error;
104mod appender;
105mod appender_params;
106mod arrow_batch;
107#[cfg(feature = "vtab-arrow")]
108mod arrow_interop;
109mod cache;
110#[cfg(any(feature = "vscalar", feature = "vtab"))]
111mod callback;
112mod column;
113mod config;
114mod executed_result;
115mod inner_connection;
116mod params;
117
118#[cfg(feature = "polars")]
119mod polars_dataframe;
120mod pragma;
121#[cfg(feature = "r2d2")]
122mod r2d2;
123mod raw_statement;
124mod row;
125mod statement;
126mod transaction;
127
128#[cfg(any(
129 feature = "autocomplete",
130 feature = "icu",
131 feature = "json",
132 feature = "parquet",
133 feature = "tpcds",
134 feature = "tpch"
135))]
136mod extension;
137
138pub mod profiling;
139pub mod types;
140#[cfg(feature = "vtab")]
142pub mod vtab;
143
144#[cfg(feature = "vscalar")]
146pub mod vscalar;
147
148#[cfg(test)]
149mod test_all_types;
150
151const STATEMENT_CACHE_DEFAULT_CAPACITY: usize = 16;
153
154#[macro_export]
176macro_rules! params {
177 () => {
178 &[] as &[&dyn $crate::ToSql]
179 };
180 ($($param:expr),+ $(,)?) => {
181 &[$(&$param as &dyn $crate::ToSql),+] as &[&dyn $crate::ToSql]
182 };
183}
184
185#[macro_export]
211macro_rules! named_params {
212 ($($name:literal : $param:expr),* $(,)?) => {
213 &[$(($name, &$param as &dyn $crate::ToSql)),*] as &[(&str, &dyn $crate::ToSql)]
214 };
215}
216
217pub type Result<T, E = Error> = result::Result<T, E>;
219
220pub trait OptionalExt<T> {
222 fn optional(self) -> Result<Option<T>>;
228}
229
230impl<T> OptionalExt<T> for Result<T> {
231 fn optional(self) -> Result<Option<T>> {
232 match self {
233 Ok(value) => Ok(Some(value)),
234 Err(Error::QueryReturnedNoRows) => Ok(None),
235 Err(e) => Err(e),
236 }
237 }
238}
239
240#[derive(Copy, Clone, Debug)]
242pub enum DatabaseName<'a> {
243 Main,
245
246 Temp,
248
249 Attached(&'a str),
251}
252
253#[allow(clippy::needless_lifetimes)]
254impl<'a> fmt::Display for DatabaseName<'a> {
255 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
256 match *self {
257 DatabaseName::Main => write!(f, "main"),
258 DatabaseName::Temp => write!(f, "temp"),
259 DatabaseName::Attached(s) => write!(f, "{s}"),
260 }
261 }
262}
263
264pub const MAIN_DB: DatabaseName<'static> = DatabaseName::Main;
266
267pub const TEMP_DB: DatabaseName<'static> = DatabaseName::Temp;
269
270pub struct Connection {
272 db: RefCell<InnerConnection>,
273 cache: StatementCache,
274 path: Option<PathBuf>,
275}
276
277unsafe impl Send for Connection {}
278
279impl Connection {
280 #[inline]
301 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
302 Self::open_with_flags(path, Config::default())
303 }
304
305 #[inline]
311 pub fn open_in_memory() -> Result<Self> {
312 Self::open_in_memory_with_flags(Config::default())
313 }
314
315 #[inline]
324 pub unsafe fn open_from_raw(raw: ffi::duckdb_database) -> Result<Self> {
325 unsafe { InnerConnection::new_from_raw_db(raw, false) }.map(|db| Self {
326 db: RefCell::new(db),
327 cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY),
328 path: None, })
330 }
331
332 #[inline]
339 pub fn open_with_flags<P: AsRef<Path>>(path: P, config: Config) -> Result<Self> {
340 #[cfg(unix)]
341 fn path_to_cstring(p: &Path) -> Result<CString> {
342 use std::os::unix::ffi::OsStrExt;
343 Ok(CString::new(p.as_os_str().as_bytes())?)
344 }
345
346 #[cfg(not(unix))]
347 fn path_to_cstring(p: &Path) -> Result<CString> {
348 let s = p.to_str().ok_or_else(|| Error::InvalidPath(p.to_owned()))?;
349 Ok(CString::new(s)?)
350 }
351
352 let c_path = path_to_cstring(path.as_ref())?;
353 let config = config.with("duckdb_api", "rust").unwrap();
354 InnerConnection::open_with_flags(&c_path, config).map(|db| Self {
355 db: RefCell::new(db),
356 cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY),
357 path: Some(path.as_ref().to_path_buf()),
358 })
359 }
360
361 #[inline]
367 pub fn open_in_memory_with_flags(config: Config) -> Result<Self> {
368 Self::open_with_flags(":memory:", config)
369 }
370
371 pub fn execute_batch(&self, sql: &str) -> Result<()> {
392 self.db.borrow_mut().execute(sql)
393 }
394
395 #[inline]
431 pub fn execute<P: Params>(&self, sql: &str, params: P) -> Result<usize> {
432 self.prepare(sql).and_then(|mut stmt| stmt.execute(params))
433 }
434
435 #[inline]
437 pub fn path(&self) -> Option<&Path> {
438 self.path.as_deref()
439 }
440
441 #[inline]
469 pub fn query_row<T, P, F>(&self, sql: &str, params: P, f: F) -> Result<T>
470 where
471 P: Params,
472 F: FnOnce(&Row<'_>) -> Result<T>,
473 {
474 self.prepare(sql)?.query_row(params, f)
475 }
476
477 #[inline]
503 pub fn query_row_and_then<T, E, P, F>(&self, sql: &str, params: P, f: F) -> Result<T, E>
504 where
505 P: Params,
506 F: FnOnce(&Row<'_>) -> Result<T, E>,
507 E: convert::From<Error>,
508 {
509 self.prepare(sql)?
510 .query(params)?
511 .get_expected_row()
512 .map_err(E::from)
513 .and_then(f)
514 }
515
516 #[inline]
535 pub fn prepare(&self, sql: &str) -> Result<Statement<'_>> {
536 self.db.borrow_mut().prepare(self, sql)
537 }
538
539 pub fn appender(&self, table: &str) -> Result<Appender<'_>> {
557 self.appender_to_db(table, &DatabaseName::Main.to_string())
558 }
559
560 pub fn appender_to_db(&self, table: &str, schema: &str) -> Result<Appender<'_>> {
577 self.db.borrow_mut().appender(self, None, schema, table)
578 }
579
580 pub fn appender_to_catalog_and_db(&self, table: &str, catalog: &str, schema: &str) -> Result<Appender<'_>> {
597 self.db.borrow_mut().appender(self, Some(catalog), schema, table)
598 }
599
600 pub fn appender_with_columns(&self, table: &str, columns: &[&str]) -> Result<Appender<'_>> {
624 self.appender_with_columns_to_db(table, &DatabaseName::Main.to_string(), columns)
625 }
626
627 pub fn appender_with_columns_to_db(&self, table: &str, schema: &str, columns: &[&str]) -> Result<Appender<'_>> {
631 self.db
632 .borrow_mut()
633 .appender_with_columns(self, None, schema, table, columns)
634 }
635
636 pub fn appender_with_columns_to_catalog_and_db(
640 &self,
641 table: &str,
642 catalog: &str,
643 schema: &str,
644 columns: &[&str],
645 ) -> Result<Appender<'_>> {
646 self.db
647 .borrow_mut()
648 .appender_with_columns(self, Some(catalog), schema, table, columns)
649 }
650
651 pub fn interrupt_handle(&self) -> std::sync::Arc<InterruptHandle> {
672 self.db.borrow().get_interrupt_handle()
673 }
674
675 #[inline]
685 #[allow(clippy::result_large_err)]
686 pub fn close(self) -> Result<(), (Self, Error)> {
687 let r = self.db.borrow_mut().close();
688 r.map_err(move |err| (self, err))
689 }
690
691 #[inline]
694 pub fn is_autocommit(&self) -> bool {
695 self.db.borrow().is_autocommit()
696 }
697
698 pub fn try_clone(&self) -> Result<Self> {
700 let inner = self.db.borrow().try_clone()?;
701 Ok(Self {
702 db: RefCell::new(inner),
703 cache: StatementCache::with_capacity(STATEMENT_CACHE_DEFAULT_CAPACITY),
704 path: self.path.clone(),
705 })
706 }
707
708 pub fn version(&self) -> Result<String> {
710 self.query_row("PRAGMA version", [], |row| row.get(0))
711 }
712}
713
714impl fmt::Debug for Connection {
715 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
716 f.debug_struct("Connection").field("path", &self.path).finish()
717 }
718}
719
720#[cfg(doctest)]
721doc_comment::doctest!("../../../README.md");
722
723#[cfg(test)]
724mod test {
725 use crate::types::Value;
726
727 use super::*;
728 use std::{error::Error as StdError, fmt};
729
730 use arrow::{array::Int32Array, datatypes::DataType, record_batch::RecordBatch};
731 use fallible_iterator::FallibleIterator;
732
733 #[test]
734 fn connection_is_send() {
735 fn assert_send<T: Send>() {
736 let _ = std::marker::PhantomData::<T>;
737 }
738
739 assert_send::<Connection>();
740 }
741
742 pub fn checked_memory_handle() -> Connection {
743 Connection::open_in_memory().unwrap()
744 }
745
746 #[test]
747 fn test_params_of_vary_types() -> Result<()> {
748 let db = checked_memory_handle();
749 let sql = "BEGIN;
750 CREATE TABLE foo(bar TEXT, qux INTEGER);
751 INSERT INTO foo VALUES ('baz', 1), ('baz', 2), ('baz', 3);
752 END;";
753 db.execute_batch(sql)?;
754
755 let changed = db.execute("UPDATE foo SET qux = ? WHERE bar = ?", params![1i32, &"baz"])?;
756 assert_eq!(changed, 3);
757 Ok(())
758 }
759
760 #[test]
761 #[cfg_attr(windows, ignore = "Windows doesn't allow concurrent writes to a file")]
762 fn test_concurrent_transactions_busy_commit() -> Result<()> {
763 let tmp = tempfile::tempdir().unwrap();
764 let path = tmp.path().join("transactions.db3");
765
766 Connection::open(&path)?.execute_batch(
767 "
768 BEGIN;
769 CREATE TABLE foo(x INTEGER);
770 INSERT INTO foo VALUES(42);
771 END;",
772 )?;
773
774 let mut db1 =
775 Connection::open_with_flags(&path, Config::default().access_mode(config::AccessMode::ReadWrite)?)?;
776 let mut db2 =
777 Connection::open_with_flags(&path, Config::default().access_mode(config::AccessMode::ReadWrite)?)?;
778
779 {
780 let tx1 = db1.transaction()?;
781 let tx2 = db2.transaction()?;
782
783 tx1.query_row("SELECT x FROM foo LIMIT 1", [], |_| Ok(()))?;
785 tx2.query_row("SELECT x FROM foo LIMIT 1", [], |_| Ok(()))?;
786
787 tx1.execute("INSERT INTO foo VALUES(?1)", [1])?;
788 let _ = tx2.execute("INSERT INTO foo VALUES(?1)", [2]);
789
790 let _ = tx1.commit();
791 let _ = tx2.commit();
792 }
793
794 let _ = db1.transaction().expect("commit should have closed transaction");
795 let _ = db2.transaction().expect("commit should have closed transaction");
796 Ok(())
797 }
798
799 #[test]
800 fn test_persistence() -> Result<()> {
801 let temp_dir = tempfile::tempdir().unwrap();
802 let path = temp_dir.path().join("test.db3");
803
804 {
805 let db = Connection::open(&path)?;
806 let sql = "BEGIN;
807 CREATE TABLE foo(x INTEGER);
808 INSERT INTO foo VALUES(42);
809 END;";
810 db.execute_batch(sql)?;
811 }
812
813 let path_string = path.to_str().unwrap();
814 let db = Connection::open(path_string)?;
815 let the_answer: Result<i64> = db.query_row("SELECT x FROM foo", [], |r| r.get(0));
816
817 assert_eq!(42i64, the_answer?);
818 Ok(())
819 }
820
821 #[test]
822 fn test_open() {
823 let con = Connection::open_in_memory();
824 if let Err(e) = con {
825 panic!("open error {e}");
826 }
827 assert!(Connection::open_in_memory().is_ok());
828 let db = checked_memory_handle();
829 assert!(db.close().is_ok());
830 let _ = checked_memory_handle();
831 let _ = checked_memory_handle();
832 }
833
834 #[test]
835 fn test_open_from_raw() {
836 unsafe {
837 use std::{ffi::c_void, os::raw::c_char, ptr};
838
839 let mut db: ffi::duckdb_database = ptr::null_mut();
840 let mut c_err: *mut c_char = ptr::null_mut();
841 let r = ffi::duckdb_open_ext(
842 c":memory:".as_ptr(),
843 &mut db,
844 Config::default().duckdb_config(),
845 &mut c_err,
846 );
847 if r != ffi::DuckDBSuccess {
848 if !c_err.is_null() {
849 ffi::duckdb_free(c_err as *mut c_void);
850 }
851 panic!("duckdb_open_ext failed: {r:?}");
852 }
853
854 let conn = Connection::open_from_raw(db).unwrap();
855 conn.execute_batch("SELECT 1").unwrap();
856 let cloned = conn.try_clone().unwrap();
857 drop(conn);
858 cloned.execute_batch("SELECT 2").unwrap();
859 cloned.close().unwrap();
860
861 ffi::duckdb_close(&mut db);
862 }
863 }
864
865 #[test]
866 fn test_open_failure() -> Result<()> {
867 let filename = "no_such_file.db";
868 let result =
869 Connection::open_with_flags(filename, Config::default().access_mode(config::AccessMode::ReadOnly)?);
870 assert!(result.is_err());
871 let err = result.err().unwrap();
872 if let Error::DuckDBFailure(_e, Some(msg)) = err {
873 assert!(
876 msg.contains(filename),
877 "error message '{msg}' does not contain '{filename}'"
878 );
879 } else {
880 panic!("DuckDBFailure expected");
881 }
882 Ok(())
883 }
884
885 #[cfg(unix)]
886 #[test]
887 fn test_invalid_unicode_file_names() -> Result<()> {
888 use std::{ffi::OsStr, fs::File, os::unix::ffi::OsStrExt};
889 let temp_dir = tempfile::tempdir().unwrap();
890
891 let path = temp_dir.path();
892 if File::create(path.join(OsStr::from_bytes(&[0xFE]))).is_err() {
893 return Ok(());
895 }
896 let db_path = path.join(OsStr::from_bytes(&[0xFF]));
897 {
898 let db = Connection::open(&db_path)?;
899 let sql = "BEGIN;
900 CREATE TABLE foo(x INTEGER);
901 INSERT INTO foo VALUES(42);
902 END;";
903 db.execute_batch(sql)?;
904 }
905
906 let db = Connection::open(&db_path)?;
907 let the_answer: Result<i64> = db.query_row("SELECT x FROM foo", [], |r| r.get(0));
908
909 assert_eq!(42i64, the_answer?);
910 Ok(())
911 }
912
913 #[test]
914 fn test_close_always_ok() -> Result<()> {
915 let db = checked_memory_handle();
916
917 db.close().unwrap();
920 Ok(())
921 }
922
923 #[test]
924 fn test_execute_batch() -> Result<()> {
925 let db = checked_memory_handle();
926 let sql = "BEGIN;
927 CREATE TABLE foo(x INTEGER);
928 INSERT INTO foo VALUES(1);
929 INSERT INTO foo VALUES(2);
930 INSERT INTO foo VALUES(3);
931 INSERT INTO foo VALUES(4);
932 END;";
933 db.execute_batch(sql)?;
934
935 db.execute_batch("UPDATE foo SET x = 3 WHERE x < 3")?;
936
937 assert!(db.execute_batch("INVALID SQL").is_err());
938 Ok(())
939 }
940
941 #[test]
942 fn test_execute_single() -> Result<()> {
943 let db = checked_memory_handle();
944 db.execute_batch("CREATE TABLE foo(x INTEGER)")?;
945
946 assert_eq!(
947 3,
948 db.execute("INSERT INTO foo(x) VALUES (?), (?), (?)", [1i32, 2i32, 3i32])?
949 );
950 assert_eq!(1, db.execute("INSERT INTO foo(x) VALUES (?)", [4i32])?);
951
952 assert_eq!(
953 10i32,
954 db.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
955 );
956 Ok(())
957 }
958
959 #[test]
960 fn test_prepare_column_names() -> Result<()> {
961 let db = checked_memory_handle();
962 db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
963
964 let mut stmt = db.prepare("SELECT * FROM foo")?;
965 stmt.execute([])?;
966 assert_eq!(stmt.column_count(), 1);
967 assert_eq!(stmt.column_names(), vec!["x"]);
968
969 let mut stmt = db.prepare("SELECT x AS a, x AS b FROM foo")?;
970 stmt.execute([])?;
971 assert_eq!(stmt.column_count(), 2);
972 assert_eq!(stmt.column_names(), vec!["a", "b"]);
973 Ok(())
974 }
975
976 #[test]
977 fn test_prepare_execute() -> Result<()> {
978 let db = checked_memory_handle();
979 db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
980
981 let mut insert_stmt = db.prepare("INSERT INTO foo(x) VALUES(?)")?;
982 assert_eq!(insert_stmt.execute([1i32])?, 1);
983 assert_eq!(insert_stmt.execute([2i32])?, 1);
984 assert_eq!(insert_stmt.execute([3i32])?, 1);
985
986 assert!(insert_stmt.execute(["hello"]).is_err());
987 let mut update_stmt = db.prepare("UPDATE foo SET x=? WHERE x<?")?;
992 assert_eq!(update_stmt.execute([3i32, 3i32])?, 2);
993 assert_eq!(update_stmt.execute([3i32, 3i32])?, 0);
994 assert_eq!(update_stmt.execute([8i32, 8i32])?, 3);
995 Ok(())
996 }
997
998 #[test]
999 fn test_prepare_query() -> Result<()> {
1000 let db = checked_memory_handle();
1001 db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
1002
1003 let mut insert_stmt = db.prepare("INSERT INTO foo(x) VALUES(?)")?;
1004 assert_eq!(insert_stmt.execute([1i32])?, 1);
1005 assert_eq!(insert_stmt.execute([2i32])?, 1);
1006 assert_eq!(insert_stmt.execute([3i32])?, 1);
1007
1008 let mut query = db.prepare("SELECT x FROM foo WHERE x < ? ORDER BY x DESC")?;
1009 {
1010 let mut rows = query.query([4i32])?;
1011 let mut v = Vec::<i32>::new();
1012
1013 while let Some(row) = rows.next()? {
1014 v.push(row.get(0)?);
1015 }
1016
1017 assert_eq!(v, [3i32, 2, 1]);
1018 }
1019
1020 {
1021 let mut rows = query.query([3i32])?;
1022 let mut v = Vec::<i32>::new();
1023
1024 while let Some(row) = rows.next()? {
1025 v.push(row.get(0)?);
1026 }
1027
1028 assert_eq!(v, [2i32, 1]);
1029 }
1030 Ok(())
1031 }
1032
1033 #[test]
1034 fn test_query_map() -> Result<()> {
1035 let db = checked_memory_handle();
1036 let sql = "BEGIN;
1037 CREATE TABLE foo(x INTEGER, y TEXT);
1038 INSERT INTO foo VALUES(4, 'hello');
1039 INSERT INTO foo VALUES(3, ', ');
1040 INSERT INTO foo VALUES(2, 'world');
1041 INSERT INTO foo VALUES(1, '!');
1042 END;";
1043 db.execute_batch(sql)?;
1044
1045 let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC")?;
1046 let results: Result<Vec<String>> = query.query([])?.map(|row| row.get(1)).collect();
1047
1048 assert_eq!(results?.concat(), "hello, world!");
1049 Ok(())
1050 }
1051
1052 #[test]
1053 fn test_query_row() -> Result<()> {
1054 let db = checked_memory_handle();
1055 let sql = "BEGIN;
1056 CREATE TABLE foo(x INTEGER);
1057 INSERT INTO foo VALUES(1);
1058 INSERT INTO foo VALUES(2);
1059 INSERT INTO foo VALUES(3);
1060 INSERT INTO foo VALUES(4);
1061 END;";
1062 db.execute_batch(sql)?;
1063
1064 assert_eq!(
1065 10i64,
1066 db.query_row::<i64, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
1067 );
1068
1069 let result: Result<i64> = db.query_row("SELECT x FROM foo WHERE x > 5", [], |r| r.get(0));
1070 match result.unwrap_err() {
1071 Error::QueryReturnedNoRows => (),
1072 err => panic!("Unexpected error {err}"),
1073 }
1074
1075 let bad_query_result = db.query_row("NOT A PROPER QUERY; test123", [], |_| Ok(()));
1076
1077 assert!(bad_query_result.is_err());
1078 Ok(())
1079 }
1080
1081 #[test]
1082 fn test_optional() -> Result<()> {
1083 let db = checked_memory_handle();
1084
1085 let result: Result<i64> = db.query_row("SELECT 1 WHERE 0 <> 0", [], |r| r.get(0));
1086 let result = result.optional();
1087 match result? {
1088 None => (),
1089 _ => panic!("Unexpected result"),
1090 }
1091
1092 let result: Result<i64> = db.query_row("SELECT 1 WHERE 0 == 0", [], |r| r.get(0));
1093 let result = result.optional();
1094 match result? {
1095 Some(1) => (),
1096 _ => panic!("Unexpected result"),
1097 }
1098
1099 let bad_query_result: Result<i64> = db.query_row("NOT A PROPER QUERY", [], |r| r.get(0));
1100 let bad_query_result = bad_query_result.optional();
1101 assert!(bad_query_result.is_err());
1102 Ok(())
1103 }
1104
1105 #[test]
1106 fn test_prepare_failures() -> Result<()> {
1107 let db = checked_memory_handle();
1108 db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
1109
1110 let _ = db.prepare("SELECT * FROM does_not_exist").unwrap_err();
1111 Ok(())
1113 }
1114
1115 #[test]
1116 fn test_is_autocommit() {
1117 let db = checked_memory_handle();
1118 assert!(db.is_autocommit(), "autocommit expected to be active by default");
1119 }
1120
1121 #[test]
1122 #[should_panic(expected = "not supported")]
1123 fn test_statement_debugging() {
1124 let db = checked_memory_handle();
1125 let query = "SELECT 12345";
1126 let stmt = db.prepare(query).unwrap();
1127
1128 assert!(format!("{stmt:?}").contains(query));
1129 }
1130
1131 #[test]
1132 fn test_notnull_constraint_error() -> Result<()> {
1133 let db = checked_memory_handle();
1134 db.execute_batch("CREATE TABLE foo(x TEXT NOT NULL)")?;
1135
1136 let result = db.execute("INSERT INTO foo (x) VALUES (NULL)", []);
1137 assert!(result.is_err());
1138
1139 match result.unwrap_err() {
1140 Error::DuckDBFailure(err, _) => {
1141 assert_eq!(err.code, ErrorCode::Unknown);
1143 }
1144 err => panic!("Unexpected error {err}"),
1145 }
1146 Ok(())
1147 }
1148
1149 #[test]
1150 fn test_clone() -> Result<()> {
1151 {
1153 let owned_con = checked_memory_handle();
1154 {
1155 let cloned_con = owned_con.try_clone().unwrap();
1156 cloned_con.execute_batch("create table test (c1 bigint)")?;
1157 cloned_con.close().unwrap();
1158 }
1159 owned_con.execute_batch("create table test2 (c1 bigint)")?;
1160 owned_con.close().unwrap();
1161 }
1162
1163 {
1165 let cloned_con = {
1166 let owned_con = checked_memory_handle();
1167 let clone = owned_con.try_clone().unwrap();
1168 owned_con.execute_batch("create table test (c1 bigint)")?;
1169 owned_con.close().unwrap();
1170 clone
1171 };
1172 cloned_con.execute_batch("create table test2 (c1 bigint)")?;
1173 cloned_con.close().unwrap();
1174 }
1175 Ok(())
1176 }
1177
1178 #[test]
1179 fn test_try_clone_after_owner_drop() -> Result<()> {
1180 let clone1 = {
1181 let owned = checked_memory_handle();
1182 let clone1 = owned.try_clone()?;
1183 drop(owned);
1184 clone1
1185 };
1186
1187 let clone2 = clone1.try_clone()?;
1188 clone2.execute_batch("CREATE TABLE t312(i INTEGER); INSERT INTO t312 VALUES (1);")?;
1189 let value: i32 = clone1.query_row("SELECT i FROM t312", [], |r| r.get(0))?;
1190 assert_eq!(value, 1);
1191 Ok(())
1192 }
1193
1194 mod query_and_then_tests {
1195 use super::*;
1196
1197 #[derive(Debug)]
1198 enum CustomError {
1199 SomeError,
1200 Sqlite(Error),
1201 }
1202
1203 impl fmt::Display for CustomError {
1204 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
1205 match *self {
1206 Self::SomeError => write!(f, "my custom error"),
1207 Self::Sqlite(ref se) => write!(f, "my custom error: {se}"),
1208 }
1209 }
1210 }
1211
1212 impl StdError for CustomError {
1213 fn description(&self) -> &str {
1214 "my custom error"
1215 }
1216
1217 fn cause(&self) -> Option<&dyn StdError> {
1218 match *self {
1219 Self::SomeError => None,
1220 Self::Sqlite(ref se) => Some(se),
1221 }
1222 }
1223 }
1224
1225 impl From<Error> for CustomError {
1226 fn from(se: Error) -> Self {
1227 Self::Sqlite(se)
1228 }
1229 }
1230
1231 type CustomResult<T> = Result<T, CustomError>;
1232
1233 #[test]
1234 fn test_query_and_then() -> Result<()> {
1235 let db = checked_memory_handle();
1236 let sql = "BEGIN;
1237 CREATE TABLE foo(x INTEGER, y TEXT);
1238 INSERT INTO foo VALUES(4, 'hello');
1239 INSERT INTO foo VALUES(3, ', ');
1240 INSERT INTO foo VALUES(2, 'world');
1241 INSERT INTO foo VALUES(1, '!');
1242 END;";
1243 db.execute_batch(sql)?;
1244
1245 let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC")?;
1246 let results: Result<Vec<String>> = query.query_and_then([], |row| row.get(1))?.collect();
1247
1248 assert_eq!(results?.concat(), "hello, world!");
1249 Ok(())
1250 }
1251
1252 #[test]
1253 fn test_query_and_then_fails() -> Result<()> {
1254 let db = checked_memory_handle();
1255 let sql = "BEGIN;
1256 CREATE TABLE foo(x INTEGER, y TEXT);
1257 INSERT INTO foo VALUES(4, 'hello');
1258 INSERT INTO foo VALUES(3, ', ');
1259 INSERT INTO foo VALUES(2, 'world');
1260 INSERT INTO foo VALUES(1, '!');
1261 END;";
1262 db.execute_batch(sql)?;
1263
1264 let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC")?;
1265 let bad_type: Result<Vec<f64>> = query.query_and_then([], |row| row.get(1))?.collect();
1266
1267 match bad_type.unwrap_err() {
1268 Error::InvalidColumnType(..) => (),
1269 err => panic!("Unexpected error {err}"),
1270 }
1271
1272 let bad_idx: Result<Vec<String>> = query.query_and_then([], |row| row.get(3))?.collect();
1273
1274 match bad_idx.unwrap_err() {
1275 Error::InvalidColumnIndex(_) => (),
1276 err => panic!("Unexpected error {err}"),
1277 }
1278 Ok(())
1279 }
1280
1281 #[test]
1282 fn test_query_and_then_custom_error() -> CustomResult<()> {
1283 let db = checked_memory_handle();
1284 let sql = "BEGIN;
1285 CREATE TABLE foo(x INTEGER, y TEXT);
1286 INSERT INTO foo VALUES(4, 'hello');
1287 INSERT INTO foo VALUES(3, ', ');
1288 INSERT INTO foo VALUES(2, 'world');
1289 INSERT INTO foo VALUES(1, '!');
1290 END;";
1291 db.execute_batch(sql)?;
1292
1293 let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC")?;
1294 let results: CustomResult<Vec<String>> = query
1295 .query_and_then([], |row| row.get(1).map_err(CustomError::Sqlite))?
1296 .collect();
1297
1298 assert_eq!(results?.concat(), "hello, world!");
1299 Ok(())
1300 }
1301
1302 #[test]
1303 fn test_query_and_then_custom_error_fails() -> Result<()> {
1304 let db = checked_memory_handle();
1305 let sql = "BEGIN;
1306 CREATE TABLE foo(x INTEGER, y TEXT);
1307 INSERT INTO foo VALUES(4, 'hello');
1308 INSERT INTO foo VALUES(3, ', ');
1309 INSERT INTO foo VALUES(2, 'world');
1310 INSERT INTO foo VALUES(1, '!');
1311 END;";
1312 db.execute_batch(sql)?;
1313
1314 let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC")?;
1315 let bad_type: CustomResult<Vec<f64>> = query
1316 .query_and_then([], |row| row.get(1).map_err(CustomError::Sqlite))?
1317 .collect();
1318
1319 match bad_type.unwrap_err() {
1320 CustomError::Sqlite(Error::InvalidColumnType(..)) => (),
1321 err => panic!("Unexpected error {err}"),
1322 }
1323
1324 let bad_idx: CustomResult<Vec<String>> = query
1325 .query_and_then([], |row| row.get(3).map_err(CustomError::Sqlite))?
1326 .collect();
1327
1328 match bad_idx.unwrap_err() {
1329 CustomError::Sqlite(Error::InvalidColumnIndex(_)) => (),
1330 err => panic!("Unexpected error {err}"),
1331 }
1332
1333 let non_sqlite_err: CustomResult<Vec<String>> =
1334 query.query_and_then([], |_| Err(CustomError::SomeError))?.collect();
1335
1336 match non_sqlite_err.unwrap_err() {
1337 CustomError::SomeError => (),
1338 err => panic!("Unexpected error {err}"),
1339 }
1340 Ok(())
1341 }
1342
1343 #[test]
1344 fn test_query_row_and_then_custom_error() -> CustomResult<()> {
1345 let db = checked_memory_handle();
1346 let sql = "BEGIN;
1347 CREATE TABLE foo(x INTEGER, y TEXT);
1348 INSERT INTO foo VALUES(4, 'hello');
1349 END;";
1350 db.execute_batch(sql)?;
1351
1352 let query = "SELECT x, y FROM foo ORDER BY x DESC";
1353 let results: CustomResult<String> =
1354 db.query_row_and_then(query, [], |row| row.get(1).map_err(CustomError::Sqlite));
1355
1356 assert_eq!(results?, "hello");
1357 Ok(())
1358 }
1359
1360 #[test]
1361 fn test_query_row_and_then_custom_error_fails() -> Result<()> {
1362 let db = checked_memory_handle();
1363 let sql = "BEGIN;
1364 CREATE TABLE foo(x INTEGER, y TEXT);
1365 INSERT INTO foo VALUES(4, 'hello');
1366 END;";
1367 db.execute_batch(sql)?;
1368
1369 let query = "SELECT x, y FROM foo ORDER BY x DESC";
1370 let bad_type: CustomResult<f64> =
1371 db.query_row_and_then(query, [], |row| row.get(1).map_err(CustomError::Sqlite));
1372
1373 match bad_type.unwrap_err() {
1374 CustomError::Sqlite(Error::InvalidColumnType(..)) => (),
1375 err => panic!("Unexpected error {err}"),
1376 }
1377
1378 let bad_idx: CustomResult<String> =
1379 db.query_row_and_then(query, [], |row| row.get(3).map_err(CustomError::Sqlite));
1380
1381 match bad_idx.unwrap_err() {
1382 CustomError::Sqlite(Error::InvalidColumnIndex(_)) => (),
1383 err => panic!("Unexpected error {err}"),
1384 }
1385
1386 let non_sqlite_err: CustomResult<String> =
1387 db.query_row_and_then(query, [], |_| Err(CustomError::SomeError));
1388
1389 match non_sqlite_err.unwrap_err() {
1390 CustomError::SomeError => (),
1391 err => panic!("Unexpected error {err}"),
1392 }
1393 Ok(())
1394 }
1395
1396 #[test]
1397 fn test_rows_and_then_with_custom_error() -> Result<()> {
1398 let db = checked_memory_handle();
1399 db.execute_batch("CREATE TABLE test (value INTEGER)")?;
1400 db.execute_batch("INSERT INTO test VALUES (1), (3), (5)")?;
1401
1402 let mut stmt = db.prepare("SELECT value FROM test ORDER BY value")?;
1403 let rows = stmt.query([])?;
1404
1405 let results: Vec<i32> = rows
1407 .and_then(|row| -> CustomResult<i32> {
1408 let val: i32 = row.get(0)?; if val > 10 {
1410 Err(CustomError::SomeError) } else {
1412 Ok(val)
1413 }
1414 })
1415 .collect::<CustomResult<Vec<_>>>()
1416 .unwrap();
1417
1418 assert_eq!(results, vec![1, 3, 5]);
1419 Ok(())
1420 }
1421 }
1422
1423 #[test]
1424 fn test_dynamic() -> Result<()> {
1425 let db = checked_memory_handle();
1426 let sql = "BEGIN;
1427 CREATE TABLE foo(x INTEGER, y TEXT);
1428 INSERT INTO foo VALUES(4, 'hello');
1429 END;";
1430 db.execute_batch(sql)?;
1431
1432 db.query_row("SELECT * FROM foo", [], |r| {
1433 assert_eq!(2, r.as_ref().column_count());
1434 Ok(())
1435 })
1436 }
1437 #[test]
1438 fn test_dyn_box() -> Result<()> {
1439 let db = checked_memory_handle();
1440 db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
1441 let b: Box<dyn ToSql> = Box::new(5);
1442 db.execute("INSERT INTO foo VALUES(?)", [b])?;
1443 db.query_row("SELECT x FROM foo", [], |r| {
1444 assert_eq!(5, r.get_unwrap::<_, i32>(0));
1445 Ok(())
1446 })
1447 }
1448
1449 #[test]
1450 fn test_alter_table() -> Result<()> {
1451 let db = checked_memory_handle();
1452 db.execute_batch("CREATE TABLE x(t INTEGER);")?;
1453 db.execute("ALTER TABLE x RENAME TO y;", [])?;
1455 Ok(())
1456 }
1457
1458 #[test]
1459 fn test_query_arrow_record_batch_small() -> Result<()> {
1460 let db = checked_memory_handle();
1461 let sql = "BEGIN TRANSACTION;
1462 CREATE TABLE test(t INTEGER);
1463 INSERT INTO test VALUES (1); INSERT INTO test VALUES (2); INSERT INTO test VALUES (3); INSERT INTO test VALUES (4); INSERT INTO test VALUES (5);
1464 END TRANSACTION;";
1465 db.execute_batch(sql)?;
1466 let mut stmt = db.prepare("select t from test order by t desc")?;
1467 let mut arr = stmt.query_arrow([])?;
1468
1469 let schema = arr.get_schema();
1470 assert_eq!(schema.fields().len(), 1);
1471 assert_eq!(schema.field(0).name(), "t");
1472 assert_eq!(schema.field(0).data_type(), &DataType::Int32);
1473
1474 let rb = arr.next().unwrap();
1475 let column = rb.column(0).as_any().downcast_ref::<Int32Array>().unwrap();
1476 assert_eq!(column.len(), 5);
1477 assert_eq!(column.value(0), 5);
1478 assert_eq!(column.value(1), 4);
1479 assert_eq!(column.value(2), 3);
1480 assert_eq!(column.value(3), 2);
1481 assert_eq!(column.value(4), 1);
1482
1483 assert!(arr.next().is_none());
1484 Ok(())
1485 }
1486
1487 #[test]
1488 fn test_query_arrow_record_batch_large() -> Result<()> {
1489 let db = checked_memory_handle();
1490 db.execute_batch("BEGIN TRANSACTION")?;
1491 db.execute_batch("CREATE TABLE test(t INTEGER);")?;
1492 for _ in 0..600 {
1493 db.execute_batch("INSERT INTO test VALUES (1); INSERT INTO test VALUES (2); INSERT INTO test VALUES (3); INSERT INTO test VALUES (4); INSERT INTO test VALUES (5);")?;
1494 }
1495 db.execute_batch("END TRANSACTION")?;
1496 let rbs: Vec<RecordBatch> = db.prepare("select t from test order by t")?.query_arrow([])?.collect();
1497 assert_eq!(rbs.iter().map(|rb| rb.num_rows()).sum::<usize>(), 3000);
1500 assert_eq!(
1501 rbs.iter()
1502 .map(|rb| rb
1503 .column(0)
1504 .as_any()
1505 .downcast_ref::<Int32Array>()
1506 .unwrap()
1507 .iter()
1508 .map(|i| i.unwrap())
1509 .sum::<i32>())
1510 .sum::<i32>(),
1511 9000
1512 );
1513 Ok(())
1514 }
1515
1516 #[test]
1517 fn test_stream_arrow_with_call() -> Result<()> {
1518 let db = checked_memory_handle();
1519
1520 db.execute_batch(
1521 "CREATE TABLE test_data(id INTEGER, name VARCHAR);
1522 INSERT INTO test_data VALUES (1, 'Alice'), (2, 'Bob'), (3, 'Charlie');",
1523 )?;
1524
1525 db.execute_batch("CREATE MACRO test_func() AS TABLE SELECT * FROM test_data;")?;
1526
1527 let mut stmt = db.prepare("CALL test_func()")?;
1528 let rbs: Vec<RecordBatch> = stmt.stream_arrow([])?.collect();
1529
1530 assert!(!rbs.is_empty(), "Expected at least one record batch");
1532 let total_rows: usize = rbs.iter().map(|rb| rb.num_rows()).sum();
1533 assert_eq!(total_rows, 3);
1534
1535 let id_column = rbs[0].column(0).as_any().downcast_ref::<Int32Array>().unwrap();
1536 assert_eq!(id_column.value(0), 1);
1537
1538 Ok(())
1539 }
1540
1541 #[test]
1542 fn round_trip_interval() -> Result<()> {
1543 let db = checked_memory_handle();
1544 db.execute_batch("CREATE TABLE foo (t INTERVAL);")?;
1545
1546 let d = Value::Interval {
1547 months: 1,
1548 days: 2,
1549 nanos: 3,
1550 };
1551 db.execute("INSERT INTO foo VALUES (?)", [d])?;
1552
1553 let mut stmt = db.prepare("SELECT t FROM foo")?;
1554 let mut rows = stmt.query([])?;
1555 let row = rows.next()?.unwrap();
1556 let d: Value = row.get_unwrap(0);
1557 assert_eq!(d, d);
1558 Ok(())
1559 }
1560
1561 #[test]
1562 fn test_database_name_to_string() -> Result<()> {
1563 assert_eq!(DatabaseName::Main.to_string(), "main");
1564 assert_eq!(DatabaseName::Temp.to_string(), "temp");
1565 assert_eq!(DatabaseName::Attached("abc").to_string(), "abc");
1566 Ok(())
1567 }
1568
1569 #[test]
1570 fn test_interrupt() -> Result<()> {
1571 let db = checked_memory_handle();
1572 let db_interrupt = db.interrupt_handle();
1573
1574 let (tx, rx) = std::sync::mpsc::channel();
1575 std::thread::spawn(move || {
1576 let mut stmt = db
1577 .prepare("select count(*) from range(10000000) t1, range(1000000) t2")
1578 .unwrap();
1579 tx.send(stmt.execute([])).unwrap();
1580 });
1581
1582 std::thread::sleep(std::time::Duration::from_millis(100));
1583 db_interrupt.interrupt();
1584
1585 let result = rx.recv_timeout(std::time::Duration::from_secs(5)).unwrap();
1586 assert!(result.is_err_and(|err| err.to_string().contains("INTERRUPT")));
1587 Ok(())
1588 }
1589
1590 #[test]
1591 fn test_interrupt_on_dropped_db() {
1592 let db = checked_memory_handle();
1593 let db_interrupt = db.interrupt_handle();
1594
1595 drop(db);
1596 db_interrupt.interrupt();
1597 }
1598
1599 #[test]
1600 fn test_arrow_string_view_setting() -> Result<()> {
1601 {
1603 let config = Config::default().with("produce_arrow_string_view", "true")?;
1604 let conn = Connection::open_in_memory_with_flags(config)?;
1605
1606 let mut query = conn.prepare("SELECT 'test'::varchar AS str")?;
1607 let arrow = query.query_arrow([])?;
1608
1609 let batch = arrow.into_iter().next().expect("Expected at least one batch");
1610 assert_eq!(batch.schema().field(0).data_type(), &DataType::Utf8);
1611 }
1612
1613 {
1614 let config = Config::default()
1615 .with("produce_arrow_string_view", "true")?
1616 .with("arrow_output_version", "1.4")?;
1617 let conn = Connection::open_in_memory_with_flags(config)?;
1618
1619 let mut query = conn.prepare("SELECT 'test'::varchar AS str")?;
1620 let arrow = query.query_arrow([])?;
1621
1622 let batch = arrow.into_iter().next().expect("Expected at least one batch");
1623 assert_eq!(batch.schema().field(0).data_type(), &DataType::Utf8View);
1624 }
1625
1626 Ok(())
1627 }
1628
1629 #[test]
1630 fn test_prepare_multi_statement() -> Result<()> {
1631 let db = checked_memory_handle();
1632
1633 {
1634 let mut stmt =
1635 db.prepare("CREATE TABLE test(x INTEGER); INSERT INTO test VALUES (42); SELECT x FROM test;")?;
1636 let result: i32 = stmt.query_row([], |row| row.get(0))?;
1637 assert_eq!(result, 42);
1638 }
1639
1640 {
1641 let mut stmt = db.prepare(
1642 "CREATE TEMP TABLE temp_data(id INTEGER, value TEXT);
1643 INSERT INTO temp_data VALUES (1, 'first'), (2, 'second');
1644 SELECT COUNT(*) FROM temp_data;",
1645 )?;
1646 let count: i32 = stmt.query_row([], |row| row.get(0))?;
1647 assert_eq!(count, 2);
1648 }
1649
1650 Ok(())
1651 }
1652
1653 #[test]
1654 fn test_pivot_query() -> Result<()> {
1655 let db = checked_memory_handle();
1656
1657 db.execute_batch(
1658 "CREATE TABLE cities(city VARCHAR, year INTEGER, population INTEGER);
1659 INSERT INTO cities VALUES
1660 ('Amsterdam', 2000, 1005),
1661 ('Amsterdam', 2010, 1065),
1662 ('Amsterdam', 2020, 1158),
1663 ('Berlin', 2000, 3382),
1664 ('Berlin', 2010, 3460),
1665 ('Berlin', 2020, 3576);",
1666 )?;
1667
1668 let mut stmt = db.prepare("PIVOT cities ON year USING sum(population);")?;
1670 let mut rows = stmt.query([])?;
1671
1672 let mut row_count = 0;
1673 while let Some(_row) = rows.next()? {
1674 row_count += 1;
1675 }
1676 assert_eq!(row_count, 2);
1677
1678 Ok(())
1679 }
1680
1681 #[test]
1682 fn test_multiple_memory_databases() -> Result<()> {
1683 {
1685 let mem1 = Connection::open_in_memory()?;
1686 let mem2 = Connection::open_in_memory()?;
1687
1688 mem1.execute_batch("CREATE TABLE test (id INTEGER)")?;
1689 mem1.execute("INSERT INTO test VALUES (1)", [])?;
1690
1691 mem2.execute_batch("CREATE TABLE test (id INTEGER)")?;
1692 mem2.execute("INSERT INTO test VALUES (2)", [])?;
1693
1694 let value1: i32 = mem1.query_row("SELECT id FROM test", [], |r| r.get(0))?;
1695 assert_eq!(value1, 1);
1696
1697 let value2: i32 = mem2.query_row("SELECT id FROM test", [], |r| r.get(0))?;
1698 assert_eq!(value2, 2);
1699 }
1700
1701 {
1703 let shared = Connection::open_in_memory()?;
1704
1705 shared.execute_batch("CREATE TABLE shared_table (id INTEGER)")?;
1706 shared.execute("INSERT INTO shared_table VALUES (123)", [])?;
1707
1708 let cloned = shared.try_clone()?;
1709
1710 let value: i32 = cloned.query_row("SELECT id FROM shared_table", [], |r| r.get(0))?;
1712 assert_eq!(value, 123);
1713
1714 cloned.execute("INSERT INTO shared_table VALUES (456)", [])?;
1715
1716 let count: i64 = shared.query_row("SELECT COUNT(*) FROM shared_table", [], |r| r.get(0))?;
1718 assert_eq!(count, 2);
1719 }
1720
1721 Ok(())
1722 }
1723
1724 #[test]
1725 fn test_appender_with_catalog() -> Result<()> {
1726 let db = checked_memory_handle();
1727
1728 let temp_dir = tempfile::tempdir().unwrap();
1730 let attached_path = temp_dir.path().join("attached.db");
1731 db.execute_batch(&format!("ATTACH '{}' AS attached_db", attached_path.display()))?;
1732
1733 db.execute_batch("CREATE TABLE attached_db.main.test_table (id INTEGER, name TEXT)")?;
1735
1736 {
1738 let mut app = db.appender_to_catalog_and_db("test_table", "attached_db", "main")?;
1739 app.append_row(params![1, "Alice"])?;
1740 app.append_row(params![2, "Bob"])?;
1741 app.append_row(params![3, "Charlie"])?;
1742 }
1743
1744 let count: i64 = db.query_row("SELECT COUNT(*) FROM attached_db.main.test_table", [], |r| r.get(0))?;
1746 assert_eq!(count, 3);
1747
1748 let name: String = db.query_row("SELECT name FROM attached_db.main.test_table WHERE id = ?", [2], |r| {
1749 r.get(0)
1750 })?;
1751 assert_eq!(name, "Bob");
1752
1753 Ok(())
1754 }
1755
1756 #[test]
1757 fn test_appender_with_catalog_multiple_schemas() -> Result<()> {
1758 let db = checked_memory_handle();
1759
1760 let temp_dir = tempfile::tempdir().unwrap();
1762 let attached_path = temp_dir.path().join("multi_schema.db");
1763 db.execute_batch(&format!("ATTACH '{}' AS my_catalog", attached_path.display()))?;
1764
1765 db.execute_batch("CREATE SCHEMA my_catalog.schema1")?;
1767 db.execute_batch("CREATE SCHEMA my_catalog.schema2")?;
1768 db.execute_batch("CREATE TABLE my_catalog.schema1.data (value INTEGER)")?;
1769 db.execute_batch("CREATE TABLE my_catalog.schema2.data (value INTEGER)")?;
1770
1771 {
1773 let mut app = db.appender_to_catalog_and_db("data", "my_catalog", "schema1")?;
1774 app.append_rows([[10], [20], [30]])?;
1775 }
1776
1777 {
1779 let mut app = db.appender_to_catalog_and_db("data", "my_catalog", "schema2")?;
1780 app.append_rows([[100], [200]])?;
1781 }
1782
1783 let sum1: i64 = db.query_row("SELECT SUM(value) FROM my_catalog.schema1.data", [], |r| r.get(0))?;
1785 assert_eq!(sum1, 60);
1786
1787 let sum2: i64 = db.query_row("SELECT SUM(value) FROM my_catalog.schema2.data", [], |r| r.get(0))?;
1789 assert_eq!(sum2, 300);
1790
1791 Ok(())
1792 }
1793
1794 #[test]
1795 fn test_appender_with_catalog_main_vs_attached() -> Result<()> {
1796 let db = checked_memory_handle();
1797
1798 db.execute_batch("CREATE TABLE test (id INTEGER)")?;
1800
1801 let temp_dir = tempfile::tempdir().unwrap();
1803 let attached_path = temp_dir.path().join("other.db");
1804 db.execute_batch(&format!("ATTACH '{}' AS other_db", attached_path.display()))?;
1805 db.execute_batch("CREATE TABLE other_db.main.test (id INTEGER)")?;
1806
1807 {
1809 let mut app = db.appender_to_catalog_and_db("test", "memory", "main")?;
1810 app.append_rows([[1], [2]])?;
1811 }
1812
1813 {
1815 let mut app = db.appender_to_catalog_and_db("test", "other_db", "main")?;
1816 app.append_rows([[100], [200]])?;
1817 }
1818
1819 let count_main: i64 = db.query_row("SELECT COUNT(*) FROM test", [], |r| r.get(0))?;
1821 assert_eq!(count_main, 2);
1822
1823 let count_attached: i64 = db.query_row("SELECT COUNT(*) FROM other_db.main.test", [], |r| r.get(0))?;
1825 assert_eq!(count_attached, 2);
1826
1827 Ok(())
1828 }
1829
1830 #[test]
1831 fn test_appender_with_catalog_error_invalid_catalog() -> Result<()> {
1832 let db = checked_memory_handle();
1833
1834 let result = db.appender_to_catalog_and_db("test", "nonexistent_catalog", "main");
1836 assert!(result.is_err());
1837
1838 Ok(())
1839 }
1840
1841 #[test]
1842 fn test_appender_with_catalog_error_invalid_schema() -> Result<()> {
1843 let db = checked_memory_handle();
1844
1845 let temp_dir = tempfile::tempdir().unwrap();
1847 let attached_path = temp_dir.path().join("test.db");
1848 db.execute_batch(&format!("ATTACH '{}' AS my_db", attached_path.display()))?;
1849
1850 db.execute_batch("CREATE TABLE my_db.main.test (id INTEGER)")?;
1851
1852 let result = db.appender_to_catalog_and_db("test", "my_db", "nonexistent_schema");
1854 assert!(result.is_err());
1855
1856 Ok(())
1857 }
1858
1859 #[test]
1860 fn test_appender_with_catalog_flush() -> Result<()> {
1861 let db = checked_memory_handle();
1862
1863 let temp_dir = tempfile::tempdir().unwrap();
1865 let attached_path = temp_dir.path().join("flush_test.db");
1866 db.execute_batch(&format!("ATTACH '{}' AS flush_db", attached_path.display()))?;
1867
1868 db.execute_batch("CREATE TABLE flush_db.main.test (id INTEGER)")?;
1869
1870 {
1872 let mut app = db.appender_to_catalog_and_db("test", "flush_db", "main")?;
1873 app.append_row([1])?;
1874 app.append_row([2])?;
1875 app.flush()?;
1876 app.append_row([3])?;
1877 app.flush()?;
1878 }
1879
1880 let count: i64 = db.query_row("SELECT COUNT(*) FROM flush_db.main.test", [], |r| r.get(0))?;
1882 assert_eq!(count, 3);
1883
1884 Ok(())
1885 }
1886
1887 #[test]
1889 fn test_enum_read() -> Result<()> {
1890 let conn = Connection::open_in_memory()?;
1891 conn.execute_batch(
1892 r#"
1893 CREATE TABLE stats (
1894 name ENUM('CA', 'NY'),
1895 value INTEGER,
1896 );
1897 INSERT INTO stats VALUES ('CA', 10), ('CA', 20), ('NY', 4);
1898 "#,
1899 )?;
1900
1901 let mut stmt = conn.prepare("SELECT * FROM stats")?;
1902 let results: Vec<(String, i32)> = stmt
1903 .query_map([], |row| {
1904 let name: String = row.get(0)?;
1905 let value: i32 = row.get(1)?;
1906 Ok((name, value))
1907 })?
1908 .map(|r| r.unwrap())
1909 .collect();
1910
1911 assert_eq!(results.len(), 3);
1912 assert_eq!(results[0], ("CA".to_string(), 10));
1913 assert_eq!(results[1], ("CA".to_string(), 20));
1914 assert_eq!(results[2], ("NY".to_string(), 4));
1915 Ok(())
1916 }
1917
1918 #[test]
1919 fn test_enum_read_nullable() -> Result<()> {
1920 let conn = Connection::open_in_memory()?;
1921 conn.execute_batch(
1922 r#"
1923 CREATE TABLE stats (name ENUM('CA', 'NY'));
1924 INSERT INTO stats VALUES ('CA'), (NULL), ('NY');
1925 "#,
1926 )?;
1927
1928 let mut stmt = conn.prepare("SELECT name FROM stats")?;
1929 let results: Vec<Option<String>> = stmt.query_map([], |row| row.get(0))?.map(|r| r.unwrap()).collect();
1930
1931 assert_eq!(results, vec![Some("CA".into()), None, Some("NY".into())]);
1932 Ok(())
1933 }
1934}