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]
|
[package]
|
||||||
name = "julid-rs"
|
name = "julid-rs"
|
||||||
version = "1.6.180"
|
version = "1.6.18033988"
|
||||||
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,8 +11,7 @@ license-file = "LICENSE.md"
|
||||||
repository = "https://gitlab.com/nebkor/julid"
|
repository = "https://gitlab.com/nebkor/julid"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["chrono", "serde", "sqlx"] # just the regular crate
|
default = ["serde", "sqlx"] # just the regular crate
|
||||||
chrono = ["dep:chrono"]
|
|
||||||
serde = ["dep:serde"]
|
serde = ["dep:serde"]
|
||||||
sqlx = ["dep:sqlx"]
|
sqlx = ["dep:sqlx"]
|
||||||
|
|
||||||
|
@ -26,14 +25,26 @@ 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]
|
||||||
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]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
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
|
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>
|
||||||
|
@ -71,9 +73,8 @@ 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 above. There's
|
project's `Cargo.toml` file (say, by running `cargo add julid-rs`), and used as shown in the sample
|
||||||
a rudimentary [benchmark](https://gitlab.com/nebkor/julid/-/blob/main/examples/benchmark.rs) example
|
commandline program (see below). But the primary use case for me was as a loadable
|
||||||
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.
|
||||||
|
|
||||||
|
@ -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
|
* `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
|
||||||
|
@ -119,11 +122,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, -- this has a secondary index
|
title text not null,
|
||||||
length int,
|
length int,
|
||||||
release_date int,
|
release_date date,
|
||||||
added_by blob not null,
|
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)
|
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
|
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.
|
||||||
|
|
||||||
## 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
|
### Safety
|
||||||
There is one `unsafe fn` in this project, `sqlite_julid_init()`, and it is only built for the
|
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
|
`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
|
the C interface, which is inherently unsafe. If you are not building the plugin, there is no
|
||||||
`unsafe` code.
|
`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
|
# 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)
|
||||||
|
|
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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
src/julid.rs
33
src/julid.rs
|
@ -54,24 +54,20 @@ 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;
|
||||||
if ots < ts {
|
let msb = if ots < ts {
|
||||||
let msb = ts << COUNTER_BITS;
|
ts << COUNTER_BITS
|
||||||
if LAST_MSB
|
|
||||||
.compare_exchange(last, msb, Ordering::SeqCst, Ordering::Relaxed)
|
|
||||||
.is_ok()
|
|
||||||
{
|
|
||||||
break (msb, lsb).into();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
let counter = ((last & bitmask!(COUNTER_BITS) as u64) as u16).saturating_add(1);
|
let counter = ((last & bitmask!(COUNTER_BITS) as u64) as u16).saturating_add(1);
|
||||||
let msb = (ots << COUNTER_BITS) + counter as u64;
|
(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();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// 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));
|
||||||
|
@ -121,7 +117,6 @@ 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> {
|
||||||
|
@ -144,7 +139,7 @@ impl Julid {
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use julid::julid::Julid;
|
/// use julid::julid::Julid;
|
||||||
/// let text = "01D39ZY06FGSCTVN4T2V9PKHFZ";
|
/// let text = "01D39ZY06FGSCTVN4T2V9PKHFZ";
|
||||||
/// let id = Julid::from_string(text).unwrap();
|
/// let id = Julid::from_str(text).unwrap();
|
||||||
///
|
///
|
||||||
/// assert_eq!(&id.to_string(), text);
|
/// assert_eq!(&id.to_string(), text);
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -161,11 +156,19 @@ impl Julid {
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use julid::julid::Julid;
|
/// use julid::julid::Julid;
|
||||||
/// let text = "01D39ZY06FGSCTVN4T2V9PKHFZ";
|
/// let text = "01D39ZY06FGSCTVN4T2V9PKHFZ";
|
||||||
/// let result = Julid::from_string(text);
|
/// let result = Julid::from_str(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)),
|
||||||
|
@ -236,7 +239,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_string(s)
|
Julid::from_str(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,7 +256,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_string(&s).unwrap();
|
let u = Julid::from_str(&s).unwrap();
|
||||||
assert_eq!(&s, "21850M2GA1850M2GA1850M2GA1");
|
assert_eq!(&s, "21850M2GA1850M2GA1850M2GA1");
|
||||||
assert_eq!(u.0, 0x41414141414141414141414141414141);
|
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
|
/// 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(
|
||||||
|
@ -40,19 +44,25 @@ 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, sqlite3_context, sqlite3_value, FunctionFlags},
|
prelude::{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;
|
let flags = FunctionFlags::UTF8 | FunctionFlags::DETERMINISTIC | FunctionFlags::INNOCUOUS;
|
||||||
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(db, "julid_string", 1, julid_string, flags)?;
|
define_scalar_function(
|
||||||
|
db,
|
||||||
|
"julid_string",
|
||||||
|
-1,
|
||||||
|
julid_string,
|
||||||
|
FunctionFlags::UTF8 | FunctionFlags::INNOCUOUS,
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -76,13 +86,17 @@ pub mod sqlite_plugin {
|
||||||
Ok(())
|
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
|
/// ```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.get(0) {
|
if let Some(value) = id.first() {
|
||||||
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")
|
||||||
|
@ -90,9 +104,7 @@ 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 {
|
||||||
return Err(sqlite_loadable::Error::new_message(
|
api::result_text(context, Julid::new().as_string())?;
|
||||||
"Could not convert empty Julid to string",
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -108,7 +120,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.get(0) {
|
if let Some(value) = id.first() {
|
||||||
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")
|
||||||
|
@ -135,7 +147,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.get(0) {
|
if let Some(value) = id.first() {
|
||||||
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")
|
||||||
|
@ -159,7 +171,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.get(0) {
|
if let Some(value) = id.first() {
|
||||||
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")
|
||||||
|
|
|
@ -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_string(&deserialized_str).map_err(serde::de::Error::custom)
|
Julid::from_str(&deserialized_str).map_err(serde::de::Error::custom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
15
src/sqlx.rs
15
src/sqlx.rs
|
@ -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,
|
Decode, Encode, Sqlite, Value, ValueRef,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::Julid;
|
use crate::Julid;
|
||||||
|
@ -25,8 +25,17 @@ 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 bytes = <&[u8] as Decode<Sqlite>>::decode(value)?;
|
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();
|
let bytes: [u8; 16] = bytes.try_into().unwrap_or_default();
|
||||||
Ok(bytes.into())
|
Julid::from_bytes(bytes)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let string = <&str as Decode<Sqlite>>::decode(value)?;
|
||||||
|
Julid::from_str(string)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(julid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue