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