1#![allow(non_upper_case_globals)]
2#![allow(non_camel_case_types)]
3#![allow(non_snake_case)]
4#![allow(deref_nullptr)]
5#![allow(improper_ctypes)]
6
7mod arrow_c_data;
9pub use arrow_c_data::{ArrowArray, ArrowSchema};
10
11#[cfg(test)]
13mod build_paths;
14
15#[allow(clippy::all, unsafe_op_in_unsafe_fn)]
16mod bindings {
17 #![allow(
20 rustdoc::broken_intra_doc_links,
21 rustdoc::invalid_html_tags,
22 rustdoc::invalid_codeblock_attributes
23 )]
24
25 use crate::arrow_c_data::{ArrowArray, ArrowSchema};
27
28 include!(concat!(env!("OUT_DIR"), "/bindgen.rs"));
29}
30#[allow(clippy::all)]
31pub use bindings::*;
32
33mod string;
34pub use string::*;
35
36pub const DuckDBError: duckdb_state = duckdb_state_DuckDBError;
37pub const DuckDBSuccess: duckdb_state = duckdb_state_DuckDBSuccess;
38
39pub use self::error::*;
40mod error;
41
42#[cfg(test)]
43mod tests {
44 use super::*;
45 use std::{
46 ffi::{CStr, CString},
47 mem,
48 os::raw::c_char,
49 ptr,
50 sync::Arc,
51 };
52
53 use arrow::{
54 array::{Array, ArrayRef, Int32Array, StructArray},
55 datatypes::{DataType, Field},
56 ffi::{FFI_ArrowArray, FFI_ArrowSchema, from_ffi, to_ffi},
57 };
58
59 unsafe fn print_int_result(result: &mut duckdb_result) {
60 unsafe {
61 for i in 0..duckdb_column_count(result) {
62 print!("{} ", CStr::from_ptr(duckdb_column_name(result, i)).to_string_lossy());
63 }
64 println!();
65 for row_idx in 0..duckdb_row_count(result) {
67 for col_idx in 0..duckdb_column_count(result) {
68 let val = duckdb_value_int32(result, col_idx, row_idx);
69 print!("{val} ");
70 }
71 println!();
72 }
73 }
74 }
75
76 struct TestDb {
77 db: duckdb_database,
78 con: duckdb_connection,
79 }
80
81 impl TestDb {
82 unsafe fn open() -> Self {
83 let mut db: duckdb_database = ptr::null_mut();
84 let mut con: duckdb_connection = ptr::null_mut();
85 unsafe {
86 if duckdb_open(ptr::null_mut(), &mut db) != duckdb_state_DuckDBSuccess {
87 panic!("duckdb_open error")
88 }
89 if duckdb_connect(db, &mut con) != duckdb_state_DuckDBSuccess {
90 duckdb_close(&mut db);
91 panic!("duckdb_connect error")
92 }
93 }
94 Self { db, con }
95 }
96
97 fn connection(&self) -> duckdb_connection {
98 self.con
99 }
100
101 unsafe fn query_result(&self, sql: &str) -> duckdb_result {
102 unsafe {
103 let sql = CString::new(sql).unwrap();
104 let mut result: duckdb_result = mem::zeroed();
105 if duckdb_query(self.con, sql.as_ptr() as *const c_char, &mut result) != duckdb_state_DuckDBSuccess {
106 let message = result_error_message(&mut result).unwrap_or_else(|| "unknown error".to_string());
107 duckdb_destroy_result(&mut result);
108 panic!("DuckDB query failed: {message}");
109 }
110 result
111 }
112 }
113
114 unsafe fn create_integers_table(&self) {
115 unsafe {
116 let mut create_result = self.query_result("CREATE TABLE integers(i INTEGER, j INTEGER);");
117 duckdb_destroy_result(&mut create_result);
118
119 let mut insert_result = self.query_result("INSERT INTO integers VALUES (3, 4), (5, 6), (7, NULL);");
120 assert_eq!(duckdb_rows_changed(&mut insert_result), 3);
121 duckdb_destroy_result(&mut insert_result);
122 }
123 }
124 }
125
126 impl Drop for TestDb {
127 fn drop(&mut self) {
128 unsafe {
129 duckdb_disconnect(&mut self.con);
130 duckdb_close(&mut self.db);
131 }
132 }
133 }
134
135 unsafe fn result_error_message(result: &mut duckdb_result) -> Option<String> {
136 unsafe {
137 let message = duckdb_result_error(result);
138 if message.is_null() {
139 None
140 } else {
141 Some(CStr::from_ptr(message).to_string_lossy().into_owned())
142 }
143 }
144 }
145
146 unsafe fn assert_no_arrow_error(error_data: duckdb_error_data) {
147 unsafe {
148 if let Some((error_type, message)) = take_arrow_error(error_data) {
149 panic!("DuckDB Arrow C Data Interface conversion failed with type {error_type}: {message}");
150 }
151 }
152 }
153
154 unsafe fn take_arrow_error(mut error_data: duckdb_error_data) -> Option<(duckdb_error_type, String)> {
155 unsafe {
156 if error_data.is_null() {
157 return None;
158 }
159
160 if !duckdb_error_data_has_error(error_data) {
161 duckdb_destroy_error_data(&mut error_data);
162 return None;
163 }
164
165 let error_type = duckdb_error_data_error_type(error_data);
166 let message = duckdb_error_data_message(error_data);
167 let message = if message.is_null() {
168 "<no error message>".to_string()
169 } else {
170 CStr::from_ptr(message).to_string_lossy().into_owned()
171 };
172 duckdb_destroy_error_data(&mut error_data);
173 Some((error_type, message))
174 }
175 }
176
177 #[test]
178 fn test_arrow_c_data_interface_to_data_chunk() {
179 unsafe {
180 let db = TestDb::open();
181 let con = db.connection();
182 let vector_size = duckdb_vector_size() as usize;
183 let row_count = vector_size + 3;
184 let ids: Vec<i32> = (0..row_count as i32).collect();
185 let scores: Vec<Option<i32>> = (0..row_count)
186 .map(|i| if i == vector_size { None } else { Some(i as i32 * 2) })
187 .collect();
188
189 let struct_array = StructArray::from(vec![
190 (
191 Arc::new(Field::new("id", DataType::Int32, false)),
192 Arc::new(Int32Array::from(ids)) as ArrayRef,
193 ),
194 (
195 Arc::new(Field::new("score", DataType::Int32, true)),
196 Arc::new(Int32Array::from(scores)) as ArrayRef,
197 ),
198 ]);
199 let (mut arrow_array, mut arrow_schema) = to_ffi(&struct_array.to_data()).expect("export Arrow array");
200
201 let mut converted_schema: duckdb_arrow_converted_schema = ptr::null_mut();
202 assert_no_arrow_error(duckdb_schema_from_arrow(
203 con,
204 &mut arrow_schema as *mut FFI_ArrowSchema as *mut ArrowSchema,
205 &mut converted_schema,
206 ));
207 assert!(!converted_schema.is_null());
208
209 let mut chunk: duckdb_data_chunk = ptr::null_mut();
210 assert_no_arrow_error(duckdb_data_chunk_from_arrow(
211 con,
212 &mut arrow_array as *mut FFI_ArrowArray as *mut ArrowArray,
213 converted_schema,
214 &mut chunk,
215 ));
216 assert!(!chunk.is_null());
217
218 assert_eq!(duckdb_data_chunk_get_column_count(chunk), 2);
219 assert_eq!(duckdb_data_chunk_get_size(chunk), row_count as idx_t);
220
221 let id_vector = duckdb_data_chunk_get_vector(chunk, 0);
222 let id_data = duckdb_vector_get_data(id_vector) as *const i32;
223 assert_eq!(*id_data, 0);
224 assert_eq!(*id_data.add(vector_size), vector_size as i32);
225 assert_eq!(*id_data.add(row_count - 1), row_count as i32 - 1);
226
227 let score_vector = duckdb_data_chunk_get_vector(chunk, 1);
228 let score_data = duckdb_vector_get_data(score_vector) as *const i32;
229 assert_eq!(*score_data, 0);
230 assert_eq!(*score_data.add(row_count - 1), (row_count as i32 - 1) * 2);
231
232 let score_validity = duckdb_vector_get_validity(score_vector);
233 assert!(!score_validity.is_null());
234 assert!(duckdb_validity_row_is_valid(score_validity, 0));
235 assert!(!duckdb_validity_row_is_valid(score_validity, vector_size as idx_t));
236 assert!(duckdb_validity_row_is_valid(score_validity, vector_size as idx_t + 1));
237
238 duckdb_destroy_data_chunk(&mut chunk);
239 duckdb_destroy_arrow_converted_schema(&mut converted_schema);
240 }
241 }
242
243 unsafe extern "C" fn noop_arrow_schema_release(_schema: *mut ArrowSchema) {}
244
245 #[test]
246 fn test_arrow_c_data_interface_reports_schema_errors() {
247 unsafe {
248 let db = TestDb::open();
249 let con = db.connection();
250
251 let root_format = CString::new("+s").unwrap();
252 let child_format = CString::new("?").unwrap();
253 let child_name = CString::new("unsupported").unwrap();
254 let mut child_schema = ArrowSchema::empty();
255 child_schema.format = child_format.as_ptr();
256 child_schema.name = child_name.as_ptr();
257 child_schema.release = Some(noop_arrow_schema_release);
258
259 let mut child_schema_ptr = &mut child_schema as *mut ArrowSchema;
260 let mut root_schema = ArrowSchema::empty();
261 root_schema.format = root_format.as_ptr();
262 root_schema.n_children = 1;
263 root_schema.children = &mut child_schema_ptr;
264 root_schema.release = Some(noop_arrow_schema_release);
265
266 let mut converted_schema: duckdb_arrow_converted_schema = ptr::null_mut();
267 let (error_type, message) =
268 take_arrow_error(duckdb_schema_from_arrow(con, &mut root_schema, &mut converted_schema))
269 .expect("expected DuckDB Arrow C Data Interface conversion to fail");
270 assert_eq!(error_type, duckdb_error_type_DUCKDB_ERROR_INVALID_INPUT);
271 assert!(message.contains("Unsupported Internal Arrow Type"));
272 assert!(converted_schema.is_null());
273 }
274 }
275
276 #[test]
277 fn test_result_data_chunk_to_arrow() {
278 unsafe {
279 let db = TestDb::open();
280 db.create_integers_table();
281
282 let mut result = db.query_result("select i, j from integers order by i desc");
284 assert_eq!(duckdb_column_count(&mut result), 2);
285
286 let mut schema = FFI_ArrowSchema::empty();
287 let mut arrow_options = duckdb_result_get_arrow_options(&mut result);
288 assert!(!arrow_options.is_null());
289
290 let mut column_types = Vec::new();
291 let mut column_names = Vec::new();
292 for column in 0..duckdb_column_count(&mut result) {
293 let logical_type = duckdb_column_logical_type(&mut result, column);
294 assert!(!logical_type.is_null());
295 column_types.push(logical_type);
296
297 let name = duckdb_column_name(&mut result, column);
298 assert!(!name.is_null());
299 column_names.push(name);
300 }
301
302 assert_no_arrow_error(duckdb_to_arrow_schema(
303 arrow_options,
304 column_types.as_mut_ptr(),
305 column_names.as_mut_ptr(),
306 column_types.len() as idx_t,
307 &mut schema as *mut FFI_ArrowSchema as *mut ArrowSchema,
308 ));
309
310 let mut chunks = Vec::new();
311 loop {
312 let mut chunk = duckdb_fetch_chunk(result);
313 if chunk.is_null() {
314 break;
315 }
316
317 let mut arrays = FFI_ArrowArray::empty();
318 assert_no_arrow_error(duckdb_data_chunk_to_arrow(
319 arrow_options,
320 chunk,
321 &mut arrays as *mut FFI_ArrowArray as *mut ArrowArray,
322 ));
323 duckdb_destroy_data_chunk(&mut chunk);
324
325 let array_data = from_ffi(arrays, &schema).expect("ok");
326 chunks.push(StructArray::from(array_data));
327 }
328 if let Some(message) = result_error_message(&mut result) {
329 panic!("DuckDB result error while fetching chunks: {message}");
330 }
331
332 assert_eq!(chunks.len(), 1);
333 let struct_array = &chunks[0];
334 assert_eq!(struct_array.len(), 3);
335 assert_eq!(struct_array.columns().len(), 2);
336 assert_eq!(struct_array.column(0).data_type(), &DataType::Int32);
337 assert_eq!(struct_array.column(1).data_type(), &DataType::Int32);
338 let arr_i = struct_array.column(0).as_any().downcast_ref::<Int32Array>().unwrap();
339 assert_eq!(arr_i.value(0), 7);
340 assert_eq!(arr_i.value(1), 5);
341 assert_eq!(arr_i.value(2), 3);
342 let arr_j = struct_array.column(1).as_any().downcast_ref::<Int32Array>().unwrap();
343 assert!(arr_j.is_null(0));
344 assert_eq!(arr_j.value(1), 6);
345 assert_eq!(arr_j.value(2), 4);
346
347 for logical_type in &mut column_types {
348 duckdb_destroy_logical_type(logical_type);
349 }
350 duckdb_destroy_arrow_options(&mut arrow_options);
351 duckdb_destroy_result(&mut result);
352 }
353 }
354
355 #[test]
356 fn basic_api_usage() {
357 unsafe {
358 let db = TestDb::open();
359 let con = db.connection();
360 db.create_integers_table();
361
362 let mut result = db.query_result("select * from integers");
364 assert_eq!(duckdb_row_count(&mut result), 3);
365 assert_eq!(duckdb_column_count(&mut result), 2);
366 print_int_result(&mut result);
367 duckdb_destroy_result(&mut result);
368
369 let mut stmt: duckdb_prepared_statement = ptr::null_mut();
371 let sql = CString::new("select * from integers where i>?").unwrap();
372 if duckdb_prepare(con, sql.as_ptr() as *const c_char, &mut stmt) != duckdb_state_DuckDBSuccess {
373 panic!("Prepare error");
374 }
375 if duckdb_bind_int32(stmt, 1, 4) != duckdb_state_DuckDBSuccess {
376 panic!("Bind params error");
377 }
378 if duckdb_execute_prepared(stmt, &mut result) != duckdb_state_DuckDBSuccess {
379 panic!("Execute prepared error");
380 }
381 assert_eq!(duckdb_row_count(&mut result), 2);
382 assert_eq!(duckdb_column_count(&mut result), 2);
383 print_int_result(&mut result);
384 duckdb_destroy_result(&mut result);
385
386 if duckdb_bind_int32(stmt, 1, 5) != duckdb_state_DuckDBSuccess {
388 panic!("Bind params error");
389 }
390 if duckdb_execute_prepared(stmt, &mut result) != duckdb_state_DuckDBSuccess {
391 panic!("Execute prepared error");
392 }
393 assert_eq!(duckdb_row_count(&mut result), 1);
394 assert_eq!(duckdb_column_count(&mut result), 2);
395 print_int_result(&mut result);
396 duckdb_destroy_result(&mut result);
397 duckdb_destroy_prepare(&mut stmt);
398 }
399 }
400}