Skip to main content

sqlite_wasm_uuid_rs/
lib.rs

1#![doc = include_str!("../README.md")]
2#![no_std]
3#![allow(clippy::similar_names)]
4#![cfg(target_arch = "wasm32")]
5
6extern crate alloc;
7
8use alloc::{ffi::CString, string::ToString};
9use core::{
10    ffi::{CStr, c_char, c_int, c_void},
11    ptr, slice,
12};
13
14use sqlite_wasm_rs::{
15    SQLITE_BLOB, SQLITE_DETERMINISTIC, SQLITE_INNOCUOUS, SQLITE_OK, SQLITE_TEXT, SQLITE_TRANSIENT,
16    SQLITE_UTF8, sqlite3, sqlite3_api_routines, sqlite3_context, sqlite3_create_function_v2,
17    sqlite3_result_blob, sqlite3_result_null, sqlite3_result_text, sqlite3_value,
18    sqlite3_value_blob, sqlite3_value_bytes, sqlite3_value_text, sqlite3_value_type,
19};
20use uuid::Uuid;
21
22/// Helper function to parse a UUID from an SQLite argument value.
23///
24/// Supports two input formats:
25/// - **TEXT**: A 32 (hex) or 36 (hyphenated) character string string.
26/// - **BLOB**: A raw 16-byte UUID buffer.
27///
28/// # Arguments
29/// * `argv` - Pointer to the array of sqlite3_value pointers.
30/// * `index` - Index of the argument to check.
31///
32/// # Returns
33/// * `Option<Uuid>` - The parsed UUID if valid, or `None` if invalid/wrong
34///   type.
35///
36/// # Safety
37/// This function is unsafe because it dereferences raw pointers from `argv`.
38unsafe fn parse_uuid_arg(argv: *mut *mut sqlite3_value, index: usize) -> Option<Uuid> {
39    // SAFETY: Caller must ensure `argv` has at least `index + 1` elements
40    let arg = unsafe { *argv.add(index) };
41    let ty = unsafe { sqlite3_value_type(arg) };
42
43    match ty {
44        SQLITE_TEXT => {
45            let text_ptr = unsafe { sqlite3_value_text(arg) };
46            if text_ptr.is_null() {
47                return None;
48            }
49            let c_str = unsafe { CStr::from_ptr(text_ptr.cast::<c_char>()) };
50            let s = c_str.to_str().ok()?;
51            Uuid::parse_str(s).ok()
52        }
53        SQLITE_BLOB => {
54            let blob_ptr = unsafe { sqlite3_value_blob(arg) };
55            let bytes = unsafe { sqlite3_value_bytes(arg) };
56            if bytes == 16 && !blob_ptr.is_null() {
57                let s = unsafe { slice::from_raw_parts(blob_ptr.cast::<u8>(), 16) };
58                let array: [u8; 16] = s.try_into().ok()?;
59                Some(Uuid::from_bytes(array))
60            } else {
61                None
62            }
63        }
64        _ => None,
65    }
66}
67
68// --- SQL Functions (UUIDv7) ---
69
70/// SQL Function: `uuid7()`
71///
72/// Generates a UUIDv7 (time-ordered) and returns it as a canonical 36-character
73/// string.
74unsafe extern "C" fn uuid7_func(
75    ctx: *mut sqlite3_context,
76    _argc: c_int,
77    _argv: *mut *mut sqlite3_value,
78) {
79    let u = Uuid::now_v7();
80    let s = u.to_string(); // canonical 36-char string
81    let c_str = CString::new(s).unwrap();
82    unsafe {
83        sqlite3_result_text(ctx, c_str.as_ptr(), -1, SQLITE_TRANSIENT());
84    }
85}
86
87/// SQL Function: `uuid7_blob()`
88unsafe extern "C" fn uuid7_blob_func(
89    ctx: *mut sqlite3_context,
90    argc: c_int,
91    argv: *mut *mut sqlite3_value,
92) {
93    if argc == 0 {
94        let u = Uuid::now_v7();
95        let bytes = u.as_bytes();
96        unsafe {
97            sqlite3_result_blob(ctx, bytes.as_ptr().cast::<c_void>(), 16, SQLITE_TRANSIENT());
98        }
99        return;
100    }
101
102    if let Some(u) = unsafe { parse_uuid_arg(argv, 0) } {
103        let bytes = u.as_bytes();
104        unsafe {
105            sqlite3_result_blob(ctx, bytes.as_ptr().cast::<c_void>(), 16, SQLITE_TRANSIENT());
106        }
107    } else {
108        unsafe {
109            sqlite3_result_null(ctx);
110        }
111    }
112}
113
114// --- SQL Functions (UUIDv4) ---
115
116/// Implementation of the `uuid()` SQL function.
117unsafe extern "C" fn uuid_func(
118    ctx: *mut sqlite3_context,
119    _argc: c_int,
120    _argv: *mut *mut sqlite3_value,
121) {
122    let u = Uuid::new_v4();
123    let s = u.to_string();
124    let c_str = CString::new(s).unwrap();
125    unsafe {
126        sqlite3_result_text(ctx, c_str.as_ptr(), -1, SQLITE_TRANSIENT());
127    }
128}
129
130/// Implementation of the `uuid_str(X)` SQL function.
131unsafe extern "C" fn uuid_str_func(
132    ctx: *mut sqlite3_context,
133    _argc: c_int,
134    argv: *mut *mut sqlite3_value,
135) {
136    if let Some(u) = unsafe { parse_uuid_arg(argv, 0) } {
137        let s = u.to_string();
138        let c_str = CString::new(s).unwrap();
139        unsafe {
140            sqlite3_result_text(ctx, c_str.as_ptr(), -1, SQLITE_TRANSIENT());
141        }
142    } else {
143        unsafe {
144            sqlite3_result_null(ctx);
145        }
146    }
147}
148
149/// Implementation of the `uuid_blob(X)` SQL function.
150unsafe extern "C" fn uuid_blob_func(
151    ctx: *mut sqlite3_context,
152    argc: c_int,
153    argv: *mut *mut sqlite3_value,
154) {
155    if argc == 0 {
156        let u = Uuid::new_v4();
157        let bytes = u.as_bytes();
158        unsafe {
159            sqlite3_result_blob(ctx, bytes.as_ptr().cast::<c_void>(), 16, SQLITE_TRANSIENT());
160        }
161        return;
162    }
163
164    if let Some(u) = unsafe { parse_uuid_arg(argv, 0) } {
165        let bytes = u.as_bytes();
166        unsafe {
167            sqlite3_result_blob(ctx, bytes.as_ptr().cast::<c_void>(), 16, SQLITE_TRANSIENT());
168        }
169    } else {
170        unsafe {
171            sqlite3_result_null(ctx);
172        }
173    }
174}
175
176// --- Extension Entry Point ---
177
178/// SQLite Extension Entry Point: `sqlite3_uuid_init`
179///
180/// Registers the following SQL functions with the SQLite database connection:
181/// - `uuid`
182/// - `uuid_str`
183/// - `uuid_blob`
184/// - `uuid7`
185/// - `uuid7_blob`
186///
187/// # Arguments
188/// * `db` - The SQLite database connection.
189/// * `_pz_err_msg` - Pointer to error message pointer (unused).
190/// * `_p_api` - Pointer to SQLite API (unused, assuming linked implementation).
191///
192/// # Returns
193/// * `SQLITE_OK` on success, or an error code.
194///
195/// # Safety
196/// This function is unsafe because it interacts with raw SQLite pointers.
197/// It assumes `db` is a valid SQLite database connection.
198#[unsafe(no_mangle)]
199#[allow(clippy::too_many_lines)]
200pub unsafe extern "C" fn sqlite3_uuid_init(
201    db: *mut sqlite3,
202    _pz_err_msg: *mut *mut c_char,
203    _p_api: *const sqlite3_api_routines,
204) -> c_int {
205    let flags = SQLITE_UTF8 | SQLITE_INNOCUOUS;
206    let deterministic = flags | SQLITE_DETERMINISTIC;
207
208    // --- UUIDv7 Registration ---
209
210    let rc = unsafe {
211        sqlite3_create_function_v2(
212            db,
213            c"uuid7".as_ptr(),
214            0,
215            flags,
216            ptr::null_mut(),
217            Some(uuid7_func),
218            None,
219            None,
220            None,
221        )
222    };
223    if rc != SQLITE_OK {
224        return rc;
225    }
226
227    let rc = unsafe {
228        sqlite3_create_function_v2(
229            db,
230            c"uuid7_blob".as_ptr(),
231            0,
232            flags,
233            ptr::null_mut(),
234            Some(uuid7_blob_func),
235            None,
236            None,
237            None,
238        )
239    };
240    if rc != SQLITE_OK {
241        return rc;
242    }
243
244    let rc = unsafe {
245        sqlite3_create_function_v2(
246            db,
247            c"uuid7_blob".as_ptr(),
248            1,
249            deterministic,
250            ptr::null_mut(),
251            Some(uuid7_blob_func),
252            None,
253            None,
254            None,
255        )
256    };
257    if rc != SQLITE_OK {
258        return rc;
259    }
260
261    // --- UUIDv4 Registration ---
262
263    let rc = unsafe {
264        sqlite3_create_function_v2(
265            db,
266            c"uuid".as_ptr(),
267            0,
268            flags,
269            ptr::null_mut(),
270            Some(uuid_func),
271            None,
272            None,
273            None,
274        )
275    };
276    if rc != SQLITE_OK {
277        return rc;
278    }
279
280    let rc = unsafe {
281        sqlite3_create_function_v2(
282            db,
283            c"uuid_str".as_ptr(),
284            1,
285            deterministic,
286            ptr::null_mut(),
287            Some(uuid_str_func),
288            None,
289            None,
290            None,
291        )
292    };
293    if rc != SQLITE_OK {
294        return rc;
295    }
296
297    let rc = unsafe {
298        sqlite3_create_function_v2(
299            db,
300            c"uuid_blob".as_ptr(),
301            0,
302            flags,
303            ptr::null_mut(),
304            Some(uuid_blob_func),
305            None,
306            None,
307            None,
308        )
309    };
310    if rc != SQLITE_OK {
311        return rc;
312    }
313
314    unsafe {
315        sqlite3_create_function_v2(
316            db,
317            c"uuid_blob".as_ptr(),
318            1,
319            deterministic,
320            ptr::null_mut(),
321            Some(uuid_blob_func),
322            None,
323            None,
324            None,
325        )
326    }
327}
328
329/// Rust-friendly helper to register the extension.
330///
331/// # Returns
332///
333/// * `c_int` - Result code from registering the extension.
334///
335/// # Safety
336///
337/// This function is unsafe because it calls the unsafe `sqlite3_uuid_init`
338/// function.
339///
340/// # Errors
341///
342/// * Returns `Ok(())` if the extension was registered successfully.
343/// * Returns `Err(c_int)` with the SQLite error code if registration failed. Learn more about SQLite error codes [here](https://www.sqlite.org/rescode.html).
344pub unsafe fn register() -> Result<(), c_int> {
345    let status = unsafe { sqlite_wasm_rs::sqlite3_auto_extension(Some(sqlite3_uuid_init)) };
346    if status == SQLITE_OK { Ok(()) } else { Err(status) }
347}