#[cfg(feature = "plugin")] use sqlite_loadable::prelude::{c_char, c_uint, sqlite3, sqlite3_api_routines}; mod base32; pub mod julid; #[cfg(feature = "serde")] /// Serialization into bytes, and deserialization from a variety of formats, /// with Serde (feature `serde` (default)) pub mod serde; #[cfg(feature = "sqlx")] /// Traits from the SQLx crate for getting Julids into and out of SQLite /// databases from normal Rust applications. (feature `sqlx` (default)) pub mod sqlx; #[doc(inline)] pub use base32::DecodeError; #[doc(inline)] pub use julid::Julid; /// This `unsafe extern "C"` function is the main entry point into the loadable /// SQLite extension. By default, it and the `plugin` module it depends on will /// not be built. Build with `cargo build --features plugin` /// /// # Safety /// This is FFI; it's inherently unsafe. But this function is called by /// sqlite, not by a user, so it should be OK. #[cfg(feature = "plugin")] #[no_mangle] pub unsafe extern "C" fn sqlite3_julid_init( db: *mut sqlite3, _pz_err_msg: *mut *mut c_char, p_api: *mut sqlite3_api_routines, ) -> c_uint { unsafe { sqlite_loadable::ext::faux_sqlite_extension_init2(p_api) } match sqlite_plugin::init_rs(db) { Ok(()) => 256, // SQLITE_OK_LOAD_PERMANENTLY Err(err) => err.code_extended(), } } /// The code for the SQLite plugin is kept in this module, and exposed via the /// `sqlite3_julid_init` function (feature `plugin`) #[cfg(feature = "plugin")] pub mod sqlite_plugin { use sqlite_loadable::{ api, define_scalar_function, prelude::{sqlite3_context, sqlite3_value, FunctionFlags}, Result, }; use super::*; pub(super) fn init_rs(db: *mut sqlite3) -> Result<()> { let flags = FunctionFlags::UTF8 | FunctionFlags::DETERMINISTIC | FunctionFlags::INNOCUOUS; define_scalar_function(db, "julid_new", 0, julid_new, FunctionFlags::INNOCUOUS)?; define_scalar_function(db, "julid_seconds", 1, julid_seconds, flags)?; define_scalar_function(db, "julid_counter", 1, julid_counter, flags)?; define_scalar_function(db, "julid_sortable", 1, julid_sortable, flags)?; define_scalar_function( db, "julid_string", -1, julid_string, FunctionFlags::UTF8 | FunctionFlags::INNOCUOUS, )?; Ok(()) } //-************************************************************************ // impls //-************************************************************************ /// Create a new `Julid` and return it as a `blob`. Because the bytes inside /// a `Julid` are not valid UTF8, if you wish to see a human-readable /// representation, use the built-in `hex()` function, or `julid_string()`. /// /// ```text /// sqlite> select hex(julid_new()); /// 018998768ACF000060B31DB175E0C5F9 /// sqlite> select julid_string(julid_new()); /// 01H6C7D9CT00009TF3EXXJHX4Y /// ``` pub fn julid_new(context: *mut sqlite3_context, _vals: &[*mut sqlite3_value]) -> Result<()> { api::result_blob(context, Julid::new().as_bytes().as_slice()); Ok(()) } /// Return the human-readable base32 Crockford encoding of the given Julid, /// or create a new one if no arguments. /// /// ```text /// sqlite> select julid_string(julid_new()); /// 01H6C7D9CT00009TF3EXXJHX4Y /// sqlite> select julid_string(); /// 01HJSHZ0PN000EKP3H94R6TPWH /// ``` pub fn julid_string(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) -> Result<()> { if let Some(value) = id.first() { let id = api::value_blob(value); let bytes: [u8; 16] = id.try_into().map_err(|_| { sqlite_loadable::Error::new_message("Could not convert given value to Julid") })?; let id: Julid = bytes.into(); api::result_text(context, id.as_string())?; } else { api::result_text(context, Julid::new().as_string())?; } Ok(()) } /// Returns the timestamp portion as fractional seconds (`f64`), for /// convenient use in SQLite's `datetime()` function. /// /// ```text /// sqlite> select julid_seconds(julid_new()); /// 1690480066.208 /// sqlite> select datetime(julid_seconds(julid_new()), 'auto'); /// 2023-07-27 17:47:50 /// ``` pub fn julid_seconds(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) -> Result<()> { if let Some(value) = id.first() { let id = api::value_blob(value); let bytes: [u8; 16] = id.try_into().map_err(|_| { sqlite_loadable::Error::new_message("Could not convert given value to Julid") })?; let id: Julid = bytes.into(); let ts = id.timestamp() as f64 / 1000.0; api::result_double(context, ts); } else { return Err(sqlite_loadable::Error::new_message( "Could not get timestamp for empty Julid", )); } Ok(()) } /// Return the value of the monotonic counter for this Julid. For the first /// Julid created in a millisecond, its value will be 0. If you are /// creating more than 65,536 Julids per millisecond, the counter will /// saturate at 65,535. /// /// ```text /// sqlite> select julid_counter(julid_new()); /// 0 /// ``` pub fn julid_counter(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) -> Result<()> { if let Some(value) = id.first() { let id = api::value_blob(value); let bytes: [u8; 16] = id.try_into().map_err(|_| { sqlite_loadable::Error::new_message("Could not convert given value to Julid") })?; let id: Julid = bytes.into(); api::result_int64(context, id.counter() as i64); } else { return Err(sqlite_loadable::Error::new_message( "Could not get counter value for empty Julid", )); } Ok(()) } /// Return the 64-bit concatenation of the Julid's timestamp and monotonic /// counter. /// /// ```text /// sqlite> select julid_sortable(julid_new()); /// 110787724287475712 /// ``` pub fn julid_sortable(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) -> Result<()> { if let Some(value) = id.first() { let id = api::value_blob(value); let bytes: [u8; 16] = id.try_into().map_err(|_| { sqlite_loadable::Error::new_message("Could not convert given value to Julid") })?; let id: Julid = bytes.into(); api::result_int64(context, id.sortable() as i64); } else { return Err(sqlite_loadable::Error::new_message( "Could not get sortable bits for empty Julid", )); } Ok(()) } }