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
22unsafe fn parse_uuid_arg(argv: *mut *mut sqlite3_value, index: usize) -> Option<Uuid> {
39 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
68unsafe 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(); let c_str = CString::new(s).unwrap();
82 unsafe {
83 sqlite3_result_text(ctx, c_str.as_ptr(), -1, SQLITE_TRANSIENT());
84 }
85}
86
87unsafe 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
114unsafe 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
130unsafe 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
149unsafe 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#[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 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 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
329pub 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}