Skip to main content

daft_ext/abi/
mod.rs

1//! Stable C ABI contract between Daft and extension cdylibs.
2//!
3//! This module defines the `repr(C)` types that Daft and extension shared
4//! libraries use to communicate. It has zero Daft internal dependencies
5//! and zero Arrow implementation dependencies (unless a feature flag is enabled).
6//!
7//! Naming follows Postgres conventions:
8//! - "module" = the shared library at the ABI boundary
9//! - "extension" = the higher-level Python package wrapping a module
10
11pub mod arrow;
12pub mod compat;
13pub mod ffi;
14
15use std::ffi::{c_char, c_int, c_void};
16
17pub use arrow::{ArrowArray, ArrowArrayStream, ArrowData, ArrowSchema};
18
19/// Modules built against a different ABI version are rejected at load time.
20pub const DAFT_ABI_VERSION: u32 = 1;
21
22/// Symbol that every Daft module cdylib must export.
23///
24/// ```ignore
25/// #[no_mangle]
26/// pub extern "C" fn daft_module_magic() -> FFI_Module { ... }
27/// ```
28pub const DAFT_MODULE_MAGIC_SYMBOL: &str = "daft_module_magic";
29
30/// Module definition returned by the entry point symbol.
31///
32/// Analogous to Postgres's `Pg_magic_struct` + `_PG_init` combined into
33/// a single struct.
34#[derive(Copy, Clone)]
35#[repr(C)]
36pub struct FFI_Module {
37    /// Must equal [`DAFT_ABI_VERSION`] or the loader rejects the module.
38    pub daft_abi_version: u32,
39
40    /// Module name as a null-terminated UTF-8 string.
41    ///
42    /// Must remain valid for the lifetime of the process (typically a
43    /// `&'static CStr` cast to `*const c_char`).
44    pub name: *const c_char,
45
46    /// Called by the host to let the module register its functions.
47    ///
48    /// Returns 0 on success, non-zero on error.
49    pub init: unsafe extern "C" fn(session: *mut FFI_SessionContext) -> c_int,
50
51    /// Free a string previously allocated by this module
52    /// (e.g. from `FFI_ScalarFunction::get_return_field` or error messages).
53    pub free_string: unsafe extern "C" fn(s: *mut c_char),
54}
55
56// SAFETY: Function pointers plus a static string pointer.
57unsafe impl Send for FFI_Module {}
58unsafe impl Sync for FFI_Module {}
59
60/// Virtual function table for a scalar function.
61///
62/// The host calls methods through these function pointers. `ctx` is an opaque
63/// pointer owned by the module; the host never dereferences it directly.
64#[repr(C)]
65pub struct FFI_ScalarFunction {
66    /// Opaque module-side context pointer.
67    pub ctx: *const c_void,
68
69    /// Return the function name as a null-terminated UTF-8 string.
70    ///
71    /// The returned pointer borrows from `ctx` and is valid until `fini`.
72    pub name: unsafe extern "C" fn(ctx: *const c_void) -> *const c_char,
73
74    /// Compute the output field given input fields.
75    ///
76    /// `args` points to `args_count` Arrow field schemas (C Data Interface).
77    /// On success, writes the result schema to `*ret`.
78    /// On error, writes a null-terminated message to `*errmsg`
79    /// (freed by `FFI_Module::free_string`).
80    ///
81    /// Returns 0 on success, non-zero on error.
82    pub get_return_field: unsafe extern "C" fn(
83        ctx: *const c_void,
84        args: *const ArrowSchema,
85        args_count: usize,
86        ret: *mut ArrowSchema,
87        errmsg: *mut *mut c_char,
88    ) -> c_int,
89
90    /// Evaluate the function on Arrow arrays via the C Data Interface.
91    ///
92    /// On error, writes a null-terminated message to `*errmsg`
93    /// (freed by `FFI_Module::free_string`).
94    ///
95    /// Returns 0 on success, non-zero on error.
96    pub call: unsafe extern "C" fn(
97        ctx: *const c_void,
98        args: *const ArrowArray,
99        args_schemas: *const ArrowSchema,
100        args_count: usize,
101        ret_array: *mut ArrowArray,
102        ret_schema: *mut ArrowSchema,
103        errmsg: *mut *mut c_char,
104    ) -> c_int,
105
106    /// Finalize the function, freeing all owned resources.
107    pub fini: unsafe extern "C" fn(ctx: *mut c_void),
108}
109
110// SAFETY: The vtable is function pointers plus an opaque ctx pointer.
111// The module is responsible for thread-safety of ctx.
112unsafe impl Send for FFI_ScalarFunction {}
113unsafe impl Sync for FFI_ScalarFunction {}
114
115/// Host-side session context passed to a module's `init` function.
116///
117/// The module calls `define_function` to register extensions.
118#[repr(C)]
119pub struct FFI_SessionContext {
120    /// Opaque host-side context pointer.
121    pub ctx: *mut c_void,
122
123    /// Register a scalar function with the host session.
124    ///
125    /// The host takes ownership of `function` on success.
126    /// Returns 0 on success, non-zero on error.
127    pub define_function:
128        unsafe extern "C" fn(ctx: *mut c_void, function: FFI_ScalarFunction) -> c_int,
129}
130
131// SAFETY: Function pointer plus opaque host pointer.
132unsafe impl Send for FFI_SessionContext {}
133unsafe impl Sync for FFI_SessionContext {}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn struct_sizes() {
141        let ptr = std::mem::size_of::<usize>();
142
143        // FFI_ScalarFunction: ctx + name + get_return_field + call + fini = 5 pointers
144        assert_eq!(std::mem::size_of::<FFI_ScalarFunction>(), 5 * ptr);
145
146        // FFI_SessionContext: ctx + define_function = 2 pointers
147        assert_eq!(std::mem::size_of::<FFI_SessionContext>(), 2 * ptr);
148
149        // FFI_Module: u32 (padded) + name + init + free_string
150        // 64-bit: 4 + 4 pad + 8 + 8 + 8 = 32
151        // 32-bit: 4 + 4 + 4 + 4 = 16
152        assert_eq!(
153            std::mem::size_of::<FFI_Module>(),
154            if ptr == 8 { 32 } else { 16 }
155        );
156    }
157
158    #[test]
159    fn send_and_sync() {
160        fn assert_send_sync<T: Send + Sync>() {}
161        assert_send_sync::<FFI_ScalarFunction>();
162        assert_send_sync::<FFI_SessionContext>();
163        assert_send_sync::<FFI_Module>();
164    }
165
166    #[test]
167    fn constants() {
168        // !! THIS TEST EXISTS SO THAT THESE ARE NOT CHANGED BY ACCIDENT
169        // IT MEANS WE HAVE TO MANUALLY UPDATE IN TWO PLACES !!
170        assert_eq!(DAFT_ABI_VERSION, 1);
171        assert_eq!(DAFT_MODULE_MAGIC_SYMBOL, "daft_module_magic");
172    }
173}