1use std::{
2 ffi::{CStr, CString},
3 mem, ptr,
4 sync::Arc,
5};
6
7use crate::ffi::{
8 duckdb_bind_parameter_index, duckdb_clear_bindings, duckdb_destroy_prepare,
9 duckdb_execute_prepared, duckdb_free, duckdb_nparams, duckdb_param_logical_type,
10 duckdb_param_type, duckdb_parameter_name, duckdb_prepare,
11 duckdb_prepared_statement_column_count, duckdb_prepared_statement_column_logical_type,
12 duckdb_prepared_statement_column_name, duckdb_prepared_statement_column_type,
13 duckdb_prepared_statement_type, duckdb_result, duckdb_statement_type, duckdb_type,
14 DuckDBSuccess,
15};
16
17use crate::{
18 error::{Error, Result},
19 ffi,
20 ffi::duckdb_prepared_statement,
21 helpers::duck_result::{result_from_duckdb_prepare, result_from_duckdb_result},
22 raw::{
23 connection::{ConnectionInner, RawConnection},
24 result::DuckResult,
25 },
26 types::{appendable::AppendAble, LogicalType, TypeInfo},
27};
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
35#[non_exhaustive]
36pub enum StatementType {
37 Invalid,
39 Select,
41 Insert,
43 Update,
45 Explain,
47 Delete,
49 Prepare,
51 Create,
53 Execute,
55 Alter,
57 Transaction,
59 Copy,
61 Analyze,
63 VariableSet,
65 CreateFunc,
67 Drop,
69 Export,
71 Pragma,
73 Vacuum,
75 Call,
77 Set,
79 Load,
81 Relation,
83 Extension,
85 LogicalPlan,
87 Attach,
89 Detach,
91 Multi,
93 Unknown(duckdb_statement_type),
95}
96
97impl StatementType {
98 #[must_use]
100 pub fn from_raw(raw: duckdb_statement_type) -> StatementType {
101 use crate::ffi as f;
102 match raw {
103 f::duckdb_statement_type_DUCKDB_STATEMENT_TYPE_INVALID => StatementType::Invalid,
104 f::duckdb_statement_type_DUCKDB_STATEMENT_TYPE_SELECT => StatementType::Select,
105 f::duckdb_statement_type_DUCKDB_STATEMENT_TYPE_INSERT => StatementType::Insert,
106 f::duckdb_statement_type_DUCKDB_STATEMENT_TYPE_UPDATE => StatementType::Update,
107 f::duckdb_statement_type_DUCKDB_STATEMENT_TYPE_EXPLAIN => StatementType::Explain,
108 f::duckdb_statement_type_DUCKDB_STATEMENT_TYPE_DELETE => StatementType::Delete,
109 f::duckdb_statement_type_DUCKDB_STATEMENT_TYPE_PREPARE => StatementType::Prepare,
110 f::duckdb_statement_type_DUCKDB_STATEMENT_TYPE_CREATE => StatementType::Create,
111 f::duckdb_statement_type_DUCKDB_STATEMENT_TYPE_EXECUTE => StatementType::Execute,
112 f::duckdb_statement_type_DUCKDB_STATEMENT_TYPE_ALTER => StatementType::Alter,
113 f::duckdb_statement_type_DUCKDB_STATEMENT_TYPE_TRANSACTION => {
114 StatementType::Transaction
115 },
116 f::duckdb_statement_type_DUCKDB_STATEMENT_TYPE_COPY => StatementType::Copy,
117 f::duckdb_statement_type_DUCKDB_STATEMENT_TYPE_ANALYZE => StatementType::Analyze,
118 f::duckdb_statement_type_DUCKDB_STATEMENT_TYPE_VARIABLE_SET => {
119 StatementType::VariableSet
120 },
121 f::duckdb_statement_type_DUCKDB_STATEMENT_TYPE_CREATE_FUNC => StatementType::CreateFunc,
122 f::duckdb_statement_type_DUCKDB_STATEMENT_TYPE_DROP => StatementType::Drop,
123 f::duckdb_statement_type_DUCKDB_STATEMENT_TYPE_EXPORT => StatementType::Export,
124 f::duckdb_statement_type_DUCKDB_STATEMENT_TYPE_PRAGMA => StatementType::Pragma,
125 f::duckdb_statement_type_DUCKDB_STATEMENT_TYPE_VACUUM => StatementType::Vacuum,
126 f::duckdb_statement_type_DUCKDB_STATEMENT_TYPE_CALL => StatementType::Call,
127 f::duckdb_statement_type_DUCKDB_STATEMENT_TYPE_SET => StatementType::Set,
128 f::duckdb_statement_type_DUCKDB_STATEMENT_TYPE_LOAD => StatementType::Load,
129 f::duckdb_statement_type_DUCKDB_STATEMENT_TYPE_RELATION => StatementType::Relation,
130 f::duckdb_statement_type_DUCKDB_STATEMENT_TYPE_EXTENSION => StatementType::Extension,
131 f::duckdb_statement_type_DUCKDB_STATEMENT_TYPE_LOGICAL_PLAN => {
132 StatementType::LogicalPlan
133 },
134 f::duckdb_statement_type_DUCKDB_STATEMENT_TYPE_ATTACH => StatementType::Attach,
135 f::duckdb_statement_type_DUCKDB_STATEMENT_TYPE_DETACH => StatementType::Detach,
136 f::duckdb_statement_type_DUCKDB_STATEMENT_TYPE_MULTI => StatementType::Multi,
137 other => StatementType::Unknown(other),
138 }
139 }
140}
141
142mod meta {
151 use super::*;
152
153 pub(super) fn statement_type(stmt: duckdb_prepared_statement) -> StatementType {
155 StatementType::from_raw(unsafe { duckdb_prepared_statement_type(stmt) })
157 }
158
159 pub(super) fn param_count(stmt: duckdb_prepared_statement) -> u64 {
161 unsafe { duckdb_nparams(stmt) }
163 }
164
165 pub(super) fn parameter_name(
170 stmt: duckdb_prepared_statement,
171 idx: u64,
172 ) -> Option<String> {
173 unsafe { owned_c_string(duckdb_parameter_name(stmt, idx)) }
176 }
177
178 pub(super) fn param_type(
180 stmt: duckdb_prepared_statement,
181 idx: u64,
182 ) -> duckdb_type {
183 unsafe { duckdb_param_type(stmt, idx) }
185 }
186
187 pub(super) fn param_type_info(
189 stmt: duckdb_prepared_statement,
190 idx: u64,
191 ) -> Option<TypeInfo> {
192 LogicalType::from_raw(unsafe { duckdb_param_logical_type(stmt, idx) })
195 .ok()
196 .map(|lt| lt.describe())
197 }
198
199 pub(super) fn parameter_index(
207 stmt: duckdb_prepared_statement,
208 name: &str,
209 ) -> Result<u64> {
210 let c_name = CString::new(name)?;
211 let mut idx: crate::ffi::idx_t = 0;
212 let rc = unsafe { duckdb_bind_parameter_index(stmt, &mut idx, c_name.as_ptr()) };
215 if rc == DuckDBSuccess {
216 Ok(idx)
217 } else {
218 Err(Error::InvalidParameterName(name.to_owned()))
219 }
220 }
221
222 pub(super) fn column_count(stmt: duckdb_prepared_statement) -> u64 {
224 unsafe { duckdb_prepared_statement_column_count(stmt) }
226 }
227
228 pub(super) fn column_name(
230 stmt: duckdb_prepared_statement,
231 idx: u64,
232 ) -> Option<String> {
233 unsafe { owned_c_string(duckdb_prepared_statement_column_name(stmt, idx)) }
235 }
236
237 pub(super) fn column_type(
239 stmt: duckdb_prepared_statement,
240 idx: u64,
241 ) -> duckdb_type {
242 unsafe { duckdb_prepared_statement_column_type(stmt, idx) }
244 }
245
246 pub(super) fn column_type_info(
248 stmt: duckdb_prepared_statement,
249 idx: u64,
250 ) -> Option<TypeInfo> {
251 LogicalType::from_raw(unsafe { duckdb_prepared_statement_column_logical_type(stmt, idx) })
253 .ok()
254 .map(|lt| lt.describe())
255 }
256
257 unsafe fn owned_c_string(ptr: *const std::os::raw::c_char) -> Option<String> {
264 if ptr.is_null() {
265 return None;
266 }
267 let owned = unsafe { CStr::from_ptr(ptr) }.to_string_lossy().into_owned();
269 unsafe { duckdb_free(ptr as *mut std::os::raw::c_void) };
271 Some(owned)
272 }
273}
274
275pub struct Statement<'a> {
280 con: &'a RawConnection,
282 stmt: duckdb_prepared_statement,
284 bind_idx: u64,
286}
287
288impl Statement<'_> {
289 pub(super) fn new<'a, 'b: 'a>(
295 con: &'b RawConnection,
296 sql: &str,
297 ) -> Result<Statement<'a>> {
298 let mut stmt: duckdb_prepared_statement = ptr::null_mut();
299 let c_str = std::ffi::CString::new(sql)?;
300 let resp = unsafe { duckdb_prepare(con.handle(), c_str.as_ptr(), &mut stmt) };
305 result_from_duckdb_prepare(resp, stmt)?;
306 Ok(Statement { con, stmt, bind_idx: 0 })
307 }
308
309 #[allow(unused)]
311 #[inline]
312 fn raw(&self) -> &duckdb_prepared_statement {
313 &self.stmt
314 }
315
316 #[allow(unused)]
318 #[inline]
319 fn connection(&self) -> &RawConnection {
320 self.con
321 }
322}
323
324impl Statement<'_> {
326 #[must_use = "bind result should be checked"]
335 #[allow(unused)]
336 #[inline]
337 pub fn bind<T: AppendAble>(
338 &mut self,
339 binder: &mut T,
340 ) -> Result<()> {
341 self.bind_idx += 1;
342 self.bind_at(binder, self.bind_idx)
344 }
345
346 #[allow(unused)]
357 #[inline]
358 pub fn bind_at<T: AppendAble>(
359 &self,
360 binder: &mut T,
361 idx: u64,
362 ) -> Result<()> {
363 binder.stmt_append(idx, self.stmt)
364 }
365
366 #[must_use = "execute returns the query result; dropping it without reading discards rows"]
375 #[allow(unused)]
376 pub fn execute(&mut self) -> Result<DuckResult> {
377 let mut out = unsafe { mem::zeroed::<duckdb_result>() };
382 let resp = unsafe { duckdb_execute_prepared(self.stmt, &mut out as *mut duckdb_result) };
386 self.con.inner().advance_query();
389 result_from_duckdb_result(resp, &mut out as *mut duckdb_result)?;
390 Ok(DuckResult::new(out))
391 }
392
393 #[allow(unused)]
395 #[inline]
396 pub fn bind_parameter_count(&self) -> usize {
397 unsafe { duckdb_nparams(self.stmt) as usize }
399 }
400
401 #[must_use = "clear_bindings result should be checked"]
410 #[allow(unused)]
411 #[inline]
412 pub fn clear_bindings(&mut self) -> Result<()> {
413 let res = unsafe { duckdb_clear_bindings(self.stmt) };
415 if res != DuckDBSuccess {
416 Err(Error::DuckDBFailure(
417 crate::ffi::Error::new(crate::ffi::DuckDBError),
418 Some("Failed to clear bindings".to_owned()),
419 ))
420 } else {
421 self.bind_idx = 0;
422 Ok(())
423 }
424 }
425
426 #[allow(unused)]
428 #[inline]
429 pub fn is_null(&self) -> bool {
430 self.stmt.is_null()
431 }
432}
433
434impl Drop for Statement<'_> {
436 fn drop(&mut self) {
437 unsafe {
441 if !self.stmt.is_null() {
442 duckdb_destroy_prepare(&mut self.stmt);
443 }
444 }
445 }
446}
447
448pub struct CachedStatement {
462 _connection: Arc<ConnectionInner>,
472 #[allow(dead_code)]
480 pub(crate) sql: Box<str>,
481 stmt: ffi::duckdb_prepared_statement,
483}
484
485impl CachedStatement {
486 pub fn prepare(
495 conn: &RawConnection,
496 sql: impl AsRef<str>,
497 ) -> Result<Self> {
498 let sql_str = sql.as_ref();
499 let mut stmt: ffi::duckdb_prepared_statement = ptr::null_mut();
500 let c_str = CString::new(sql_str)?;
501 let r = unsafe { ffi::duckdb_prepare(conn.handle(), c_str.as_ptr(), &mut stmt) };
507 result_from_duckdb_prepare(r, stmt)?;
508 Ok(CachedStatement::from_prepared(Arc::clone(conn.inner()), stmt, sql_str.into()))
509 }
510
511 pub(crate) fn from_prepared(
518 connection: Arc<ConnectionInner>,
519 stmt: duckdb_prepared_statement,
520 sql: Box<str>,
521 ) -> CachedStatement {
522 CachedStatement { _connection: connection, sql, stmt }
523 }
524
525 #[inline]
528 pub(crate) fn handle(&self) -> duckdb_prepared_statement {
529 self.stmt
530 }
531
532 pub fn pending(&self) -> Result<crate::raw::pending::PendingResult<'_>> {
543 crate::raw::pending::PendingResult::new(self)
544 }
545
546 #[allow(dead_code)]
556 pub fn into_pending(self) -> Result<crate::raw::pending::OwnedPending> {
557 crate::raw::pending::OwnedPending::new(self)
558 }
559
560 pub fn reset_bindings(&mut self) -> Result<()> {
566 let r = unsafe { ffi::duckdb_clear_bindings(self.stmt) };
568 if r == ffi::DuckDBSuccess {
569 Ok(())
570 } else {
571 Err(Error::DuckDBFailure(ffi::Error::new(r), None))
572 }
573 }
574
575 pub fn bind<T: AppendAble + ?Sized>(
581 &mut self,
582 idx: u64,
583 value: &mut T,
584 ) -> Result<()> {
585 value.stmt_append(idx, self.stmt)
586 }
587
588 pub fn bind_named<T: AppendAble + ?Sized>(
598 &mut self,
599 name: &str,
600 value: &mut T,
601 ) -> Result<()> {
602 let idx = meta::parameter_index(self.stmt, name)?;
603 value.stmt_append(idx, self.stmt)
604 }
605
606 #[must_use]
608 pub fn statement_type(&self) -> StatementType {
609 meta::statement_type(self.stmt)
610 }
611
612 #[must_use]
614 pub fn parameter_count(&self) -> u64 {
615 meta::param_count(self.stmt)
616 }
617
618 #[must_use]
621 pub fn parameter_name(
622 &self,
623 index: u64,
624 ) -> Option<String> {
625 meta::parameter_name(self.stmt, index)
626 }
627
628 #[must_use]
630 pub fn parameter_type(
631 &self,
632 index: u64,
633 ) -> duckdb_type {
634 meta::param_type(self.stmt, index)
635 }
636
637 #[must_use]
639 pub fn parameter_logical_type(
640 &self,
641 index: u64,
642 ) -> Option<TypeInfo> {
643 meta::param_type_info(self.stmt, index)
644 }
645
646 pub fn parameter_index(
652 &self,
653 name: &str,
654 ) -> Result<u64> {
655 meta::parameter_index(self.stmt, name)
656 }
657
658 #[must_use]
660 pub fn column_count(&self) -> u64 {
661 meta::column_count(self.stmt)
662 }
663
664 #[must_use]
666 pub fn column_name(
667 &self,
668 index: u64,
669 ) -> Option<String> {
670 meta::column_name(self.stmt, index)
671 }
672
673 #[must_use]
675 pub fn column_type(
676 &self,
677 index: u64,
678 ) -> duckdb_type {
679 meta::column_type(self.stmt, index)
680 }
681
682 #[must_use]
684 pub fn column_logical_type(
685 &self,
686 index: u64,
687 ) -> Option<TypeInfo> {
688 meta::column_type_info(self.stmt, index)
689 }
690
691 #[must_use = "the DuckResult carries both affected-row count (.changes()) and row iterator — consume it"]
703 pub fn execute(&mut self) -> Result<DuckResult> {
704 let mut out = unsafe { mem::zeroed::<ffi::duckdb_result>() };
709 let r =
713 unsafe { ffi::duckdb_execute_prepared(self.stmt, &mut out as *mut ffi::duckdb_result) };
714 self._connection.advance_query();
717 result_from_duckdb_result(r, &mut out as *mut ffi::duckdb_result)?;
718 Ok(DuckResult::new(out))
719 }
720}
721
722impl Drop for CachedStatement {
723 fn drop(&mut self) {
724 if !self.stmt.is_null() {
725 unsafe { ffi::duckdb_destroy_prepare(&mut self.stmt) };
728 }
729 }
730}
731
732unsafe impl Send for CachedStatement {}
740
741#[cfg(test)]
742mod tests {
743 use super::*;
744 use crate::config::Config;
745 use crate::ffi::DUCKDB_TYPE_DUCKDB_TYPE_INTEGER;
746 use crate::helpers::path::path_to_cstring;
747 use crate::raw::connection::RawConnection;
748 use crate::types::{appendable::AppendAble, value::DuckValue};
749
750 struct CheckedI32(i32);
751 struct DummyAppendAble;
752
753 impl AppendAble for DummyAppendAble {
754 fn stmt_append(
755 &mut self,
756 _idx: u64,
757 _stmt: duckdb_prepared_statement,
758 ) -> Result<()> {
759 Ok(())
760 }
761
762 fn appender_append(
763 &mut self,
764 _appender: ffi::duckdb_appender,
765 ) -> Result<()> {
766 unreachable!("DummyAppendAble is only used for statement binding")
767 }
768 }
769
770 impl AppendAble for CheckedI32 {
771 fn stmt_append(
772 &mut self,
773 idx: u64,
774 stmt: duckdb_prepared_statement,
775 ) -> Result<()> {
776 let state = unsafe { ffi::duckdb_bind_int32(stmt, idx, self.0) };
778 if state == DuckDBSuccess {
779 Ok(())
780 } else {
781 Err(Error::DuckDBFailure(ffi::Error::new(state), None))
782 }
783 }
784
785 fn appender_append(
786 &mut self,
787 _appender: crate::ffi::duckdb_appender,
788 ) -> Result<()> {
789 unreachable!("CheckedI32 is only used for statement binding")
790 }
791 }
792
793 fn get_test_connection() -> RawConnection {
794 let c_path = path_to_cstring(":memory:".as_ref()).unwrap();
795 let config = Config::default().with("duckdb_api", "rust").unwrap();
796 RawConnection::open_with_flags(&c_path, config).unwrap()
797 }
798
799 fn assert_single_value(
800 mut result: DuckResult,
801 column: &str,
802 expected: DuckValue,
803 ) {
804 let row = result.next().expect("expected one row").unwrap();
805 assert_eq!(row.get(column), Some(&expected));
806 assert!(result.next().is_none());
807 }
808
809 #[test]
810 fn test_new() {
811 let con = get_test_connection();
812 let sql = "SELECT 1";
813 let stmt = Statement::new(&con, sql);
814 assert!(stmt.is_ok());
815 }
816
817 #[test]
818 fn test_prepare_rejects_invalid_sql_and_interior_nul() {
819 let con = get_test_connection();
820
821 assert!(matches!(Statement::new(&con, "SELEC 1"), Err(Error::DuckDBFailure(..))));
822 assert!(matches!(Statement::new(&con, "SELECT \0 1"), Err(Error::NulError(_))));
823 assert!(matches!(CachedStatement::prepare(&con, "SELEC 1"), Err(Error::DuckDBFailure(..))));
824 assert!(matches!(CachedStatement::prepare(&con, "SELECT \0 1"), Err(Error::NulError(_))));
825 }
826
827 #[test]
828 fn test_statement_binds_real_values_and_reports_parameter_errors() {
829 let con = get_test_connection();
830 let mut stmt = Statement::new(&con, "SELECT $1::INTEGER + $2::INTEGER AS total").unwrap();
831 assert_eq!(stmt.bind_parameter_count(), 2);
832
833 let mut first = CheckedI32(19);
834 let mut second = CheckedI32(23);
835 stmt.bind(&mut first).unwrap();
836 stmt.bind(&mut second).unwrap();
837 assert_single_value(stmt.execute().unwrap(), "total", DuckValue::Int(42));
838
839 let mut out_of_range = CheckedI32(99);
840 assert!(matches!(stmt.bind_at(&mut out_of_range, 3), Err(Error::DuckDBFailure(..))));
841 assert_single_value(stmt.execute().unwrap(), "total", DuckValue::Int(42));
842 }
843
844 #[test]
845 fn test_statement_clear_bindings_resets_and_reuses() {
846 let con = get_test_connection();
847 let mut stmt = Statement::new(&con, "SELECT $1::INTEGER AS value").unwrap();
848 let mut first = CheckedI32(7);
849 stmt.bind(&mut first).unwrap();
850 assert_single_value(stmt.execute().unwrap(), "value", DuckValue::Int(7));
851
852 stmt.clear_bindings().unwrap();
853 assert_eq!(stmt.bind_idx, 0);
854 let mut second = CheckedI32(11);
855 stmt.bind(&mut second).unwrap();
856 assert_single_value(stmt.execute().unwrap(), "value", DuckValue::Int(11));
857 }
858
859 #[test]
860 fn test_cached_statement_retains_sql_and_resets_for_reuse() {
861 let con = get_test_connection();
862 let sql = "SELECT $1::INTEGER AS value";
863 let mut stmt = CachedStatement::prepare(&con, sql).unwrap();
864 assert_eq!(stmt.sql.as_ref(), sql);
865
866 let mut first = CheckedI32(100);
867 stmt.bind(1, &mut first).unwrap();
868 assert_single_value(stmt.execute().unwrap(), "value", DuckValue::Int(100));
869
870 stmt.reset_bindings().unwrap();
871 let mut second = CheckedI32(200);
872 stmt.bind(1, &mut second).unwrap();
873 assert_single_value(stmt.execute().unwrap(), "value", DuckValue::Int(200));
874 }
875
876 #[test]
877 fn borrowing_pending_execution_runs_the_statement() {
878 let con = get_test_connection();
881 let stmt = CachedStatement::prepare(&con, "SELECT 1 AS v").unwrap();
882 let result = stmt.pending().unwrap().execute().unwrap();
883 assert_single_value(result, "v", DuckValue::Int(1));
884 }
885
886 #[test]
887 fn test_cached_statement_recovers_after_invalid_bind_position() {
888 let con = get_test_connection();
889 let mut stmt = CachedStatement::prepare(&con, "SELECT $1::INTEGER AS value").unwrap();
890 let mut invalid = CheckedI32(1);
891 assert!(matches!(stmt.bind(0, &mut invalid), Err(Error::DuckDBFailure(..))));
892
893 stmt.reset_bindings().unwrap();
894 let mut valid = CheckedI32(55);
895 stmt.bind(1, &mut valid).unwrap();
896 assert_single_value(stmt.execute().unwrap(), "value", DuckValue::Int(55));
897 }
898
899 #[test]
900 fn test_raw_and_connection() {
901 let con = get_test_connection();
902 let sql = "SELECT 1";
903 let stmt = Statement::new(&con, sql).unwrap();
904 let _raw = stmt.raw();
905 let _con = stmt.connection();
906 }
907
908 #[test]
909 fn test_execute() {
910 let con = get_test_connection();
911 let sql = "SELECT 1";
912 let mut stmt = Statement::new(&con, sql).unwrap();
913 let result = stmt.execute();
914 assert!(result.is_ok());
915 }
916
917 #[test]
918 fn test_execute_can_be_called_multiple_times() {
919 let con = get_test_connection();
920 let sql = "SELECT 1";
921 let mut stmt = Statement::new(&con, sql).unwrap();
922 assert!(stmt.execute().is_ok());
923 assert!(stmt.execute().is_ok());
924 }
925
926 #[test]
927 fn test_clear_bindings_resets_idx() {
928 let con = get_test_connection();
929 let sql = "SELECT $1";
930 let mut stmt = Statement::new(&con, sql).unwrap();
931 let mut dummy = DummyAppendAble;
932 stmt.bind(&mut dummy).unwrap();
933 assert_eq!(stmt.bind_idx, 1);
934 stmt.clear_bindings().unwrap();
935 assert_eq!(stmt.bind_idx, 0);
936 }
937
938 #[test]
939 fn statement_type_is_classified() {
940 let mut con = get_test_connection();
941 let select = CachedStatement::prepare(&con, "SELECT 1").unwrap();
942 assert_eq!(select.statement_type(), StatementType::Select);
943 drop(select);
944
945 con.query("CREATE TABLE t (id INTEGER)").unwrap();
946 let insert = CachedStatement::prepare(&con, "INSERT INTO t VALUES (1)").unwrap();
947 assert_eq!(insert.statement_type(), StatementType::Insert);
948 }
949
950 #[test]
951 fn prepared_parameter_metadata_names_types_and_index() {
952 let con = get_test_connection();
953 let stmt = CachedStatement::prepare(&con, "SELECT $id::INTEGER AS a, $label::VARCHAR AS b")
956 .unwrap();
957 assert_eq!(stmt.parameter_count(), 2);
958 assert_eq!(stmt.parameter_name(1).as_deref(), Some("id"));
960 assert_eq!(stmt.parameter_name(2).as_deref(), Some("label"));
961 assert_eq!(stmt.parameter_index("id").unwrap(), 1);
962 assert_eq!(stmt.parameter_index("label").unwrap(), 2);
963 assert_eq!(stmt.parameter_type(1), DUCKDB_TYPE_DUCKDB_TYPE_INTEGER);
964 assert_eq!(
965 stmt.parameter_logical_type(1),
966 Some(TypeInfo::Scalar(DUCKDB_TYPE_DUCKDB_TYPE_INTEGER))
967 );
968
969 assert!(
971 matches!(stmt.parameter_index("nope"), Err(Error::InvalidParameterName(n)) if n == "nope")
972 );
973 assert!(matches!(stmt.parameter_index("a\0b"), Err(Error::NulError(_))));
974 assert_eq!(stmt.parameter_name(999), None);
975 }
976
977 #[test]
978 fn prepared_output_column_schema() {
979 let con = get_test_connection();
980 let stmt = CachedStatement::prepare(
981 &con,
982 "SELECT 1::INTEGER AS id, CAST(1.5 AS DECIMAL(8,2)) AS amount",
983 )
984 .unwrap();
985 assert_eq!(stmt.column_count(), 2);
986 assert_eq!(stmt.column_name(0).as_deref(), Some("id"));
987 assert_eq!(stmt.column_name(1).as_deref(), Some("amount"));
988 assert_eq!(stmt.column_type(0), DUCKDB_TYPE_DUCKDB_TYPE_INTEGER);
989 assert_eq!(stmt.column_logical_type(1), Some(TypeInfo::Decimal { width: 8, scale: 2 }));
991 assert_eq!(stmt.column_name(999), None);
992 }
993
994 #[test]
995 fn bind_named_binds_by_parameter_name() {
996 let con = get_test_connection();
997 let mut stmt = CachedStatement::prepare(&con, "SELECT $value::INTEGER AS value").unwrap();
998 let mut v = CheckedI32(77);
999 stmt.bind_named("value", &mut v).unwrap();
1000 assert_single_value(stmt.execute().unwrap(), "value", DuckValue::Int(77));
1001
1002 let mut other = CheckedI32(1);
1004 assert!(matches!(
1005 stmt.bind_named("missing", &mut other),
1006 Err(Error::InvalidParameterName(n)) if n == "missing"
1007 ));
1008 }
1009}