#sqlite-extension #unique-identifier #ulid #extension #library #julid

bin+lib julid-rs

一个用于SQLite的crate和可加载扩展,提供Joe的ULID

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 数据库接口

Download history 10/week @ 2024-04-13 2/week @ 2024-05-18 27/week @ 2024-06-29 90/week @ 2024-07-27

每月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 bit structure

根据ULID规范,对于在同一毫秒创建的ULID,每个新ID的最低有效位都应该递增。由于ULID的这一部分是随机的,这意味着你可能需要在时间戳部分溢出才能递增它。同样,通过递增一个已知的ULID,你可以轻松猜测一个新的可能有效的ULID。最后,这意味着排序需要读取ULID的末尾,对于在同一毫秒创建的ID。

为了解决这些不足,Julid(Joe的ULID)具有以下结构

Julid bit structure

与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 功能包括通过 sqlxserde 功能将 Julid 插入到 SQLite 和从中检索的特质的实现,以及通过 SQLxSerde 进行一般序列化和反序列化。

需要注意的一点是:如果您在使用此 crate 的 Rust 应用程序内部,请勿在 Cargo.toml 中启用 plugin 功能,尤其是如果您还在应用程序中将它作为 SQLite 的扩展加载。您将得到一个漫长且令人困惑的运行时恐慌,因为定义了具有相同名称的多个入口点。

感谢

这个项目没有很多灵感(以及一点点无耻的窃取)来自 ulid-rs crate。对于可加载的扩展,sqlite-loadable-rs crate 使其变得非常容易编写;原本我以为需要几天时间,结果只花了几个小时。感谢这些 crate 的作者!随时欢迎从我这里窃取代码!

依赖项

~32–44MB
~764K SLoC