177 lines
6.5 KiB
Rust
177 lines
6.5 KiB
Rust
#[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`
|
|
#[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, sqlite3_context, sqlite3_value, FunctionFlags},
|
|
Result,
|
|
};
|
|
|
|
use super::*;
|
|
|
|
pub(super) fn init_rs(db: *mut sqlite3) -> Result<()> {
|
|
let flags = FunctionFlags::UTF8 | FunctionFlags::DETERMINISTIC;
|
|
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, flags)?;
|
|
|
|
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 this Julid.
|
|
/// ```text
|
|
/// sqlite> select julid_string(julid_new());
|
|
/// 01H6C7D9CT00009TF3EXXJHX4Y
|
|
/// ```
|
|
pub fn julid_string(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) -> Result<()> {
|
|
if let Some(value) = id.get(0) {
|
|
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 {
|
|
return Err(sqlite_loadable::Error::new_message(
|
|
"Could not convert empty Julid to string",
|
|
));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Returns the timestamp portion as number of fractional seconds (`f64`),
|
|
/// for 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.get(0) {
|
|
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.get(0) {
|
|
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.get(0) {
|
|
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(())
|
|
}
|
|
}
|