1use std::ffi::{c_char, CStr, CString};
6
7use arrow::datatypes::SchemaRef;
8
9use super::connection::LaminarConnection;
10use super::error::{
11 clear_last_error, set_last_error, LAMINAR_ERR_INVALID_UTF8, LAMINAR_ERR_NULL_POINTER,
12 LAMINAR_OK,
13};
14use super::memory::take_ownership_string;
15
16#[repr(C)]
18pub struct LaminarSchema {
19 inner: SchemaRef,
20}
21
22impl LaminarSchema {
23 pub(crate) fn new(schema: SchemaRef) -> Self {
25 Self { inner: schema }
26 }
27
28 #[allow(dead_code)] pub(crate) fn schema(&self) -> &SchemaRef {
31 &self.inner
32 }
33}
34
35#[no_mangle]
53pub unsafe extern "C" fn laminar_get_schema(
54 conn: *mut LaminarConnection,
55 name: *const c_char,
56 out: *mut *mut LaminarSchema,
57) -> i32 {
58 clear_last_error();
59
60 if conn.is_null() || name.is_null() || out.is_null() {
61 return LAMINAR_ERR_NULL_POINTER;
62 }
63
64 let Ok(name_str) = (unsafe { CStr::from_ptr(name) }).to_str() else {
66 return LAMINAR_ERR_INVALID_UTF8;
67 };
68
69 let conn_ref = unsafe { &(*conn).inner };
71
72 match conn_ref.get_schema(name_str) {
73 Ok(schema) => {
74 let handle = Box::new(LaminarSchema::new(schema));
75 unsafe { *out = Box::into_raw(handle) };
77 LAMINAR_OK
78 }
79 Err(e) => {
80 let code = e.code();
81 set_last_error(e);
82 code
83 }
84 }
85}
86
87#[no_mangle]
105pub unsafe extern "C" fn laminar_list_sources(
106 conn: *mut LaminarConnection,
107 out: *mut *mut c_char,
108) -> i32 {
109 clear_last_error();
110
111 if conn.is_null() || out.is_null() {
112 return LAMINAR_ERR_NULL_POINTER;
113 }
114
115 let conn_ref = unsafe { &(*conn).inner };
117
118 let sources = conn_ref.list_sources();
119 let json = format!(
120 "[{}]",
121 sources
122 .iter()
123 .map(|s| format!("\"{s}\""))
124 .collect::<Vec<_>>()
125 .join(", ")
126 );
127
128 match CString::new(json) {
129 Ok(c_str) => {
130 unsafe { *out = take_ownership_string(c_str) };
132 LAMINAR_OK
133 }
134 Err(_) => LAMINAR_ERR_INVALID_UTF8,
135 }
136}
137
138#[no_mangle]
154pub unsafe extern "C" fn laminar_schema_num_fields(
155 schema: *mut LaminarSchema,
156 out: *mut usize,
157) -> i32 {
158 clear_last_error();
159
160 if schema.is_null() || out.is_null() {
161 return LAMINAR_ERR_NULL_POINTER;
162 }
163
164 unsafe {
166 *out = (*schema).inner.fields().len();
167 }
168 LAMINAR_OK
169}
170
171#[no_mangle]
189pub unsafe extern "C" fn laminar_schema_field_name(
190 schema: *mut LaminarSchema,
191 index: usize,
192 out: *mut *mut c_char,
193) -> i32 {
194 clear_last_error();
195
196 if schema.is_null() || out.is_null() {
197 return LAMINAR_ERR_NULL_POINTER;
198 }
199
200 let schema_ref = unsafe { &(*schema).inner };
202
203 if index >= schema_ref.fields().len() {
204 return LAMINAR_ERR_NULL_POINTER; }
206
207 let name = schema_ref.field(index).name();
208 match CString::new(name.as_str()) {
209 Ok(c_str) => {
210 unsafe { *out = take_ownership_string(c_str) };
212 LAMINAR_OK
213 }
214 Err(_) => LAMINAR_ERR_INVALID_UTF8,
215 }
216}
217
218#[no_mangle]
238pub unsafe extern "C" fn laminar_schema_field_type(
239 schema: *mut LaminarSchema,
240 index: usize,
241 out: *mut *mut c_char,
242) -> i32 {
243 clear_last_error();
244
245 if schema.is_null() || out.is_null() {
246 return LAMINAR_ERR_NULL_POINTER;
247 }
248
249 let schema_ref = unsafe { &(*schema).inner };
251
252 if index >= schema_ref.fields().len() {
253 return LAMINAR_ERR_NULL_POINTER; }
255
256 let data_type = schema_ref.field(index).data_type();
257 let type_str = format!("{data_type:?}");
258 match CString::new(type_str) {
259 Ok(c_str) => {
260 unsafe { *out = take_ownership_string(c_str) };
262 LAMINAR_OK
263 }
264 Err(_) => LAMINAR_ERR_INVALID_UTF8,
265 }
266}
267
268#[no_mangle]
278pub unsafe extern "C" fn laminar_schema_free(schema: *mut LaminarSchema) {
279 if !schema.is_null() {
280 drop(unsafe { Box::from_raw(schema) });
282 }
283}
284
285#[cfg(test)]
286#[allow(clippy::borrow_as_ptr)]
287mod tests {
288 use std::ptr;
289
290 use super::*;
291 use crate::ffi::connection::laminar_open;
292 use crate::ffi::memory::laminar_string_free;
293
294 #[test]
295 fn test_list_sources_empty() {
296 let mut conn: *mut LaminarConnection = ptr::null_mut();
297 let mut sources: *mut c_char = ptr::null_mut();
298
299 unsafe {
301 laminar_open(&mut conn);
302 let rc = laminar_list_sources(conn, &mut sources);
303 assert_eq!(rc, LAMINAR_OK);
304 assert!(!sources.is_null());
305
306 let sources_str = CStr::from_ptr(sources).to_str().unwrap();
307 assert_eq!(sources_str, "[]");
308
309 laminar_string_free(sources);
310 crate::ffi::connection::laminar_close(conn);
311 }
312 }
313
314 #[test]
315 fn test_get_schema() {
316 let mut conn: *mut LaminarConnection = ptr::null_mut();
317 let mut schema: *mut LaminarSchema = ptr::null_mut();
318
319 unsafe {
321 laminar_open(&mut conn);
322
323 let sql = b"CREATE SOURCE schema_ffi_test (id BIGINT, name VARCHAR)\0";
325 crate::ffi::connection::laminar_execute(conn, sql.as_ptr().cast(), ptr::null_mut());
326
327 let name = b"schema_ffi_test\0";
329 let rc = laminar_get_schema(conn, name.as_ptr().cast(), &mut schema);
330 assert_eq!(rc, LAMINAR_OK);
331 assert!(!schema.is_null());
332
333 let mut num_fields: usize = 0;
335 let rc = laminar_schema_num_fields(schema, &mut num_fields);
336 assert_eq!(rc, LAMINAR_OK);
337 assert_eq!(num_fields, 2);
338
339 let mut field_name: *mut c_char = ptr::null_mut();
341 laminar_schema_field_name(schema, 0, &mut field_name);
342 assert_eq!(CStr::from_ptr(field_name).to_str().unwrap(), "id");
343 laminar_string_free(field_name);
344
345 laminar_schema_field_name(schema, 1, &mut field_name);
346 assert_eq!(CStr::from_ptr(field_name).to_str().unwrap(), "name");
347 laminar_string_free(field_name);
348
349 laminar_schema_free(schema);
350 crate::ffi::connection::laminar_close(conn);
351 }
352 }
353
354 #[test]
355 fn test_schema_null_pointer() {
356 let mut num_fields: usize = 0;
357 let rc = unsafe { laminar_schema_num_fields(ptr::null_mut(), &mut num_fields) };
359 assert_eq!(rc, LAMINAR_ERR_NULL_POINTER);
360 }
361}