Compare commits
10 commits
7067721f72
...
7dfe1ffb94
Author | SHA1 | Date | |
---|---|---|---|
|
7dfe1ffb94 | ||
|
e0150a2161 | ||
|
a54d704300 | ||
|
705afc19e9 | ||
|
6a9e43feee | ||
|
f2ade6d85e | ||
|
34f85a95a0 | ||
|
d9e68f057c | ||
|
f4ac603ac8 | ||
|
50a59e1898 |
10 changed files with 203 additions and 136 deletions
21
Cargo.toml
21
Cargo.toml
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "julid-rs"
|
||||
version = "1.6.180"
|
||||
version = "1.6.18033988"
|
||||
authors = ["Joe Ardent <code@ardent.nebcorp.com>"]
|
||||
edition = "2021"
|
||||
keywords = ["ulid", "library", "sqlite", "extension", "julid"]
|
||||
|
@ -11,8 +11,7 @@ license-file = "LICENSE.md"
|
|||
repository = "https://gitlab.com/nebkor/julid"
|
||||
|
||||
[features]
|
||||
default = ["chrono", "serde", "sqlx"] # just the regular crate
|
||||
chrono = ["dep:chrono"]
|
||||
default = ["serde", "sqlx"] # just the regular crate
|
||||
serde = ["dep:serde"]
|
||||
sqlx = ["dep:sqlx"]
|
||||
|
||||
|
@ -26,14 +25,26 @@ crate-type = ["cdylib", "rlib"]
|
|||
|
||||
[dependencies]
|
||||
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
|
||||
chrono = { version = "0.4", optional = true, default-features = false, features = ["std", "time"] }
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
sqlx = { version = "0.7", features = ["sqlite"], default-features = false, optional = true }
|
||||
sqlite-loadable = { version = "0.0.5", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
clap = { version = "4.3", default-features = false, features = ["help", "usage", "std", "derive"] }
|
||||
divan = "0.1"
|
||||
|
||||
[[bench]]
|
||||
name = "simple"
|
||||
harness = false
|
||||
|
||||
[[bin]]
|
||||
name = "julid-gen"
|
||||
path = "src/bin/gen.rs"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
|
116
README.md
116
README.md
|
@ -20,6 +20,8 @@ sqlite> select datetime(julid_timestamp(julid_new()), 'auto');
|
|||
2023-07-27 17:47:50
|
||||
sqlite> select julid_counter(julid_new());
|
||||
0
|
||||
sqlite> select julid_string();
|
||||
01HM4WJ7T90001P8SN9898FBTN
|
||||
```
|
||||
|
||||
Crates.io: <https://crates.io/crates/julid-rs>
|
||||
|
@ -71,9 +73,8 @@ are always fresh, it's not possible to easily guess a valid Julid if you already
|
|||
# How to use
|
||||
|
||||
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 above. There's
|
||||
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
|
||||
project's `Cargo.toml` file (say, by running `cargo add julid-rs`), and used as shown in the sample
|
||||
commandline program (see below). 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/),
|
||||
but let's go over them here, starting with the extension.
|
||||
|
||||
|
@ -83,6 +84,8 @@ The extension, when loaded into SQLite, provides the following functions:
|
|||
|
||||
* `julid_new()`: create a new Julid and return it as a 16-byte
|
||||
[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 was created (convenient for passing to the builtin `datetime()` function)
|
||||
* `julid_counter(julid)`: show the value of this julid's monotonic counter
|
||||
|
@ -119,11 +122,11 @@ For a table created like:
|
|||
create table if not exists watches (
|
||||
id blob not null primary key default (julid_new()),
|
||||
kind int not null, -- enum for movie or tv show or whatev
|
||||
title text not null, -- this has a secondary index
|
||||
title text not null,
|
||||
length int,
|
||||
release_date int,
|
||||
release_date date,
|
||||
added_by blob not null,
|
||||
last_updated int not null default (unixepoch()),
|
||||
last_updated date not null default CURRENT_TIMESTAMP,
|
||||
foreign key (added_by) references users (id)
|
||||
);
|
||||
```
|
||||
|
@ -140,63 +143,62 @@ 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
|
||||
file-backed DB in WAL mode and `NORMAL` durability settings.
|
||||
|
||||
## Inside a Rust program
|
||||
|
||||
Of course, you can also use it outside of a database; the `Julid` type is publicly exported. There's
|
||||
a simple benchmark in the examples folder of the repo, the important parts of which look like:
|
||||
|
||||
``` rust
|
||||
use julid::Julid;
|
||||
|
||||
fn main() {
|
||||
/* snip some stuff */
|
||||
|
||||
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");
|
||||
```
|
||||
|
||||
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
|
||||
$ cargo run --example=benchmark --release -- -n 30000 2> /dev/null
|
||||
30000 Julids generated in 1240us
|
||||
```
|
||||
|
||||
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
|
||||
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,
|
||||
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
|
||||
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
|
||||
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.
|
||||
|
||||
## Inside a Rust program
|
||||
|
||||
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`
|
||||
(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:
|
||||
|
||||
```
|
||||
$ julid-gen 4
|
||||
01HV2G2ATR000CJ2WESB7CVC19
|
||||
01HV2G2ATR000K1AGQPKMX5H0M
|
||||
01HV2G2ATR001CM27S59BHZ25G
|
||||
01HV2G2ATR001WPJ8BS7PZHE6A
|
||||
$ julid-gen -d 01HV2G2ATR001WPJ8BS7PZHE6A
|
||||
Created at: 2024-04-09 22:36:11.992 UTC
|
||||
Monotonic counter: 3
|
||||
Random: 14648252224908081354
|
||||
```
|
||||
|
||||
The help is useful:
|
||||
|
||||
```
|
||||
$ julid-gen -h
|
||||
Generate, print, and parse Julids
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
serializing/deserializing with [Serde](https://serde.rs/), via the `sqlx` and `serde` features,
|
||||
respectively.
|
||||
|
||||
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
|
||||
your application. You'll get a long and confusing runtime panic due to there being multiple
|
||||
entrypoints defined with the same name.
|
||||
|
||||
# Thanks
|
||||
|
||||
This project wouldn't have happened without a lot of inspiration (and a little shameless stealing)
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
1.6180
|
||||
1.618033988
|
||||
|
|
16
benches/simple.rs
Normal file
16
benches/simple.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
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");
|
||||
}
|
51
src/bin/gen.rs
Normal file
51
src/bin/gen.rs
Normal file
|
@ -0,0 +1,51 @@
|
|||
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}");
|
||||
}
|
||||
}
|
||||
}
|
43
src/julid.rs
43
src/julid.rs
|
@ -54,24 +54,20 @@ impl Julid {
|
|||
.as_millis() as u64;
|
||||
let last = LAST_MSB.load(Ordering::SeqCst);
|
||||
let ots = last >> COUNTER_BITS;
|
||||
if ots < ts {
|
||||
let msb = ts << COUNTER_BITS;
|
||||
if LAST_MSB
|
||||
.compare_exchange(last, msb, Ordering::SeqCst, Ordering::Relaxed)
|
||||
.is_ok()
|
||||
{
|
||||
break (msb, lsb).into();
|
||||
}
|
||||
let msb = if ots < ts {
|
||||
ts << COUNTER_BITS
|
||||
} 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();
|
||||
}
|
||||
(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
|
||||
let micros = thread_rng().gen_range(10..50);
|
||||
std::thread::sleep(Duration::from_micros(micros));
|
||||
|
@ -121,7 +117,6 @@ impl Julid {
|
|||
(self.0 & bitmask!(UNIQUE_BITS)) as u64
|
||||
}
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
/// Returns the timestamp as a `chrono::DateTime<chrono::Utc>` (feature
|
||||
/// `chrono` (default))
|
||||
pub fn created_at(&self) -> chrono::DateTime<chrono::Utc> {
|
||||
|
@ -144,7 +139,7 @@ impl Julid {
|
|||
/// ```rust
|
||||
/// use julid::julid::Julid;
|
||||
/// let text = "01D39ZY06FGSCTVN4T2V9PKHFZ";
|
||||
/// let id = Julid::from_string(text).unwrap();
|
||||
/// let id = Julid::from_str(text).unwrap();
|
||||
///
|
||||
/// assert_eq!(&id.to_string(), text);
|
||||
/// ```
|
||||
|
@ -161,11 +156,19 @@ impl Julid {
|
|||
/// ```rust
|
||||
/// use julid::julid::Julid;
|
||||
/// let text = "01D39ZY06FGSCTVN4T2V9PKHFZ";
|
||||
/// let result = Julid::from_string(text);
|
||||
/// let result = Julid::from_str(text);
|
||||
///
|
||||
/// assert!(result.is_ok());
|
||||
/// 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> {
|
||||
match base32::decode(encoded) {
|
||||
Ok(int_val) => Ok(Julid(int_val)),
|
||||
|
@ -236,7 +239,7 @@ impl FromStr for Julid {
|
|||
type Err = DecodeError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Julid::from_string(s)
|
||||
Julid::from_str(s)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -253,7 +256,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_static() {
|
||||
let s = Julid(0x41414141414141414141414141414141).as_string();
|
||||
let u = Julid::from_string(&s).unwrap();
|
||||
let u = Julid::from_str(&s).unwrap();
|
||||
assert_eq!(&s, "21850M2GA1850M2GA1850M2GA1");
|
||||
assert_eq!(u.0, 0x41414141414141414141414141414141);
|
||||
}
|
||||
|
|
34
src/lib.rs
34
src/lib.rs
|
@ -20,6 +20,10 @@ 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(
|
||||
|
@ -40,19 +44,25 @@ pub unsafe extern "C" fn sqlite3_julid_init(
|
|||
pub mod sqlite_plugin {
|
||||
use sqlite_loadable::{
|
||||
api, define_scalar_function,
|
||||
prelude::{sqlite3, sqlite3_context, sqlite3_value, FunctionFlags},
|
||||
prelude::{sqlite3_context, sqlite3_value, FunctionFlags},
|
||||
Result,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
pub(super) fn init_rs(db: *mut sqlite3) -> Result<()> {
|
||||
let flags = FunctionFlags::UTF8 | FunctionFlags::DETERMINISTIC;
|
||||
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, flags)?;
|
||||
define_scalar_function(
|
||||
db,
|
||||
"julid_string",
|
||||
-1,
|
||||
julid_string,
|
||||
FunctionFlags::UTF8 | FunctionFlags::INNOCUOUS,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -76,13 +86,17 @@ pub mod sqlite_plugin {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Return the human-readable base32 Crockford encoding of this Julid.
|
||||
/// 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.get(0) {
|
||||
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")
|
||||
|
@ -90,9 +104,7 @@ pub mod sqlite_plugin {
|
|||
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",
|
||||
));
|
||||
api::result_text(context, Julid::new().as_string())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -108,7 +120,7 @@ pub mod sqlite_plugin {
|
|||
/// 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) {
|
||||
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")
|
||||
|
@ -135,7 +147,7 @@ pub mod sqlite_plugin {
|
|||
/// 0
|
||||
/// ```
|
||||
pub fn julid_counter(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) -> Result<()> {
|
||||
if let Some(value) = id.get(0) {
|
||||
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")
|
||||
|
@ -159,7 +171,7 @@ pub mod sqlite_plugin {
|
|||
/// 110787724287475712
|
||||
/// ```
|
||||
pub fn julid_sortable(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) -> Result<()> {
|
||||
if let Some(value) = id.get(0) {
|
||||
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")
|
||||
|
|
|
@ -114,6 +114,6 @@ pub mod julid_as_str {
|
|||
D: Deserializer<'de>,
|
||||
{
|
||||
let deserialized_str = String::deserialize(deserializer)?;
|
||||
Julid::from_string(&deserialized_str).map_err(serde::de::Error::custom)
|
||||
Julid::from_str(&deserialized_str).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
|
17
src/sqlx.rs
17
src/sqlx.rs
|
@ -3,7 +3,7 @@ use std::borrow::Cow;
|
|||
use sqlx::{
|
||||
encode::IsNull,
|
||||
sqlite::{SqliteArgumentValue, SqliteValueRef},
|
||||
Decode, Encode, Sqlite,
|
||||
Decode, Encode, Sqlite, Value, ValueRef,
|
||||
};
|
||||
|
||||
use crate::Julid;
|
||||
|
@ -25,8 +25,17 @@ impl<'q> Encode<'q, Sqlite> for Julid {
|
|||
|
||||
impl Decode<'_, Sqlite> for Julid {
|
||||
fn decode(value: SqliteValueRef<'_>) -> Result<Self, sqlx::error::BoxDynError> {
|
||||
let bytes = <&[u8] as Decode<Sqlite>>::decode(value)?;
|
||||
let bytes: [u8; 16] = bytes.try_into().unwrap_or_default();
|
||||
Ok(bytes.into())
|
||||
let julid = match <&[u8] as Decode<Sqlite>>::decode(value.to_owned().as_ref()) {
|
||||
Ok(bytes) => {
|
||||
let bytes: [u8; 16] = bytes.try_into().unwrap_or_default();
|
||||
Julid::from_bytes(bytes)
|
||||
}
|
||||
_ => {
|
||||
let string = <&str as Decode<Sqlite>>::decode(value)?;
|
||||
Julid::from_str(string)?
|
||||
}
|
||||
};
|
||||
|
||||
Ok(julid)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue