Compare commits

..

No commits in common. "7dfe1ffb9485980ce648db2de547cee5b250502d" and "7067721f725b98830d8d530fff68b67cf268e6c6" have entirely different histories.

10 changed files with 123 additions and 190 deletions

View file

@ -1,6 +1,6 @@
[package] [package]
name = "julid-rs" name = "julid-rs"
version = "1.6.18033988" version = "1.6.180"
authors = ["Joe Ardent <code@ardent.nebcorp.com>"] authors = ["Joe Ardent <code@ardent.nebcorp.com>"]
edition = "2021" edition = "2021"
keywords = ["ulid", "library", "sqlite", "extension", "julid"] keywords = ["ulid", "library", "sqlite", "extension", "julid"]
@ -11,7 +11,8 @@ license-file = "LICENSE.md"
repository = "https://gitlab.com/nebkor/julid" repository = "https://gitlab.com/nebkor/julid"
[features] [features]
default = ["serde", "sqlx"] # just the regular crate default = ["chrono", "serde", "sqlx"] # just the regular crate
chrono = ["dep:chrono"]
serde = ["dep:serde"] serde = ["dep:serde"]
sqlx = ["dep:sqlx"] sqlx = ["dep:sqlx"]
@ -25,26 +26,14 @@ crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
rand = "0.8" rand = "0.8"
# for the CLI
clap = { version = "4.3", default-features = false, features = ["help", "usage", "std", "derive"] }
chrono = { version = "0.4", default-features = false, features = ["std", "time"] }
# all other deps are optional # all other deps are optional
chrono = { version = "0.4", optional = true, default-features = false, features = ["std", "time"] }
serde = { version = "1.0", features = ["derive"], optional = true } serde = { version = "1.0", features = ["derive"], optional = true }
sqlx = { version = "0.7", features = ["sqlite"], default-features = false, optional = true } sqlx = { version = "0.7", features = ["sqlite"], default-features = false, optional = true }
sqlite-loadable = { version = "0.0.5", optional = true } sqlite-loadable = { version = "0.0.5", optional = true }
[dev-dependencies] [dev-dependencies]
divan = "0.1" clap = { version = "4.3", default-features = false, features = ["help", "usage", "std", "derive"] }
[[bench]]
name = "simple"
harness = false
[[bin]]
name = "julid-gen"
path = "src/bin/gen.rs"
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true

View file

@ -20,8 +20,6 @@ sqlite> select datetime(julid_timestamp(julid_new()), 'auto');
2023-07-27 17:47:50 2023-07-27 17:47:50
sqlite> select julid_counter(julid_new()); sqlite> select julid_counter(julid_new());
0 0
sqlite> select julid_string();
01HM4WJ7T90001P8SN9898FBTN
``` ```
Crates.io: <https://crates.io/crates/julid-rs> Crates.io: <https://crates.io/crates/julid-rs>
@ -73,8 +71,9 @@ are always fresh, it's not possible to easily guess a valid Julid if you already
# How to use # How to use
The Julid crate can be used in two different ways: as a regular Rust library, declared in your Rust The Julid crate can be used in two different ways: as a regular Rust library, declared in your Rust
project's `Cargo.toml` file (say, by running `cargo add julid-rs`), and used as shown in the sample project's `Cargo.toml` file (say, by running `cargo add julid-rs`), and used as shown above. There's
commandline program (see below). But the primary use case for me was as a loadable a rudimentary [benchmark](https://gitlab.com/nebkor/julid/-/blob/main/examples/benchmark.rs) example
in the repo that shows off most of the Rust API. But the primary use case for me was as a loadable
SQLite extension. Both are covered in the [documentation](https://docs.rs/julid-rs/latest/julid/), SQLite extension. Both are covered in the [documentation](https://docs.rs/julid-rs/latest/julid/),
but let's go over them here, starting with the extension. but let's go over them here, starting with the extension.
@ -84,8 +83,6 @@ The extension, when loaded into SQLite, provides the following functions:
* `julid_new()`: create a new Julid and return it as a 16-byte * `julid_new()`: create a new Julid and return it as a 16-byte
[blob](https://www.sqlite.org/datatype3.html#storage_classes_and_datatypes) [blob](https://www.sqlite.org/datatype3.html#storage_classes_and_datatypes)
* `julid_string()`: create a new Julid and return it as a 26-character [base-32
Crockford](https://en.wikipedia.org/wiki/Base32)-encoded string
* `julid_seconds(julid)`: get the number seconds (as a 64-bit float) since the UNIX epoch that this * `julid_seconds(julid)`: get the number seconds (as a 64-bit float) since the UNIX epoch that this
julid was created (convenient for passing to the builtin `datetime()` function) julid was created (convenient for passing to the builtin `datetime()` function)
* `julid_counter(julid)`: show the value of this julid's monotonic counter * `julid_counter(julid)`: show the value of this julid's monotonic counter
@ -122,11 +119,11 @@ For a table created like:
create table if not exists watches ( create table if not exists watches (
id blob not null primary key default (julid_new()), id blob not null primary key default (julid_new()),
kind int not null, -- enum for movie or tv show or whatev kind int not null, -- enum for movie or tv show or whatev
title text not null, title text not null, -- this has a secondary index
length int, length int,
release_date date, release_date int,
added_by blob not null, added_by blob not null,
last_updated date not null default CURRENT_TIMESTAMP, last_updated int not null default (unixepoch()),
foreign key (added_by) references users (id) foreign key (added_by) references users (id)
); );
``` ```
@ -143,62 +140,63 @@ where the wildcards get bound in a loop with unique values and the Julid `id` fi
generated by the extension for each row, I get over 100,000 insertions/second when using a generated by the extension for each row, I get over 100,000 insertions/second when using a
file-backed DB in WAL mode and `NORMAL` durability settings. file-backed DB in WAL mode and `NORMAL` durability settings.
### Safety
There is one `unsafe fn` in this project, `sqlite_julid_init()`, and it is only built for the
`plugin` feature. The reason for it is that it's interacting with foreign code (SQLite itself) via
the C interface, which is inherently unsafe. If you are not building the plugin, there is no
`unsafe` code.
## Inside a Rust program ## Inside a Rust program
Of course, you can also use it outside of a database; the `Julid` type is publicly exported. There's Of course, you can also use it outside of a database; the `Julid` type is publicly exported. There's
a simple commandline program in `src/bin/gen.rs`, and can be run like `cargo run --bin julid-gen` a simple benchmark in the examples folder of the repo, the important parts of which look like:
(or you can `cargo install julid-rs` to get the `julid-gen` program installed on your computer),
which will generate and print one Julid. If you want to see its component pieces, grab the Julid
printed from it, and then run it with the `-d` flag:
``` ``` rust
$ julid-gen 4 use julid::Julid;
01HV2G2ATR000CJ2WESB7CVC19
01HV2G2ATR000K1AGQPKMX5H0M fn main() {
01HV2G2ATR001CM27S59BHZ25G /* snip some stuff */
01HV2G2ATR001WPJ8BS7PZHE6A
$ julid-gen -d 01HV2G2ATR001WPJ8BS7PZHE6A let start = Instant::now();
Created at: 2024-04-09 22:36:11.992 UTC for _ in 0..num {
Monotonic counter: 3 v.push(Julid::new());
Random: 14648252224908081354 }
let end = Instant::now();
let dur = (end - start).as_micros();
for id in v.iter() {
eprintln!(
"{id}: created_at {}; counter: {}; sortable: {}",
id.created_at(),
id.counter(),
id.sortable()
);
}
println!("{num} Julids generated in {dur}us");
``` ```
The help is useful: If you were to run it on a computer like mine (AMD Ryzen 9 3900X, 12-core, 2.2-4.6 GHz), you might
see something like this:
``` ``` text
$ julid-gen -h $ cargo run --example=benchmark --release -- -n 30000 2> /dev/null
Generate, print, and parse Julids 30000 Julids generated in 1240us
Usage: julid-gen [OPTIONS] [NUM]
Arguments:
[NUM] Number of Julids to generate [default: 1]
Options:
-d, --decode <INPUT> Print the components of the given Julid
-a, --answer The answer to the meaning of Julid
-h, --help Print help
-V, --version Print version
``` ```
The whole program is just 34 lines, so check it out. That's about 24,000 IDs/millisecond; 24 *MILLION* per second!
The default optional Cargo features include implementations of traits for getting Julids into and The default optional Cargo features include implementations of traits for getting Julids into and
out of SQLite with [SQLx](https://github.com/launchbadge/sqlx), and for generally out of SQLite with [SQLx](https://github.com/launchbadge/sqlx), and for generally
serializing/deserializing with [Serde](https://serde.rs/), via the `sqlx` and `serde` features, serializing/deserializing with [Serde](https://serde.rs/), via the `sqlx` and `serde` features,
respectively. respectively. One final default optional feature, `chrono`, uses the Chrono crate to return the
timestamp as a [`DateTime`](https://docs.rs/chrono/latest/chrono/struct.DateTime.html) by adding a
`created_at(&self)` method to `Julid`.
Something to note: don't enable the `plugin` feature in your Cargo.toml if you're using this crate Something to note: don't enable the `plugin` feature in your Cargo.toml if you're using this crate
inside your Rust application, especially if you're *also* loading it as an extension in SQLite in inside your Rust application, especially if you're *also* loading it as an extension in SQLite in
your application. You'll get a long and confusing runtime panic due to there being multiple your application. You'll get a long and confusing runtime panic due to there being multiple
entrypoints defined with the same name. entrypoints defined with the same name.
### Safety
There is one `unsafe fn` in this project, `sqlite_julid_init()`, and it is only built for the
`plugin` feature. The reason for it is that it's interacting with foreign code (SQLite itself) via
the C interface, which is inherently unsafe. If you are not building the plugin, there is no
`unsafe` code.
# Thanks # Thanks
This project wouldn't have happened without a lot of inspiration (and a little shameless stealing) This project wouldn't have happened without a lot of inspiration (and a little shameless stealing)

View file

@ -1 +1 @@
1.618033988 1.6180

View file

@ -1,16 +0,0 @@
use divan::black_box;
use julid::Julid;
fn main() {
divan::main();
}
#[divan::bench]
fn jbench() {
for _ in 0..1_000_000 {
let x = black_box(Julid::new());
if x < 1u128.into() {
println!("that's weird");
}
}
}

37
examples/benchmark.rs Normal file
View file

@ -0,0 +1,37 @@
use std::time::Instant;
use clap::Parser;
use julid::Julid;
#[derive(Debug, Parser)]
struct Cli {
#[clap(
long,
short,
help = "Number of Julids to generate",
default_value_t = 2_000
)]
pub num: usize,
}
fn main() {
let cli = Cli::parse();
let num = cli.num;
let mut v = Vec::with_capacity(num);
let start = Instant::now();
for _ in 0..num {
v.push(Julid::new());
}
let end = Instant::now();
let dur = (end - start).as_micros();
for id in v.iter() {
eprintln!(
"{id}: created_at {}; counter: {}; sortable: {}",
id.created_at(),
id.counter(),
id.sortable()
);
}
println!("{num} Julids generated in {dur}us");
}

View file

@ -1,51 +0,0 @@
use clap::Parser;
use julid::Julid;
#[derive(Debug, Parser)]
#[command(
author,
version = "1.618033988",
about = "Generate, print, and parse Julids"
)]
struct Cli {
#[clap(
help = "Print the components of the given Julid",
short = 'd',
long = "decode"
)]
pub input: Option<String>,
#[clap(help = "Number of Julids to generate", default_value_t = 1)]
pub num: usize,
#[clap(
help = "The answer to the meaning of Julid",
default_value_t = false,
short,
long
)]
pub answer: bool,
}
fn main() {
let cli = Cli::parse();
if let Some(ts) = cli.input {
if let Ok(ts) = Julid::from_str(&ts) {
println!("Created at:\t\t{}", ts.created_at());
println!("Monotonic counter:\t{}", ts.counter());
println!("Random:\t\t\t{}", ts.random());
} else {
eprintln!("Could not parse input '{}' as a Julid", ts);
std::process::exit(1);
}
} else {
// Just print some Julids
let num = cli.num;
for _ in 0..num {
let j = if cli.answer {
42u128.into()
} else {
Julid::new()
};
println!("{j}");
}
}
}

View file

@ -54,20 +54,24 @@ impl Julid {
.as_millis() as u64; .as_millis() as u64;
let last = LAST_MSB.load(Ordering::SeqCst); let last = LAST_MSB.load(Ordering::SeqCst);
let ots = last >> COUNTER_BITS; let ots = last >> COUNTER_BITS;
let msb = if ots < ts { if ots < ts {
ts << COUNTER_BITS let msb = ts << COUNTER_BITS;
} else {
let counter = ((last & bitmask!(COUNTER_BITS) as u64) as u16).saturating_add(1);
(ots << COUNTER_BITS) + counter as u64
};
if LAST_MSB if LAST_MSB
.compare_exchange(last, msb, Ordering::SeqCst, Ordering::Relaxed) .compare_exchange(last, msb, Ordering::SeqCst, Ordering::Relaxed)
.is_ok() .is_ok()
{ {
break (msb, lsb).into(); break (msb, lsb).into();
} }
} else {
let counter = ((last & bitmask!(COUNTER_BITS) as u64) as u16).saturating_add(1);
let msb = (ots << COUNTER_BITS) + counter as u64;
if LAST_MSB
.compare_exchange(last, msb, Ordering::SeqCst, Ordering::Relaxed)
.is_ok()
{
break (msb, lsb).into();
}
}
// we didn't update the global counter, try again // we didn't update the global counter, try again
let micros = thread_rng().gen_range(10..50); let micros = thread_rng().gen_range(10..50);
std::thread::sleep(Duration::from_micros(micros)); std::thread::sleep(Duration::from_micros(micros));
@ -117,6 +121,7 @@ impl Julid {
(self.0 & bitmask!(UNIQUE_BITS)) as u64 (self.0 & bitmask!(UNIQUE_BITS)) as u64
} }
#[cfg(feature = "chrono")]
/// Returns the timestamp as a `chrono::DateTime<chrono::Utc>` (feature /// Returns the timestamp as a `chrono::DateTime<chrono::Utc>` (feature
/// `chrono` (default)) /// `chrono` (default))
pub fn created_at(&self) -> chrono::DateTime<chrono::Utc> { pub fn created_at(&self) -> chrono::DateTime<chrono::Utc> {
@ -139,7 +144,7 @@ impl Julid {
/// ```rust /// ```rust
/// use julid::julid::Julid; /// use julid::julid::Julid;
/// let text = "01D39ZY06FGSCTVN4T2V9PKHFZ"; /// let text = "01D39ZY06FGSCTVN4T2V9PKHFZ";
/// let id = Julid::from_str(text).unwrap(); /// let id = Julid::from_string(text).unwrap();
/// ///
/// assert_eq!(&id.to_string(), text); /// assert_eq!(&id.to_string(), text);
/// ``` /// ```
@ -156,19 +161,11 @@ impl Julid {
/// ```rust /// ```rust
/// use julid::julid::Julid; /// use julid::julid::Julid;
/// let text = "01D39ZY06FGSCTVN4T2V9PKHFZ"; /// let text = "01D39ZY06FGSCTVN4T2V9PKHFZ";
/// let result = Julid::from_str(text); /// let result = Julid::from_string(text);
/// ///
/// assert!(result.is_ok()); /// assert!(result.is_ok());
/// assert_eq!(&result.unwrap().to_string(), text); /// assert_eq!(&result.unwrap().to_string(), text);
/// ``` /// ```
pub const fn from_str(encoded: &str) -> Result<Julid, DecodeError> {
match base32::decode(encoded) {
Ok(int_val) => Ok(Julid(int_val)),
Err(err) => Err(err),
}
}
#[deprecated(since = "1.6.1803398", note = "use `from_str` instead")]
pub const fn from_string(encoded: &str) -> Result<Julid, DecodeError> { pub const fn from_string(encoded: &str) -> Result<Julid, DecodeError> {
match base32::decode(encoded) { match base32::decode(encoded) {
Ok(int_val) => Ok(Julid(int_val)), Ok(int_val) => Ok(Julid(int_val)),
@ -239,7 +236,7 @@ impl FromStr for Julid {
type Err = DecodeError; type Err = DecodeError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
Julid::from_str(s) Julid::from_string(s)
} }
} }
@ -256,7 +253,7 @@ mod tests {
#[test] #[test]
fn test_static() { fn test_static() {
let s = Julid(0x41414141414141414141414141414141).as_string(); let s = Julid(0x41414141414141414141414141414141).as_string();
let u = Julid::from_str(&s).unwrap(); let u = Julid::from_string(&s).unwrap();
assert_eq!(&s, "21850M2GA1850M2GA1850M2GA1"); assert_eq!(&s, "21850M2GA1850M2GA1850M2GA1");
assert_eq!(u.0, 0x41414141414141414141414141414141); assert_eq!(u.0, 0x41414141414141414141414141414141);
} }

View file

@ -20,10 +20,6 @@ pub use julid::Julid;
/// This `unsafe extern "C"` function is the main entry point into the loadable /// 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 /// SQLite extension. By default, it and the `plugin` module it depends on will
/// not be built. Build with `cargo build --features plugin` /// 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")] #[cfg(feature = "plugin")]
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sqlite3_julid_init( pub unsafe extern "C" fn sqlite3_julid_init(
@ -44,25 +40,19 @@ pub unsafe extern "C" fn sqlite3_julid_init(
pub mod sqlite_plugin { pub mod sqlite_plugin {
use sqlite_loadable::{ use sqlite_loadable::{
api, define_scalar_function, api, define_scalar_function,
prelude::{sqlite3_context, sqlite3_value, FunctionFlags}, prelude::{sqlite3, sqlite3_context, sqlite3_value, FunctionFlags},
Result, Result,
}; };
use super::*; use super::*;
pub(super) fn init_rs(db: *mut sqlite3) -> Result<()> { pub(super) fn init_rs(db: *mut sqlite3) -> Result<()> {
let flags = FunctionFlags::UTF8 | FunctionFlags::DETERMINISTIC | FunctionFlags::INNOCUOUS; let flags = FunctionFlags::UTF8 | FunctionFlags::DETERMINISTIC;
define_scalar_function(db, "julid_new", 0, julid_new, 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_seconds", 1, julid_seconds, flags)?;
define_scalar_function(db, "julid_counter", 1, julid_counter, 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_sortable", 1, julid_sortable, flags)?;
define_scalar_function( define_scalar_function(db, "julid_string", 1, julid_string, flags)?;
db,
"julid_string",
-1,
julid_string,
FunctionFlags::UTF8 | FunctionFlags::INNOCUOUS,
)?;
Ok(()) Ok(())
} }
@ -86,17 +76,13 @@ pub mod sqlite_plugin {
Ok(()) Ok(())
} }
/// Return the human-readable base32 Crockford encoding of the given Julid, /// Return the human-readable base32 Crockford encoding of this Julid.
/// or create a new one if no arguments.
///
/// ```text /// ```text
/// sqlite> select julid_string(julid_new()); /// sqlite> select julid_string(julid_new());
/// 01H6C7D9CT00009TF3EXXJHX4Y /// 01H6C7D9CT00009TF3EXXJHX4Y
/// sqlite> select julid_string();
/// 01HJSHZ0PN000EKP3H94R6TPWH
/// ``` /// ```
pub fn julid_string(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) -> Result<()> { pub fn julid_string(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) -> Result<()> {
if let Some(value) = id.first() { if let Some(value) = id.get(0) {
let id = api::value_blob(value); let id = api::value_blob(value);
let bytes: [u8; 16] = id.try_into().map_err(|_| { let bytes: [u8; 16] = id.try_into().map_err(|_| {
sqlite_loadable::Error::new_message("Could not convert given value to Julid") sqlite_loadable::Error::new_message("Could not convert given value to Julid")
@ -104,7 +90,9 @@ pub mod sqlite_plugin {
let id: Julid = bytes.into(); let id: Julid = bytes.into();
api::result_text(context, id.as_string())?; api::result_text(context, id.as_string())?;
} else { } else {
api::result_text(context, Julid::new().as_string())?; return Err(sqlite_loadable::Error::new_message(
"Could not convert empty Julid to string",
));
} }
Ok(()) Ok(())
@ -120,7 +108,7 @@ pub mod sqlite_plugin {
/// 2023-07-27 17:47:50 /// 2023-07-27 17:47:50
/// ``` /// ```
pub fn julid_seconds(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) -> Result<()> { pub fn julid_seconds(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) -> Result<()> {
if let Some(value) = id.first() { if let Some(value) = id.get(0) {
let id = api::value_blob(value); let id = api::value_blob(value);
let bytes: [u8; 16] = id.try_into().map_err(|_| { let bytes: [u8; 16] = id.try_into().map_err(|_| {
sqlite_loadable::Error::new_message("Could not convert given value to Julid") sqlite_loadable::Error::new_message("Could not convert given value to Julid")
@ -147,7 +135,7 @@ pub mod sqlite_plugin {
/// 0 /// 0
/// ``` /// ```
pub fn julid_counter(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) -> Result<()> { pub fn julid_counter(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) -> Result<()> {
if let Some(value) = id.first() { if let Some(value) = id.get(0) {
let id = api::value_blob(value); let id = api::value_blob(value);
let bytes: [u8; 16] = id.try_into().map_err(|_| { let bytes: [u8; 16] = id.try_into().map_err(|_| {
sqlite_loadable::Error::new_message("Could not convert given value to Julid") sqlite_loadable::Error::new_message("Could not convert given value to Julid")
@ -171,7 +159,7 @@ pub mod sqlite_plugin {
/// 110787724287475712 /// 110787724287475712
/// ``` /// ```
pub fn julid_sortable(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) -> Result<()> { pub fn julid_sortable(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) -> Result<()> {
if let Some(value) = id.first() { if let Some(value) = id.get(0) {
let id = api::value_blob(value); let id = api::value_blob(value);
let bytes: [u8; 16] = id.try_into().map_err(|_| { let bytes: [u8; 16] = id.try_into().map_err(|_| {
sqlite_loadable::Error::new_message("Could not convert given value to Julid") sqlite_loadable::Error::new_message("Could not convert given value to Julid")

View file

@ -114,6 +114,6 @@ pub mod julid_as_str {
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
let deserialized_str = String::deserialize(deserializer)?; let deserialized_str = String::deserialize(deserializer)?;
Julid::from_str(&deserialized_str).map_err(serde::de::Error::custom) Julid::from_string(&deserialized_str).map_err(serde::de::Error::custom)
} }
} }

View file

@ -3,7 +3,7 @@ use std::borrow::Cow;
use sqlx::{ use sqlx::{
encode::IsNull, encode::IsNull,
sqlite::{SqliteArgumentValue, SqliteValueRef}, sqlite::{SqliteArgumentValue, SqliteValueRef},
Decode, Encode, Sqlite, Value, ValueRef, Decode, Encode, Sqlite,
}; };
use crate::Julid; use crate::Julid;
@ -25,17 +25,8 @@ impl<'q> Encode<'q, Sqlite> for Julid {
impl Decode<'_, Sqlite> for Julid { impl Decode<'_, Sqlite> for Julid {
fn decode(value: SqliteValueRef<'_>) -> Result<Self, sqlx::error::BoxDynError> { fn decode(value: SqliteValueRef<'_>) -> Result<Self, sqlx::error::BoxDynError> {
let julid = match <&[u8] as Decode<Sqlite>>::decode(value.to_owned().as_ref()) { let bytes = <&[u8] as Decode<Sqlite>>::decode(value)?;
Ok(bytes) => {
let bytes: [u8; 16] = bytes.try_into().unwrap_or_default(); let bytes: [u8; 16] = bytes.try_into().unwrap_or_default();
Julid::from_bytes(bytes) Ok(bytes.into())
}
_ => {
let string = <&str as Decode<Sqlite>>::decode(value)?;
Julid::from_str(string)?
}
};
Ok(julid)
} }
} }