Skip to content

Commit 5c70099

Browse files
authored
improve all docs and readme (#7)
1 parent f65f499 commit 5c70099

File tree

5 files changed

+200
-56
lines changed

5 files changed

+200
-56
lines changed

README.md

Lines changed: 113 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ SELECT id FROM users; -- user_2accvpp5guht4dts56je5a
3333
SELECT id FROM users WHERE id = 'user_2accvpp5guht4dts56je5a';
3434
```
3535

36-
Plays nice with your server code too, no extra work needed:
36+
Plays nice with your server code, no extra work needed:
3737
```python
3838
with psycopg.connect("postgresql://...") as conn:
3939
res = conn.execute("SELECT id FROM users").fetchone()
@@ -79,7 +79,7 @@ Key changes relative to ULID:
7979
```
8080

8181
### Collision
82-
Relative to ULID, the time precision is reduced from 48 to 40 bits (keeping the most significant bits, so oveflow still won't occur until 10889 AD), and the randomness reduced from 80 to 64 bits.
82+
Relative to ULID, the time precision is reduced from 48 to 40 bits (keeping the most significant bits, so overflow still won't occur until 10889 AD), and the randomness reduced from 80 to 64 bits.
8383

8484
The timestamp precision at 40 bits is around 250 milliseconds. In order to have a 50% probability of collision with 64 bits of randomness, you would need to generate around **4 billion items per 250 millisecond window**.
8585

@@ -104,8 +104,41 @@ from upid import upid
104104
upid("user")
105105
```
106106

107+
Or more explicitly:
108+
```python
109+
from upid import UPID
110+
UPID.from_prefix("user")
111+
```
112+
113+
Or specifying your own timestamp or datetime
114+
```python
115+
import time, datetime
116+
UPID.from_prefix_and_milliseconds("user", milliseconds)
117+
UPID.from_prefix_and_datetime("user", datetime.datetime.now())
118+
```
119+
120+
From and to a string:
121+
```python
122+
u = UPID.from_str("user_2accvpp5guht4dts56je5a")
123+
u.to_str() # user_2a...
124+
```
125+
126+
Get stuff out:
127+
```python
128+
u.prefix # user
129+
u.datetime # 2024-07-07 ...
130+
```
131+
132+
Convert to other formats:
133+
```python
134+
int(u) # 2079795568564925668398930358940603766
135+
u.hex # 01908dd6a3669b912738191ea3d61576
136+
u.to_uuid() # UUID('01908dd6-a366-9b91-2738-191ea3d61576')
137+
```
138+
107139
#### Development
108-
Code and tests are in the [py/](./py/) directory. Using [Rye](https://rye.astral.sh/) for development (installation instructions at the link).
140+
Code and tests are in the [py/](./py/) directory.
141+
Using [Rye](https://rye.astral.sh/) for development (installation instructions at the link).
109142

110143
```bash
111144
# can be run from the repo root
@@ -118,6 +151,8 @@ If you just want to have a look around, pip should also work:
118151
pip install -e .
119152
```
120153

154+
Please open a PR if you spot a bug or improvement!
155+
121156
## Rust implementation
122157
The current Rust implementation is based on [dylanhart/ulid-rs](https://github.com/dylanhart/ulid-rs), but using the same lookup base32 lookup method as the Python implementation.
123158

@@ -132,6 +167,31 @@ use upid::Upid;
132167
Upid::new("user");
133168
```
134169

170+
Or specifying your own timestamp or datetime:
171+
```rust
172+
use std::time::SystemTime;
173+
Upid::from_prefix_and_milliseconds("user", 1720366572288);
174+
Upid::from_prefix_and_datetime("user", SystemTime::now());
175+
```
176+
177+
From and to a string:
178+
```rust
179+
let u = Upid::from_string("user_2accvpp5guht4dts56je5a");
180+
u.to_string();
181+
```
182+
183+
Get stuff out:
184+
```rust
185+
u.prefix(); // user
186+
u.datetime(); // 2024-07-07 ...
187+
u.milliseconds(); // 17203...
188+
```
189+
190+
Convert to other formats:
191+
```rust
192+
u.to_bytes();
193+
```
194+
135195
#### Development
136196
Code and tests are in the [upid_rs/](./upid_rs/) directory.
137197

@@ -140,48 +200,80 @@ cd upid_rs
140200
cargo check # or fmt/clippy/build/test/run
141201
```
142202

203+
Please open a PR if you spot a bug or improvement!
204+
143205
## Postgres extension
144206
There is also a Postgres extension built on the Rust implementation, using [pgrx](https://github.com/pgcentralfoundation/pgrx) and based on the very similar extension [pksunkara/pgx_ulid](https://github.com/pksunkara/pgx_ulid).
145207

146208
#### Installation
147-
You can try out the Docker image [carderne/postgres-upid:16](https://hub.docker.com/r/carderne/postgres-upid):
209+
The easiest would be to try out the Docker image [carderne/postgres-upid:16](https://hub.docker.com/r/carderne/postgres-upid), currently built for arm64 and amd64 but only for Postgres 16:
148210
```bash
149211
docker run -e POSTGRES_HOST_AUTH_METHOD=trust -p 5432:5432 carderne/postgres-upid:16
150212
```
151213

152-
If you want to install it into another Postgres, you'll install pgrx and follow its [installation instructions](https://github.com/pgcentralfoundation/pgrx/blob/develop/cargo-pgrx/README.md).
153-
Something like this:
154-
```bash
155-
cargo install --locked cargo-pgrx
156-
pgrx init
157-
cd upid_pg
158-
pgrx install
159-
```
214+
You can also grab a Linux `.deb` from the [Releases](https://github.com/carderne/upid/releases) page. This is built for Postgres 16 and amd64 only.
160215

161-
Installable binaries will come soon.
216+
More architectures and versions will follow once it is out of alpha.
162217

163218
#### Usage
164219
```sql
165-
CREATE EXTENSION ulid;
166-
220+
CREATE EXTENSION upid_pg;
167221

168222
CREATE TABLE users (
169223
id upid NOT NULL DEFAULT gen_upid('user') PRIMARY KEY,
170224
name text NOT NULL
171225
);
226+
172227
INSERT INTO users (name) VALUES('Bob');
228+
173229
SELECT * FROM users;
230+
-- id | name
231+
-- -----------------------------+------
232+
-- user_2accvpp5guht4dts56je5a | Bob
174233
```
175234

176-
#### Development
177-
Code and tests are in the [upid_pg/](./upid_pg/) directory.
235+
You can get the raw `bytea` data, or the prefix or timestamp:
236+
```sql
237+
SELECT upid_to_bytea(id) FROM users;
238+
-- \x019...
178239

240+
SELECT upid_to_prefix(id) FROM users;
241+
-- 'user'
242+
243+
SELECT upid_to_timestamp(id) FROM users;
244+
-- 2024-07-07 ...
245+
```
246+
247+
You can convert a `UPID` to a regular Postgres `UUID`:
248+
```sql
249+
SELECT upid_to_uuid(gen_upid('user'));
250+
```
251+
252+
Or the reverse (although the prefix and timestamp will no longer make sense):
253+
```sql
254+
select upid_from_uuid(gen_random_uuid());
255+
```
256+
257+
#### Development
258+
If you want to install it into another Postgres, you'll install pgrx and follow its [installation instructions](https://github.com/pgcentralfoundation/pgrx/blob/develop/cargo-pgrx/README.md).
259+
Something like this:
179260
```bash
180261
cd upid_pg
262+
cargo install --locked cargo-pgrx
263+
cargo pgrx init
264+
cargo pgrx install
265+
```
266+
267+
Some `cargo` commands work as normal:
268+
```bash
181269
cargo check # or fmt/clippy
270+
```
271+
272+
But building, testing and running must be done via pgrx.
273+
This will compile it into a Postgres installation, and allow an interactive session and tests there.
182274

183-
# must test/run/install with pgrx
184-
# this will compile it into a Postgres installation
185-
# and run the tests there, or drop you into a psql prompt
186-
cargo pgrx test # or run/install
275+
```bash
276+
cargo pgrx test pg16
277+
# or run
278+
# or install
187279
```

py/upid/core.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ class UPID:
2121
"""
2222
The `UPID` contains a 20-bit prefix, 40-bit timestamp and 68 bits of randomness.
2323
24-
The prefix should only contain lower-case latin alphabet characters.
24+
The prefix should only contain lower-case latin alphabet characters and be max
25+
four characters long.
2526
2627
It is usually created using the `upid(prefix: str)` helper function:
2728
@@ -78,10 +79,19 @@ def from_prefix_and_milliseconds(cls: type[Self], prefix: str, milliseconds: int
7879

7980
@classmethod
8081
def from_str(cls: type[Self], string: str) -> Self:
82+
"""
83+
Convert the provided `str` to a `UPID`.
84+
85+
Throws a `ValueError` if the string is invalid:
86+
- too long
87+
- too short
88+
- contains characters not in the `ENCODE` base32 alphabet
89+
"""
8190
return cls(b32.decode(string))
8291

8392
@property
8493
def prefix(self) -> str:
94+
"""Return just the prefix as a `str`."""
8595
prefix, _ = b32.encode_prefix(self.b[b32.END_RANDO_BIN :])
8696
return prefix
8797

@@ -99,14 +109,18 @@ def datetime(self) -> dt.datetime:
99109
def hex(self) -> str:
100110
return self.b.hex()
101111

112+
def to_str(self) -> str:
113+
return b32.encode(self.b)
114+
102115
def to_uuid(self) -> uuid.UUID:
116+
"""Convert to a standard Python UUID."""
103117
return uuid.UUID(bytes=self.b)
104118

105119
def __repr__(self) -> str:
106120
return f"UPID({self!s})"
107121

108122
def __str__(self) -> str:
109-
return b32.encode(self.b)
123+
return self.to_str()
110124

111125
def __int__(self) -> int:
112126
return int.from_bytes(self.b, "big")

upid_pg/src/lib.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
//! # upid_pg
22
//!
33
//! `upid_pg` is a thin wrapper for [upid](https://crates.io/crates/upid)
4-
//! providing the UPID datatype and generator as a Postgres extension
5-
//!
6-
//! The code below is based largely on the following:
7-
//! https://github.com/pksunkara/pgx_ulid
4+
//! providing the UPID datatype and generator as a Postgres extension.
5+
6+
// The code below is based largely on the following:
7+
// https://github.com/pksunkara/pgx_ulid
88

99
use core::ffi::CStr;
1010
use inner_upid::Upid as InnerUpid;
@@ -105,6 +105,11 @@ fn upid_to_bytea(input: upid) -> Vec<u8> {
105105
bytes.to_vec()
106106
}
107107

108+
#[pg_extern(immutable, parallel_safe)]
109+
fn upid_to_prefix(input: upid) -> String {
110+
InnerUpid(input.0).prefix()
111+
}
112+
108113
#[pg_extern(immutable, parallel_safe)]
109114
fn upid_to_timestamp(input: upid) -> Timestamp {
110115
let inner_seconds = (InnerUpid(input.0).milliseconds() as f64) / 1000.0;

upid_rs/src/b32.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ const RANDO_CHAR_LEN: usize = 13;
1414
const VERSION_CHAR_LEN: usize = 1;
1515

1616
/// Length of a string-encoded Upid
17-
pub const CHAR_LEN: usize = 26;
17+
const CHAR_LEN: usize = 26;
1818

1919
/// 32-character alphabet modified from Crockford's
2020
/// Numbers first for sensible sorting, but full lower-case
2121
/// latin alphabet so any sensible prefix can be used
2222
/// Effectively a mapping from 8 bit byte -> 5 bit int -> base32 character
23-
const ENCODE: &[u8; 32] = b"234567abcdefghijklmnopqrstuvwxyz";
23+
pub const ENCODE: &[u8; 32] = b"234567abcdefghijklmnopqrstuvwxyz";
2424

2525
/// Speedy O(1) inverse lookup
2626
/// base32 char -> ascii byte int -> base32 alphabet index

0 commit comments

Comments
 (0)