9个版本 (稳定)
1.6.18033988 | 2024年4月9日 |
---|---|
1.6.1803398 | 2024年1月14日 |
1.6.180339 | 2023年12月29日 |
1.6.1803 | 2023年9月3日 |
0.0.1 | 2023年7月26日 |
#177 in 数据库接口
每月115次下载
40KB
595 行
直接了当
Julid是全局唯一的、可排序的标识符,与ULID向后兼容。此crate提供Rust Julid数据类型,以及用于创建和查询它们的SQLite可加载扩展
$ sqlite3
SQLite version 3.40.1 2022-12-28 14:03:47
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> .load ./libjulid
sqlite> select hex(julid_new());
018998768ACF000060B31DB175E0C5F9
sqlite> select julid_string(julid_new());
01H6C7D9CT00009TF3EXXJHX4Y
sqlite> select julid_seconds(julid_new());
1690480066.208
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
Docs.rs: https://docs.rs/julid-rs/latest/julid/
博客文章: https://proclamations.nebcorp-hias.com/sundries/presenting-julids/
更深入的了解
Julid是ULID的直接替代品:所有Julid都是有效的ULID,但并非所有ULID都是有效的Julid。
由于它们的兼容关系,Julid和ULID必须有相当多的共同点,确实如此
- 它们是128位的长度
- 它们是按字典顺序可排序的
- 它们将创建时间编码为自UNIX纪元以来的毫秒数,位于它们的最高48位
- 它们的字符串表示是它们的字节的大端表示的26字符base-32 Crockford编码
- 在同一毫秒内创建的ID仍然按照它们的创建顺序排序
Julid和ULID有不同的方式来实现这一点。如果你查看ULID中位的布局,你会看到
根据ULID规范,对于在同一毫秒创建的ULID,每个新ID的最低有效位都应该递增。由于ULID的这一部分是随机的,这意味着你可能需要在时间戳部分溢出才能递增它。同样,通过递增一个已知的ULID,你可以轻松猜测一个新的可能有效的ULID。最后,这意味着排序需要读取ULID的末尾,对于在同一毫秒创建的ID。
为了解决这些不足,Julid(Joe的ULID)具有以下结构
与ULID一样,最显著的48位编码了创建时间。与ULID不同,接下来的16位最显著的位不是随机的,它是同一毫秒内创建的ID的单调计数器。由于它只有16位,当65,536个ID在毫秒内创建后就会饱和,之后,同一毫秒的ID将不会有一个固有的总顺序(随机位仍然不同,所以不应该有冲突)。我的电脑性能不错,但每毫秒只能生成大约20,000个,所以希望这不会成为问题!因为随机位总是新鲜的,如果你已经知道一个Julid,就不可能轻易猜测出另一个有效的Julid。
如何使用
Julid crate可以以两种不同的方式使用:作为一个常规的Rust库,在Rust项目的Cargo.toml
文件中声明(例如,通过运行cargo add julid-rs
),以及按照下面的示例命令行程序使用(见下文)。但对我来说,主要的用途是作为可加载的SQLite扩展。两者都在文档中有介绍,但让我们在这里简要说明,首先是扩展。
作为SQLite的可加载扩展内部的
当扩展加载到SQLite中时,提供了以下功能
julid_new()
:创建一个新的Julid,并以16字节的blob形式返回julid_string()
:创建一个新的Julid,并以26字符的base-32 Crockford编码的字符串形式返回julid_seconds(julid)
:获取从UNIX纪元以来的秒数(作为64位浮点数),这是该Julid创建的时间(方便传递给内置的datetime()
函数)julid_counter(julid)
:显示这个Julid的单调计数器的值julid_sortable(julid)
:返回时间戳和计数器的64位连接julid_string(julid)
:显示这个Julid的base-32 Crockford编码;Julid的原始字节不会是有效的UTF-8,所以使用这个或内置的hex()
函数来选择
一个可读的表示形式
构建和加载
如果你想要将其用作SQLite扩展
- 克隆仓库
- 使用
cargo build --features plugin
(这构建了SQLite扩展)来构建它 - 将生成的
libjulid.[so|dylib|whatevs]
复制到可以...的地方 - 如上所示,将其加载到SQLite中,使用
.load /path/to/libjulid
- 派对
如果你像我一样,希望将Julid用作主键,只需创建你的表,就像
create table users (
id blob not null primary key default (julid_new()),
...
);
这样,你就有了一张直达Julid城市的头等票,宝贝!
对于创建的表
-- table of things to watch
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,
length int,
release_date date,
added_by blob not null,
last_updated date not null default CURRENT_TIMESTAMP,
foreign key (added_by) references users (id)
);
然后是 一些代码,将这些行插入到该表中,如下所示
insert into watches (kind, title, length, release_date, added_by) values (?,?,?,?,?)
其中通配符在循环中与唯一值绑定,Julid 的 id
字段由扩展为每一行生成,当使用支持 WAL 模式和 NORMAL
可靠性设置的文件支持数据库时,我可以达到每秒超过 100,000 次插入。
安全性
本项目有一个 unsafe fn
,即 sqlite_julid_init()
,并且它只为 plugin
功能构建。原因是它通过 C 接口与外部代码(SQLite 本身)交互,这是固有的不安全的。如果您没有构建插件,则没有 unsafe
代码。
Rust 程序内部
当然,您也可以在数据库外使用它;Julid
类型是公开导出的。在 src/bin/gen.rs
中有一个简单的命令行程序,可以像这样运行 cargo run --bin julid-gen
(或者您也可以运行 cargo install julid-rs
以在您的计算机上安装 julid-gen
程序),这将生成并打印一个 Julid。如果您想查看其组件部分,请捕获打印出的 Julid,然后使用 -d
标志运行它
$ 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
帮助信息很有用
$ 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
整个程序只有 34 行,所以请查看。
默认的可选 Cargo 功能包括通过 sqlx
和 serde
功能将 Julid 插入到 SQLite 和从中检索的特质的实现,以及通过 SQLx 和 Serde 进行一般序列化和反序列化。
需要注意的一点是:如果您在使用此 crate 的 Rust 应用程序内部,请勿在 Cargo.toml 中启用 plugin
功能,尤其是如果您还在应用程序中将它作为 SQLite 的扩展加载。您将得到一个漫长且令人困惑的运行时恐慌,因为定义了具有相同名称的多个入口点。
感谢
这个项目没有很多灵感(以及一点点无耻的窃取)来自 ulid-rs crate。对于可加载的扩展,sqlite-loadable-rs crate 使其变得非常容易编写;原本我以为需要几天时间,结果只花了几个小时。感谢这些 crate 的作者!随时欢迎从我这里窃取代码!
依赖项
~32–44MB
~764K SLoC