Skip to main content

laminar_db/ffi/
connection.rs

1//! FFI connection functions.
2
3use std::ffi::{c_char, CStr};
4use std::ptr;
5
6use crate::api::Connection;
7
8use super::error::{
9    clear_last_error, set_last_error, LAMINAR_ERR_INVALID_UTF8, LAMINAR_ERR_NULL_POINTER,
10    LAMINAR_OK,
11};
12use super::query::{LaminarQueryResult, LaminarQueryStream};
13
14/// Opaque connection handle. Create with `laminar_open`, free with `laminar_close`.
15#[repr(C)]
16pub struct LaminarConnection {
17    pub(crate) inner: Connection,
18}
19
20/// Open a new database connection.
21///
22/// # Safety
23///
24/// `out` must be a valid pointer to a `*mut LaminarConnection`.
25#[no_mangle]
26pub unsafe extern "C" fn laminar_open(out: *mut *mut LaminarConnection) -> i32 {
27    clear_last_error();
28
29    if out.is_null() {
30        return LAMINAR_ERR_NULL_POINTER;
31    }
32
33    match Connection::open() {
34        Ok(conn) => {
35            let handle = Box::new(LaminarConnection { inner: conn });
36            unsafe { *out = Box::into_raw(handle) };
37            LAMINAR_OK
38        }
39        Err(e) => {
40            let code = e.code();
41            set_last_error(e);
42            code
43        }
44    }
45}
46
47/// Close a database connection and free all associated resources.
48///
49/// # Safety
50///
51/// `conn` must be a valid handle from `laminar_open` that has not been closed.
52#[no_mangle]
53pub unsafe extern "C" fn laminar_close(conn: *mut LaminarConnection) -> i32 {
54    clear_last_error();
55
56    if conn.is_null() {
57        return LAMINAR_ERR_NULL_POINTER;
58    }
59
60    let handle = unsafe { Box::from_raw(conn) };
61    match handle.inner.close() {
62        Ok(()) => LAMINAR_OK,
63        Err(e) => {
64            let code = e.code();
65            set_last_error(e);
66            code
67        }
68    }
69}
70
71/// Execute a SQL statement. For DDL, `out` may be NULL.
72///
73/// # Safety
74///
75/// `conn` and `sql` must be valid non-null pointers; `sql` must be null-terminated UTF-8;
76/// if `out` is non-null it must be a valid pointer.
77#[no_mangle]
78pub unsafe extern "C" fn laminar_execute(
79    conn: *mut LaminarConnection,
80    sql: *const c_char,
81    out: *mut *mut LaminarQueryResult,
82) -> i32 {
83    clear_last_error();
84
85    if conn.is_null() || sql.is_null() {
86        return LAMINAR_ERR_NULL_POINTER;
87    }
88
89    let Ok(sql_str) = (unsafe { CStr::from_ptr(sql) }).to_str() else {
90        return LAMINAR_ERR_INVALID_UTF8;
91    };
92
93    let conn_ref = unsafe { &(*conn).inner };
94
95    match conn_ref.execute(sql_str) {
96        Ok(result) => {
97            use crate::api::ExecuteResult;
98            match result {
99                ExecuteResult::Query(stream) => match stream.collect() {
100                    Ok(query_result) => {
101                        if !out.is_null() {
102                            let handle = Box::new(LaminarQueryResult::new(query_result));
103                            unsafe { *out = Box::into_raw(handle) };
104                        }
105                        LAMINAR_OK
106                    }
107                    Err(e) => {
108                        let code = e.code();
109                        set_last_error(e);
110                        code
111                    }
112                },
113                ExecuteResult::Metadata(batch) => {
114                    if !out.is_null() {
115                        let query_result = crate::api::QueryResult::from_batch(batch);
116                        let handle = Box::new(LaminarQueryResult::new(query_result));
117                        unsafe { *out = Box::into_raw(handle) };
118                    }
119                    LAMINAR_OK
120                }
121                ExecuteResult::Ddl(_) | ExecuteResult::RowsAffected(_) => {
122                    if !out.is_null() {
123                        unsafe { *out = ptr::null_mut() };
124                    }
125                    LAMINAR_OK
126                }
127            }
128        }
129        Err(e) => {
130            let code = e.code();
131            set_last_error(e);
132            code
133        }
134    }
135}
136
137/// Execute a query and collect all results into memory.
138///
139/// # Safety
140///
141/// `conn`, `sql`, and `out` must be valid non-null pointers; `sql` must be null-terminated UTF-8.
142#[no_mangle]
143pub unsafe extern "C" fn laminar_query(
144    conn: *mut LaminarConnection,
145    sql: *const c_char,
146    out: *mut *mut LaminarQueryResult,
147) -> i32 {
148    clear_last_error();
149
150    if conn.is_null() || sql.is_null() || out.is_null() {
151        return LAMINAR_ERR_NULL_POINTER;
152    }
153
154    let Ok(sql_str) = (unsafe { CStr::from_ptr(sql) }).to_str() else {
155        return LAMINAR_ERR_INVALID_UTF8;
156    };
157
158    let conn_ref = unsafe { &(*conn).inner };
159
160    match conn_ref.query(sql_str) {
161        Ok(result) => {
162            let handle = Box::new(LaminarQueryResult::new(result));
163            unsafe { *out = Box::into_raw(handle) };
164            LAMINAR_OK
165        }
166        Err(e) => {
167            let code = e.code();
168            set_last_error(e);
169            code
170        }
171    }
172}
173
174/// Execute a query with streaming results.
175///
176/// # Safety
177///
178/// `conn`, `sql`, and `out` must be valid non-null pointers; `sql` must be null-terminated UTF-8.
179#[no_mangle]
180pub unsafe extern "C" fn laminar_query_stream(
181    conn: *mut LaminarConnection,
182    sql: *const c_char,
183    out: *mut *mut LaminarQueryStream,
184) -> i32 {
185    clear_last_error();
186
187    if conn.is_null() || sql.is_null() || out.is_null() {
188        return LAMINAR_ERR_NULL_POINTER;
189    }
190
191    let Ok(sql_str) = (unsafe { CStr::from_ptr(sql) }).to_str() else {
192        return LAMINAR_ERR_INVALID_UTF8;
193    };
194
195    let conn_ref = unsafe { &(*conn).inner };
196
197    match conn_ref.query_stream(sql_str) {
198        Ok(stream) => {
199            let handle = Box::new(LaminarQueryStream::new(stream));
200            unsafe { *out = Box::into_raw(handle) };
201            LAMINAR_OK
202        }
203        Err(e) => {
204            let code = e.code();
205            set_last_error(e);
206            code
207        }
208    }
209}
210
211/// Start the streaming pipeline.
212///
213/// # Safety
214///
215/// `conn` must be a valid connection handle.
216#[no_mangle]
217pub unsafe extern "C" fn laminar_start(conn: *mut LaminarConnection) -> i32 {
218    clear_last_error();
219
220    if conn.is_null() {
221        return LAMINAR_ERR_NULL_POINTER;
222    }
223
224    let conn_ref = unsafe { &(*conn).inner };
225
226    match conn_ref.start() {
227        Ok(()) => LAMINAR_OK,
228        Err(e) => {
229            let code = e.code();
230            set_last_error(e);
231            code
232        }
233    }
234}
235
236/// Check if the connection is closed.
237///
238/// # Safety
239///
240/// `conn` and `out` must be valid non-null pointers.
241#[no_mangle]
242pub unsafe extern "C" fn laminar_is_closed(conn: *mut LaminarConnection, out: *mut bool) -> i32 {
243    clear_last_error();
244
245    if conn.is_null() || out.is_null() {
246        return LAMINAR_ERR_NULL_POINTER;
247    }
248
249    unsafe {
250        *out = (*conn).inner.is_closed();
251    }
252    LAMINAR_OK
253}
254
255#[cfg(test)]
256#[allow(clippy::borrow_as_ptr)]
257mod tests {
258    use super::*;
259
260    #[test]
261    fn test_open_close() {
262        let mut conn: *mut LaminarConnection = ptr::null_mut();
263        // SAFETY: Test code with valid pointer
264        let rc = unsafe { laminar_open(&mut conn) };
265        assert_eq!(rc, LAMINAR_OK);
266        assert!(!conn.is_null());
267
268        // SAFETY: conn is valid from laminar_open
269        let rc = unsafe { laminar_close(conn) };
270        assert_eq!(rc, LAMINAR_OK);
271    }
272
273    #[test]
274    fn test_open_null_pointer() {
275        // SAFETY: Testing null pointer handling
276        let rc = unsafe { laminar_open(ptr::null_mut()) };
277        assert_eq!(rc, LAMINAR_ERR_NULL_POINTER);
278    }
279
280    #[test]
281    fn test_close_null_pointer() {
282        // SAFETY: Testing null pointer handling
283        let rc = unsafe { laminar_close(ptr::null_mut()) };
284        assert_eq!(rc, LAMINAR_ERR_NULL_POINTER);
285    }
286
287    #[test]
288    fn test_execute_create_source() {
289        let mut conn: *mut LaminarConnection = ptr::null_mut();
290        // SAFETY: Test code with valid pointers
291        unsafe {
292            let rc = laminar_open(&mut conn);
293            assert_eq!(rc, LAMINAR_OK);
294
295            let sql = b"CREATE SOURCE ffi_test (id BIGINT, name VARCHAR)\0";
296            let rc = laminar_execute(conn, sql.as_ptr().cast(), ptr::null_mut());
297            assert_eq!(rc, LAMINAR_OK);
298
299            laminar_close(conn);
300        }
301    }
302
303    #[test]
304    fn test_is_closed() {
305        let mut conn: *mut LaminarConnection = ptr::null_mut();
306        let mut is_closed = true;
307
308        // SAFETY: Test code with valid pointers
309        unsafe {
310            laminar_open(&mut conn);
311            let rc = laminar_is_closed(conn, &mut is_closed);
312            assert_eq!(rc, LAMINAR_OK);
313            assert!(!is_closed);
314
315            laminar_close(conn);
316        }
317    }
318}