1use std::ffi::{c_char, c_void, CStr, CString};
4use std::sync::atomic::{AtomicBool, Ordering};
5use std::sync::Arc;
6use std::thread::{self, JoinHandle};
7
8use super::connection::LaminarConnection;
9use super::error::{
10 clear_last_error, set_last_error, LAMINAR_ERR_INVALID_UTF8, LAMINAR_ERR_NULL_POINTER,
11 LAMINAR_OK,
12};
13use super::query::LaminarRecordBatch;
14
15pub const LAMINAR_EVENT_INSERT: i32 = 0;
17pub const LAMINAR_EVENT_DELETE: i32 = 1;
19pub const LAMINAR_EVENT_UPDATE: i32 = 2;
21pub const LAMINAR_EVENT_WATERMARK: i32 = 3;
23pub const LAMINAR_EVENT_SNAPSHOT: i32 = 4;
25
26pub type LaminarSubscriptionCallback = Option<
32 unsafe extern "C" fn(user_data: *mut c_void, batch: *mut LaminarRecordBatch, event_type: i32),
33>;
34
35pub type LaminarErrorCallback = Option<
37 unsafe extern "C" fn(user_data: *mut c_void, error_code: i32, error_message: *const c_char),
38>;
39
40#[repr(C)]
44pub struct LaminarSubscriptionHandle {
45 cancelled: Arc<AtomicBool>,
46 thread_handle: Option<JoinHandle<()>>,
47 user_data: *mut c_void,
49}
50
51unsafe impl Send for LaminarSubscriptionHandle {}
53
54impl LaminarSubscriptionHandle {
55 fn new(
56 cancelled: Arc<AtomicBool>,
57 thread_handle: JoinHandle<()>,
58 user_data: *mut c_void,
59 ) -> Self {
60 Self {
61 cancelled,
62 thread_handle: Some(thread_handle),
63 user_data,
64 }
65 }
66
67 fn cancel(&mut self) {
68 self.cancelled.store(true, Ordering::SeqCst);
69 if let Some(handle) = self.thread_handle.take() {
70 let _ = handle.join();
71 }
72 }
73}
74
75struct CallbackContext {
76 user_data: *mut c_void,
77 on_data: LaminarSubscriptionCallback,
78 on_error: LaminarErrorCallback,
79 cancelled: Arc<AtomicBool>,
80}
81
82unsafe impl Send for CallbackContext {}
84
85impl CallbackContext {
86 fn call_on_data(&self, batch: LaminarRecordBatch, event_type: i32) {
87 if let Some(callback) = self.on_data {
88 let batch_ptr = Box::into_raw(Box::new(batch));
89 unsafe { callback(self.user_data, batch_ptr, event_type) };
90 }
91 }
92
93 fn call_on_error(&self, error_code: i32, message: &str) {
94 if let Some(callback) = self.on_error {
95 let c_message = CString::new(message)
96 .unwrap_or_else(|_| CString::new("Error message contained null byte").unwrap());
97 unsafe { callback(self.user_data, error_code, c_message.as_ptr()) };
98 }
99 }
100
101 fn is_cancelled(&self) -> bool {
102 self.cancelled.load(Ordering::SeqCst)
103 }
104}
105
106#[no_mangle]
113pub unsafe extern "C" fn laminar_subscribe_callback(
114 conn: *mut LaminarConnection,
115 query: *const c_char,
116 on_data: LaminarSubscriptionCallback,
117 on_error: LaminarErrorCallback,
118 user_data: *mut c_void,
119 out: *mut *mut LaminarSubscriptionHandle,
120) -> i32 {
121 clear_last_error();
122
123 if conn.is_null() || query.is_null() || out.is_null() {
124 return LAMINAR_ERR_NULL_POINTER;
125 }
126
127 let Ok(query_str) = (unsafe { CStr::from_ptr(query) }).to_str() else {
128 return LAMINAR_ERR_INVALID_UTF8;
129 };
130
131 let conn_ref = unsafe { &(*conn).inner };
132
133 let stream = match conn_ref.query_stream(query_str) {
134 Ok(s) => s,
135 Err(e) => {
136 let code = e.code();
137 set_last_error(e);
138 return code;
139 }
140 };
141
142 let cancelled = Arc::new(AtomicBool::new(false));
143 let cancelled_clone = Arc::clone(&cancelled);
144
145 let ctx = CallbackContext {
146 user_data,
147 on_data,
148 on_error,
149 cancelled: cancelled_clone,
150 };
151
152 let thread_handle = thread::spawn(move || {
153 subscription_thread(stream, ctx);
154 });
155
156 let handle = Box::new(LaminarSubscriptionHandle::new(
157 cancelled,
158 thread_handle,
159 user_data,
160 ));
161
162 unsafe { *out = Box::into_raw(handle) };
163
164 LAMINAR_OK
165}
166
167#[allow(clippy::needless_pass_by_value)]
168fn subscription_thread(mut stream: crate::api::QueryStream, ctx: CallbackContext) {
169 loop {
170 if ctx.is_cancelled() {
171 break;
172 }
173
174 match stream.try_next() {
175 Ok(Some(batch)) => {
176 ctx.call_on_data(LaminarRecordBatch::new(batch), LAMINAR_EVENT_INSERT);
177 }
178 Ok(None) => {
179 if !stream.is_active() {
180 break;
181 }
182 std::thread::sleep(std::time::Duration::from_millis(1));
184 }
185 Err(e) => {
186 ctx.call_on_error(e.code(), e.message());
187 if !stream.is_active() {
188 break;
189 }
190 }
191 }
192 }
193}
194
195#[no_mangle]
201pub unsafe extern "C" fn laminar_subscription_cancel(
202 handle: *mut LaminarSubscriptionHandle,
203) -> i32 {
204 clear_last_error();
205
206 if handle.is_null() {
207 return LAMINAR_ERR_NULL_POINTER;
208 }
209
210 let handle_ref = unsafe { &mut *handle };
211 handle_ref.cancel();
212
213 LAMINAR_OK
214}
215
216#[no_mangle]
222pub unsafe extern "C" fn laminar_subscription_is_active(
223 handle: *mut LaminarSubscriptionHandle,
224 out: *mut bool,
225) -> i32 {
226 clear_last_error();
227
228 if handle.is_null() || out.is_null() {
229 return LAMINAR_ERR_NULL_POINTER;
230 }
231
232 let handle_ref = unsafe { &*handle };
233 let active = !handle_ref.cancelled.load(Ordering::SeqCst) && handle_ref.thread_handle.is_some();
234
235 unsafe { *out = active };
236
237 LAMINAR_OK
238}
239
240#[no_mangle]
246pub unsafe extern "C" fn laminar_subscription_user_data(
247 handle: *mut LaminarSubscriptionHandle,
248) -> *mut c_void {
249 if handle.is_null() {
250 return std::ptr::null_mut();
251 }
252
253 unsafe { (*handle).user_data }
254}
255
256#[no_mangle]
262pub unsafe extern "C" fn laminar_subscription_free(handle: *mut LaminarSubscriptionHandle) {
263 if !handle.is_null() {
264 let mut boxed = unsafe { Box::from_raw(handle) };
265 boxed.cancel();
266 drop(boxed);
267 }
268}
269
270#[cfg(test)]
271#[allow(
272 clippy::borrow_as_ptr,
273 clippy::manual_c_str_literals,
274 clippy::items_after_statements
275)]
276mod tests {
277 use super::*;
278 use crate::ffi::connection::{laminar_close, laminar_execute, laminar_open};
279 use std::ptr;
280 use std::sync::atomic::AtomicUsize;
281
282 #[test]
283 fn test_event_type_constants() {
284 assert_eq!(LAMINAR_EVENT_INSERT, 0);
285 assert_eq!(LAMINAR_EVENT_DELETE, 1);
286 assert_eq!(LAMINAR_EVENT_UPDATE, 2);
287 assert_eq!(LAMINAR_EVENT_WATERMARK, 3);
288 assert_eq!(LAMINAR_EVENT_SNAPSHOT, 4);
289 }
290
291 #[test]
292 fn test_subscribe_null_pointer() {
293 let mut out: *mut LaminarSubscriptionHandle = ptr::null_mut();
294
295 let rc = unsafe {
297 laminar_subscribe_callback(
298 ptr::null_mut(),
299 b"SELECT 1\0".as_ptr().cast(),
300 None,
301 None,
302 ptr::null_mut(),
303 &mut out,
304 )
305 };
306 assert_eq!(rc, LAMINAR_ERR_NULL_POINTER);
307
308 let mut conn: *mut LaminarConnection = ptr::null_mut();
310 unsafe { laminar_open(&mut conn) };
311
312 let rc = unsafe {
313 laminar_subscribe_callback(conn, ptr::null(), None, None, ptr::null_mut(), &mut out)
314 };
315 assert_eq!(rc, LAMINAR_ERR_NULL_POINTER);
316
317 let rc = unsafe {
319 laminar_subscribe_callback(
320 conn,
321 b"SELECT 1\0".as_ptr().cast(),
322 None,
323 None,
324 ptr::null_mut(),
325 ptr::null_mut(),
326 )
327 };
328 assert_eq!(rc, LAMINAR_ERR_NULL_POINTER);
329
330 unsafe { laminar_close(conn) };
331 }
332
333 #[test]
334 fn test_subscription_cancel_null() {
335 let rc = unsafe { laminar_subscription_cancel(ptr::null_mut()) };
336 assert_eq!(rc, LAMINAR_ERR_NULL_POINTER);
337 }
338
339 #[test]
340 fn test_subscription_free_null() {
341 unsafe { laminar_subscription_free(ptr::null_mut()) };
343 }
344
345 #[test]
346 fn test_subscription_user_data_null() {
347 let result = unsafe { laminar_subscription_user_data(ptr::null_mut()) };
348 assert!(result.is_null());
349 }
350
351 #[test]
352 fn test_subscribe_and_cancel() {
353 let mut conn: *mut LaminarConnection = ptr::null_mut();
354 let mut sub: *mut LaminarSubscriptionHandle = ptr::null_mut();
355
356 unsafe {
357 laminar_open(&mut conn);
358
359 let sql = b"CREATE TABLE callback_test (id BIGINT PRIMARY KEY)\0";
361 laminar_execute(conn, sql.as_ptr().cast(), ptr::null_mut());
362
363 let query = b"SELECT * FROM callback_test\0";
365 let rc = laminar_subscribe_callback(
366 conn,
367 query.as_ptr().cast(),
368 None,
369 None,
370 ptr::null_mut(),
371 &mut sub,
372 );
373 assert_eq!(rc, LAMINAR_OK);
374 assert!(!sub.is_null());
375
376 let mut active = false;
378 let rc = laminar_subscription_is_active(sub, &mut active);
379 assert_eq!(rc, LAMINAR_OK);
380 let rc = laminar_subscription_cancel(sub);
384 assert_eq!(rc, LAMINAR_OK);
385
386 let rc = laminar_subscription_is_active(sub, &mut active);
388 assert_eq!(rc, LAMINAR_OK);
389 assert!(!active);
390
391 laminar_subscription_free(sub);
392 laminar_close(conn);
393 }
394 }
395
396 #[test]
397 fn test_subscribe_with_user_data() {
398 let mut conn: *mut LaminarConnection = ptr::null_mut();
399 let mut sub: *mut LaminarSubscriptionHandle = ptr::null_mut();
400
401 static COUNTER: AtomicUsize = AtomicUsize::new(42);
403
404 unsafe {
405 laminar_open(&mut conn);
406
407 let sql = b"CREATE TABLE userdata_test (id BIGINT PRIMARY KEY)\0";
408 laminar_execute(conn, sql.as_ptr().cast(), ptr::null_mut());
409
410 let query = b"SELECT * FROM userdata_test\0";
411 let user_data = std::ptr::addr_of!(COUNTER) as *mut c_void;
412
413 let rc = laminar_subscribe_callback(
414 conn,
415 query.as_ptr().cast(),
416 None,
417 None,
418 user_data,
419 &mut sub,
420 );
421 assert_eq!(rc, LAMINAR_OK);
422
423 let retrieved = laminar_subscription_user_data(sub);
425 assert_eq!(retrieved, user_data);
426
427 laminar_subscription_cancel(sub);
428 laminar_subscription_free(sub);
429 laminar_close(conn);
430 }
431 }
432
433 static DATA_CALLBACK_COUNT: AtomicUsize = AtomicUsize::new(0);
435 static ERROR_CALLBACK_COUNT: AtomicUsize = AtomicUsize::new(0);
436
437 unsafe extern "C" fn test_data_callback(
438 _user_data: *mut c_void,
439 batch: *mut LaminarRecordBatch,
440 _event_type: i32,
441 ) {
442 DATA_CALLBACK_COUNT.fetch_add(1, Ordering::SeqCst);
443 if !batch.is_null() {
445 crate::ffi::query::laminar_batch_free(batch);
446 }
447 }
448
449 unsafe extern "C" fn test_error_callback(
450 _user_data: *mut c_void,
451 _error_code: i32,
452 _error_message: *const c_char,
453 ) {
454 ERROR_CALLBACK_COUNT.fetch_add(1, Ordering::SeqCst);
455 }
456
457 #[test]
458 fn test_subscribe_with_callbacks() {
459 DATA_CALLBACK_COUNT.store(0, Ordering::SeqCst);
461 ERROR_CALLBACK_COUNT.store(0, Ordering::SeqCst);
462
463 let mut conn: *mut LaminarConnection = ptr::null_mut();
464 let mut sub: *mut LaminarSubscriptionHandle = ptr::null_mut();
465
466 unsafe {
467 laminar_open(&mut conn);
468
469 let sql = b"CREATE TABLE callback_data_test (id BIGINT PRIMARY KEY)\0";
470 laminar_execute(conn, sql.as_ptr().cast(), ptr::null_mut());
471
472 let query = b"SELECT * FROM callback_data_test\0";
473 let rc = laminar_subscribe_callback(
474 conn,
475 query.as_ptr().cast(),
476 Some(test_data_callback),
477 Some(test_error_callback),
478 ptr::null_mut(),
479 &mut sub,
480 );
481 assert_eq!(rc, LAMINAR_OK);
482
483 std::thread::sleep(std::time::Duration::from_millis(50));
485
486 laminar_subscription_cancel(sub);
487 laminar_subscription_free(sub);
488 laminar_close(conn);
489 }
490
491 }
494
495 #[test]
496 fn test_subscription_is_active_null_pointer() {
497 let mut active = true;
498 let rc = unsafe { laminar_subscription_is_active(ptr::null_mut(), &mut active) };
499 assert_eq!(rc, LAMINAR_ERR_NULL_POINTER);
500
501 let mut conn: *mut LaminarConnection = ptr::null_mut();
502 let mut sub: *mut LaminarSubscriptionHandle = ptr::null_mut();
503
504 unsafe {
505 laminar_open(&mut conn);
506 let sql = b"CREATE TABLE active_test (id BIGINT PRIMARY KEY)\0";
507 laminar_execute(conn, sql.as_ptr().cast(), ptr::null_mut());
508
509 let query = b"SELECT * FROM active_test\0";
510 laminar_subscribe_callback(
511 conn,
512 query.as_ptr().cast(),
513 None,
514 None,
515 ptr::null_mut(),
516 &mut sub,
517 );
518
519 let rc = laminar_subscription_is_active(sub, ptr::null_mut());
521 assert_eq!(rc, LAMINAR_ERR_NULL_POINTER);
522
523 laminar_subscription_cancel(sub);
524 laminar_subscription_free(sub);
525 laminar_close(conn);
526 }
527 }
528}