1use std::ffi::{c_char, CStr, CString};
4
5use arrow::datatypes::SchemaRef;
6
7use super::connection::LaminarConnection;
8use super::error::{
9 clear_last_error, set_last_error, LAMINAR_ERR_INVALID_UTF8, LAMINAR_ERR_NULL_POINTER,
10 LAMINAR_OK,
11};
12use super::memory::take_ownership_string;
13
14#[repr(C)]
16pub struct LaminarSchema {
17 inner: SchemaRef,
18}
19
20impl LaminarSchema {
21 pub(crate) fn new(schema: SchemaRef) -> Self {
22 Self { inner: schema }
23 }
24
25 #[allow(dead_code)] pub(crate) fn schema(&self) -> &SchemaRef {
27 &self.inner
28 }
29}
30
31#[no_mangle]
37pub unsafe extern "C" fn laminar_get_schema(
38 conn: *mut LaminarConnection,
39 name: *const c_char,
40 out: *mut *mut LaminarSchema,
41) -> i32 {
42 clear_last_error();
43
44 if conn.is_null() || name.is_null() || out.is_null() {
45 return LAMINAR_ERR_NULL_POINTER;
46 }
47
48 let Ok(name_str) = (unsafe { CStr::from_ptr(name) }).to_str() else {
49 return LAMINAR_ERR_INVALID_UTF8;
50 };
51
52 let conn_ref = unsafe { &(*conn).inner };
53
54 match conn_ref.get_schema(name_str) {
55 Ok(schema) => {
56 let handle = Box::new(LaminarSchema::new(schema));
57 unsafe { *out = Box::into_raw(handle) };
58 LAMINAR_OK
59 }
60 Err(e) => {
61 let code = e.code();
62 set_last_error(e);
63 code
64 }
65 }
66}
67
68#[no_mangle]
74pub unsafe extern "C" fn laminar_list_sources(
75 conn: *mut LaminarConnection,
76 out: *mut *mut c_char,
77) -> i32 {
78 clear_last_error();
79
80 if conn.is_null() || out.is_null() {
81 return LAMINAR_ERR_NULL_POINTER;
82 }
83
84 let conn_ref = unsafe { &(*conn).inner };
85
86 let sources = conn_ref.list_sources();
87 let json = format!(
88 "[{}]",
89 sources
90 .iter()
91 .map(|s| format!("\"{s}\""))
92 .collect::<Vec<_>>()
93 .join(", ")
94 );
95
96 match CString::new(json) {
97 Ok(c_str) => {
98 unsafe { *out = take_ownership_string(c_str) };
99 LAMINAR_OK
100 }
101 Err(_) => LAMINAR_ERR_INVALID_UTF8,
102 }
103}
104
105#[no_mangle]
111pub unsafe extern "C" fn laminar_schema_num_fields(
112 schema: *mut LaminarSchema,
113 out: *mut usize,
114) -> i32 {
115 clear_last_error();
116
117 if schema.is_null() || out.is_null() {
118 return LAMINAR_ERR_NULL_POINTER;
119 }
120
121 unsafe {
122 *out = (*schema).inner.fields().len();
123 }
124 LAMINAR_OK
125}
126
127#[no_mangle]
133pub unsafe extern "C" fn laminar_schema_field_name(
134 schema: *mut LaminarSchema,
135 index: usize,
136 out: *mut *mut c_char,
137) -> i32 {
138 clear_last_error();
139
140 if schema.is_null() || out.is_null() {
141 return LAMINAR_ERR_NULL_POINTER;
142 }
143
144 let schema_ref = unsafe { &(*schema).inner };
145
146 if index >= schema_ref.fields().len() {
147 return LAMINAR_ERR_NULL_POINTER;
148 }
149
150 let name = schema_ref.field(index).name();
151 match CString::new(name.as_str()) {
152 Ok(c_str) => {
153 unsafe { *out = take_ownership_string(c_str) };
154 LAMINAR_OK
155 }
156 Err(_) => LAMINAR_ERR_INVALID_UTF8,
157 }
158}
159
160#[no_mangle]
166pub unsafe extern "C" fn laminar_schema_field_type(
167 schema: *mut LaminarSchema,
168 index: usize,
169 out: *mut *mut c_char,
170) -> i32 {
171 clear_last_error();
172
173 if schema.is_null() || out.is_null() {
174 return LAMINAR_ERR_NULL_POINTER;
175 }
176
177 let schema_ref = unsafe { &(*schema).inner };
178
179 if index >= schema_ref.fields().len() {
180 return LAMINAR_ERR_NULL_POINTER;
181 }
182
183 let data_type = schema_ref.field(index).data_type();
184 let type_str = format!("{data_type:?}");
185 match CString::new(type_str) {
186 Ok(c_str) => {
187 unsafe { *out = take_ownership_string(c_str) };
188 LAMINAR_OK
189 }
190 Err(_) => LAMINAR_ERR_INVALID_UTF8,
191 }
192}
193
194#[no_mangle]
200pub unsafe extern "C" fn laminar_schema_free(schema: *mut LaminarSchema) {
201 if !schema.is_null() {
202 drop(unsafe { Box::from_raw(schema) });
203 }
204}
205
206#[cfg(test)]
207#[allow(clippy::borrow_as_ptr)]
208mod tests {
209 use std::ptr;
210
211 use super::*;
212 use crate::ffi::connection::laminar_open;
213 use crate::ffi::memory::laminar_string_free;
214
215 #[test]
216 fn test_list_sources_empty() {
217 let mut conn: *mut LaminarConnection = ptr::null_mut();
218 let mut sources: *mut c_char = ptr::null_mut();
219
220 unsafe {
222 laminar_open(&mut conn);
223 let rc = laminar_list_sources(conn, &mut sources);
224 assert_eq!(rc, LAMINAR_OK);
225 assert!(!sources.is_null());
226
227 let sources_str = CStr::from_ptr(sources).to_str().unwrap();
228 assert_eq!(sources_str, "[]");
229
230 laminar_string_free(sources);
231 crate::ffi::connection::laminar_close(conn);
232 }
233 }
234
235 #[test]
236 fn test_get_schema() {
237 let mut conn: *mut LaminarConnection = ptr::null_mut();
238 let mut schema: *mut LaminarSchema = ptr::null_mut();
239
240 unsafe {
242 laminar_open(&mut conn);
243
244 let sql = b"CREATE SOURCE schema_ffi_test (id BIGINT, name VARCHAR)\0";
246 crate::ffi::connection::laminar_execute(conn, sql.as_ptr().cast(), ptr::null_mut());
247
248 let name = b"schema_ffi_test\0";
250 let rc = laminar_get_schema(conn, name.as_ptr().cast(), &mut schema);
251 assert_eq!(rc, LAMINAR_OK);
252 assert!(!schema.is_null());
253
254 let mut num_fields: usize = 0;
256 let rc = laminar_schema_num_fields(schema, &mut num_fields);
257 assert_eq!(rc, LAMINAR_OK);
258 assert_eq!(num_fields, 2);
259
260 let mut field_name: *mut c_char = ptr::null_mut();
262 laminar_schema_field_name(schema, 0, &mut field_name);
263 assert_eq!(CStr::from_ptr(field_name).to_str().unwrap(), "id");
264 laminar_string_free(field_name);
265
266 laminar_schema_field_name(schema, 1, &mut field_name);
267 assert_eq!(CStr::from_ptr(field_name).to_str().unwrap(), "name");
268 laminar_string_free(field_name);
269
270 laminar_schema_free(schema);
271 crate::ffi::connection::laminar_close(conn);
272 }
273 }
274
275 #[test]
276 fn test_schema_null_pointer() {
277 let mut num_fields: usize = 0;
278 let rc = unsafe { laminar_schema_num_fields(ptr::null_mut(), &mut num_fields) };
280 assert_eq!(rc, LAMINAR_ERR_NULL_POINTER);
281 }
282}