diff --git a/.cargo/config b/.cargo/config
deleted file mode 100644
index 2b25fcd..0000000
--- a/.cargo/config
+++ /dev/null
@@ -1,3 +0,0 @@
-[build]
-# Postgres symbols won't be available until runtime
-rustflags = ["-C", "link-args=-Wl,-undefined,dynamic_lookup"]
diff --git a/.cargo/config.toml b/.cargo/config.toml
new file mode 100644
index 0000000..13c456b
--- /dev/null
+++ b/.cargo/config.toml
@@ -0,0 +1,3 @@
+[target.'cfg(target_os="macos")']
+# Postgres symbols won't be available until runtime
+rustflags = ["-Clink-arg=-Wl,-undefined,dynamic_lookup"]
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..b4efeae
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,8 @@
+.git
+.github
+.gitignore
+*.md
+*.yml
+
+target
+Cargo.lock
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..512dce1
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,58 @@
+name: CI
+on:
+ pull_request:
+ branches: [main, rewrite]
+ types: [opened, reopened, synchronize]
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+jobs:
+ ci:
+ name: CI
+ needs: [test, lint, lockfile]
+ runs-on: ubuntu-latest
+ steps:
+ - name: Done
+ run: exit 0
+ test:
+ name: Tests
+ strategy:
+ fail-fast: false
+ matrix:
+ postgres: [14, 15, 16]
+ runner:
+ - ubuntu-22.04
+ - buildjet-8vcpu-ubuntu-2204-arm
+ runs-on: ${{ matrix.runner }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ - name: Build
+ run: docker buildx build --build-arg PG_MAJOR=${{ matrix.postgres }} -t test .
+ - name: Test
+ run: docker run test cargo pgrx test pg${{ matrix.postgres }}
+ lint:
+ name: Linting (fmt + clippy)
+ runs-on: ubuntu-latest
+ steps:
+ - name: Install rust
+ uses: dtolnay/rust-toolchain@1.74.0
+ with:
+ components: rustfmt, clippy
+ - name: Checkout
+ uses: actions/checkout@v3
+ - name: Format check
+ uses: actions-rs/cargo@v1
+ with:
+ command: fmt
+ args: -- --check
+
+ lockfile:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ - name: Install rust
+ uses: dtolnay/rust-toolchain@1.74.0
+ - name: Lockfile check
+ run: cargo update -w --locked
\ No newline at end of file
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..57db33c
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,120 @@
+env:
+ NAME: uids_postgres
+ EXT_NAME: ulid
+ PKG_NAME: uids_postgres
+name: Release
+on:
+ push:
+ tags: [v*]
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+jobs:
+ create-release:
+ name: Create release
+ runs-on: ubuntu-latest
+ outputs:
+ upload_url: ${{ steps.create-release.outputs.upload_url }}
+ steps:
+ - name: Create Release
+ id: create-release
+ uses: actions/create-release@v1
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ with:
+ tag_name: ${{ github.ref }}
+ release_name: ${{ github.ref }}
+ build-linux-gnu:
+ name: Build & Release for linux
+ needs:
+ - create-release
+ strategy:
+ fail-fast: false
+ matrix:
+ postgres: [14, 15, 16]
+ box:
+ - runner: ubuntu-22.04
+ arch: amd64
+ - runner: buildjet-8vcpu-ubuntu-2204-arm
+ arch: arm64
+ runs-on: ${{ matrix.box.runner }}
+ timeout-minutes: 45
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ - name: Install rust
+ uses: dtolnay/rust-toolchain@1.74.0
+ - name: Install dependencies
+ run: |
+ # Add postgres package repo
+ sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
+ wget -qO- https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo tee /etc/apt/trusted.gpg.d/pgdg.asc &>/dev/null
+
+ sudo apt-get update
+ sudo apt-get install -y --no-install-recommends git build-essential libpq-dev curl libreadline6-dev zlib1g-dev pkg-config cmake
+ sudo apt-get install -y --no-install-recommends libreadline-dev zlib1g-dev flex bison libxml2-dev libxslt-dev libssl-dev libxml2-utils xsltproc ccache
+ sudo apt-get install -y --no-install-recommends clang libclang-dev llvm-dev gcc tree
+
+ # Install requested postgres version
+ sudo apt-get install -y postgresql-${{ matrix.postgres }} postgresql-server-dev-${{ matrix.postgres }} -y
+
+ # Ensure installed pg_config is first on path
+ export PATH=$PATH:/usr/lib/postgresql/${{ matrix.postgres }}/bin
+
+ cargo install cargo-pgrx --version 0.11.2 --locked
+ cargo pgrx init --pg${{ matrix.postgres }}=/usr/lib/postgresql/${{ matrix.postgres }}/bin/pg_config
+ - name: Build artifacts
+ run: |
+ # selects the pgVer from pg_config on path
+ # https://github.com/tcdi/pgrx/issues/288
+ cargo pgrx package --no-default-features --features pg${{ matrix.postgres }}
+
+ # Create installable package
+ mkdir archive
+ cp `find target/release -type f -name "${{ env.EXT_NAME }}*"` archive
+
+ # Copy files into directory structure
+ mkdir -p package/usr/lib/postgresql/lib
+ mkdir -p package/var/lib/postgresql/extension
+ cp archive/*.so package/usr/lib/postgresql/lib
+ cp archive/*.control package/var/lib/postgresql/extension
+ cp archive/*.sql package/var/lib/postgresql/extension
+
+ # symlinks to Copy files into directory structure
+ mkdir -p package/usr/lib/postgresql/${{ matrix.postgres }}/lib
+ cd package/usr/lib/postgresql/${{ matrix.postgres }}/lib
+ cp -s ../../lib/*.so .
+ cd ../../../../../..
+
+ mkdir -p package/usr/share/postgresql/${{ matrix.postgres }}/extension
+ cd package/usr/share/postgresql/${{ matrix.postgres }}/extension
+
+ cp -s ../../../../../var/lib/postgresql/extension/${{ env.EXT_NAME }}.control .
+ cp -s ../../../../../var/lib/postgresql/extension/${{ env.EXT_NAME }}*.sql .
+ cd ../../../../../..
+
+ # Create install control file
+ extension_version=${{ github.ref_name }}
+ # strip the leading v
+ deb_version=${extension_version:1}
+
+ mkdir -p package/DEBIAN
+ touch package/DEBIAN/control
+ echo 'Package: ${{ env.PKG_NAME }}' >> package/DEBIAN/control
+ echo 'Version:' ${deb_version} >> package/DEBIAN/control
+ echo 'Architecture: ${{ matrix.box.arch }}' >> package/DEBIAN/control
+ echo 'Maintainer: Pavan Sunkara' >> package/DEBIAN/control
+ echo 'Description: A PostgreSQL extension for ULID' >> package/DEBIAN/control
+
+ # Create deb package
+ sudo chown -R root:root package
+ sudo chmod -R 00755 package
+ sudo dpkg-deb -Zxz --build --root-owner-group package
+ - name: Upload artifacts
+ uses: actions/upload-release-asset@v1
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ with:
+ upload_url: ${{ needs.create-release.outputs.upload_url }}
+ asset_path: ./package.deb
+ asset_name: ${{ env.NAME }}-${{ github.ref_name }}-pg${{ matrix.postgres }}-${{ matrix.box.arch }}-linux-gnu.deb
+ asset_content_type: application/vnd.debian.binary-package
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
deleted file mode 100644
index 1ff8bf7..0000000
--- a/.vscode/settings.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "conventionalCommits.scopes": [
- "main",
- "readme",
- "nanoid"
- ]
-}
diff --git a/Cargo.toml b/Cargo.toml
index fa645de..e4b7f82 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,33 +1,45 @@
[package]
name = "uids"
-version = "0.2.0"
+version = "0.0.0"
edition = "2021"
-description = "PostgreSQL Extension to generate various type of Unique IDS."
[lib]
crate-type = ["cdylib"]
[features]
-default = ["pg13"]
-pg10 = ["pgx/pg10", "pgx-tests/pg10"]
-pg11 = ["pgx/pg11", "pgx-tests/pg11"]
-pg12 = ["pgx/pg12", "pgx-tests/pg12"]
-pg13 = ["pgx/pg13", "pgx-tests/pg13"]
-pg14 = ["pgx/pg14", "pgx-tests/pg14"]
+default = ["pg16"]
+pg11 = ["pgrx/pg11", "pgrx-tests/pg11"]
+pg12 = ["pgrx/pg12", "pgrx-tests/pg12"]
+pg13 = ["pgrx/pg13", "pgrx-tests/pg13"]
+pg14 = ["pgrx/pg14", "pgrx-tests/pg14"]
+pg15 = ["pgrx/pg15", "pgrx-tests/pg15"]
+pg16 = ["pgrx/pg16", "pgrx-tests/pg16"]
pg_test = []
[dependencies]
nanoid = "0.4.0"
-pgx = "0.4.5"
-rusty_ulid = "1.0.0"
-svix-ksuid = "0.6.0"
+pgrx = "=0.11.4"
+rusty_ulid = "2.0.0"
+svix-ksuid = "0.8.0"
+type-safe-id = "0.3.0"
+uuid = { version = "1.8.0", features = [
+ "v7",
+ "v4",
+ "v6",
+ "v8",
+ "fast-rng",
+ "macro-diagnostics",
+] }
+getrandom = "0.2"
+cuid2 = "0.1.2"
+timeflake-rs = "0.3.0"
+pushid = "0.0.1"
[dev-dependencies]
-pgx-tests = "0.4.5"
+pgrx-tests = "=0.11.4"
[profile.dev]
panic = "unwind"
-lto = "thin"
[profile.release]
panic = "unwind"
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..b4b92de
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,51 @@
+ARG PG_MAJOR
+
+FROM postgres:${PG_MAJOR}
+
+RUN apt-get update
+
+ENV build_deps ca-certificates \
+ git \
+ build-essential \
+ libpq-dev \
+ postgresql-server-dev-${PG_MAJOR} \
+ curl \
+ libreadline6-dev \
+ zlib1g-dev
+
+RUN apt-get install -y --no-install-recommends $build_deps pkg-config cmake
+
+WORKDIR /home/postgres
+
+ENV HOME=/home/postgres
+ENV PATH=/home/postgres/.cargo/bin:$PATH
+
+RUN chown postgres:postgres /home/postgres
+
+USER postgres
+
+RUN \
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path --profile minimal --default-toolchain 1.74.0 && \
+ rustup --version && \
+ rustc --version && \
+ cargo --version
+
+# pgrx
+RUN cargo install cargo-pgrx --version 0.11.2 --locked
+
+RUN cargo pgrx init --pg${PG_MAJOR} $(which pg_config)
+
+USER root
+
+COPY . .
+
+RUN cargo pgrx install
+
+RUN chown -R postgres:postgres /home/postgres
+RUN chown -R postgres:postgres /usr/share/postgresql/${PG_MAJOR}/extension
+RUN chown -R postgres:postgres /usr/lib/postgresql/${PG_MAJOR}/lib
+
+USER postgres
+
+ENV POSTGRES_HOST_AUTH_METHOD=trust
+ENV USER=postgres
\ No newline at end of file
diff --git a/README.md b/README.md
index 7608897..2e9c1fe 100644
--- a/README.md
+++ b/README.md
@@ -1,152 +1,449 @@
-# PostgreSQL Extension to generate various type of Unique IDS.
+Here's the updated README with the corrected links in the table:
-## 1. Supported IDs
+# PostgreSQL Extension for Generating Unique IDs
-1. [NanoId](https://github.com/ai/nanoid)
+
+ uids
+
-2. [Ksuid](https://github.com/segmentio/ksuid)
+```sql
+postgres=# CREATE EXTENSION uids;
+CREATE EXTENSION
+postgres=# SELECT generate_typeid('user');
+ generate_typeid
+--------------------------------------
+ user_01h2xcejqtf2nbrexx3vqjhp41
+```
+
+## Description
+
+| Methodology | Function | Crate | Description |
+|---------------------------|---------------------------------------------|--------------------------------------|----------------------------------------------------------|
+| [UUID v6][uuidv6] | `generate_uuidv6()` | [`uuid`][crate-uuid] | UUID v6 ([RFC 4122][rfc-4122-update]) |
+| | `generate_uuidv6_text()` | | UUID v6 as text |
+| | `generate_uuidv6_uuid()` | | UUID v6 as Postgres UUID object |
+| [UUID v7][uuidv7] | `generate_uuidv7()` | [`uuid`][crate-uuid] | UUID v7 ([RFC 4122][rfc-4122-update]) |
+| | `generate_uuidv7_bytes()` | | UUID v7 as bytes |
+| | `generate_uuidv7_from_string(TEXT)` | | Generate UUID v7 from a string |
+| | `parse_uuidv7(TEXT)` | | Parse UUID v7 |
+| [NanoId][nanoid] | `generate_nanoid()` | [`nanoid`][crate-nanoid] | NanoID, developed by [Andrey Sitnik][github-ai] |
+| | `generate_nanoid_length(INT)` | | NanoID with a custom length |
+| | `generate_nanoid_c(TEXT)` | | NanoID with custom alphabets |
+| | `generate_nanoid_length_c(INT, TEXT)` | | NanoID with custom length and alphabets |
+| [Ksuid][ksuid] | `generate_ksuid()` | [`svix-ksuid`][crate-svix-ksuid] | Created by [Segment][segment] |
+| | `generate_ksuid_bytes()` | | KSUID as bytes |
+| [Ulid][ulid] | `generate_ulid()` | [`ulid`][crate-ulid] | Unique, lexicographically sortable identifiers |
+| | `generate_ulid_bytes()` | | ULID as bytes |
+| | `generate_ulid_from_string(TEXT)` | | Generate ULID from a string |
+| [Timeflake][timeflake] | `generate_timeflake()` | [`timeflake-rs`][crate-timeflake-rs] | Twitter's Snowflake + Instagram's ID + Firebase's PushID |
+| | `generate_timeflake_bytes()` | | Timeflake as bytes |
+| | `generate_timeflake_uuid()` | | Timeflake as UUID |
+| [PushId][pushid] | `generate_pushid()` | [`pushid`][crate-pushid] | Google Firebase's PushID |
+| | `generate_pushid_text()` | | PushID as text |
+| [Cuid2][cuid2] | `generate_cuid2()` | [`cuid2`][crate-cuid2] | CUID2 |
+| | `check_cuid2(TEXT)` | | Check if a string is a valid CUID2 |
+| [TypeId][typeid] | `generate_typeid(TEXT)` | [`typeid`][crate-typeid] | Generate TypeId with a specific prefix |
+| | `check_typeid(TEXT, TEXT)` | | Check if a TypeId matches a specific prefix |
+
+This Postgres extension is made possible thanks to [`pgrx`][pgrx].
+
+## Supported IDs
+
+1. [NanoId](https://github.com/ai/nanoid)
+2. [Ksuid](https://github.com/segmentio/ksuid)
3. [Ulid](https://github.com/ulid/spec)
+4. [TypeId](https://github.com/jetpack-io/typeid)
+5. [UUIDv7](https://github.com/uuid-rs/uuid)
+6. [Cuid2](https://github.com/paralleldrive/cuid2)
+7. [PushId](https://github.com/firebase/firebase-js-sdk)
+8. [Timeflake](https://github.com/anthonynsimon/timeflake)
+9. [UUIDv6](https://github.com/uuid-rs/uuid)
-## 2. Installation
+[crate-uuid]: https://crates.io/crates/uuid
+[crate-nanoid]: https://crates.io/crates/nanoid
+[crate-svix-ksuid]: https://crates.io/crates/svix-ksuid
+[crate-ulid]: https://crates.io/crates/ulid
+[crate-timeflake-rs]: https://crates.io/crates/timeflake-rs
+[crate-pushid]: https://crates.io/crates/pushid
+[crate-cuid2]: https://crates.io/crates/cuid2
+[crate-typeid]: https://crates.io/crates/typeid
+[postgres]: https://www.postgresql.org/
+[uuidv6]: https://github.com/uuid-rs/uuid
+[uuidv7]: https://github.com/uuid-rs/uuid
+[nanoid]: https://github.com/ai/nanoid
+[ksuid]: https://github.com/segmentio/ksuid
+[ulid]: https://github.com/ulid/spec
+[timeflake]: https://github.com/anthonynsimon/timeflake
+[pushid]: https://github.com/firebase/firebase-js-sdk
+[cuid2]: https://github.com/paralleldrive/cuid2
+[typeid]: https://github.com/jetpack-io/typeid
+[pgrx]: https://github.com/zombodb/pgx
-### 2.1. Installation on non docker environment
-2.1.1. Install rust through rustup.
-```bash
-curl https://sh.rustup.rs -sSf | sh
-```
+## Installation
-2.1.2. Prepare your postgres installation
-2.1.3. Install pgx
+### Non-Docker Environment
-```bash
-cargo install cargo-pgx
-```
+1. **Install Rust via rustup:**
-2.1.4. Initialize pgx for the postgres version you have already installed
+ ```bash
+ curl https://sh.rustup.rs -sSf | sh
+ ```
-Handle the number accordingly.
+2. **Prepare your PostgreSQL installation.**
-```bash
-cargo pgx init --pg14 $(which pg_config)
-```
+3. **Install pgx:**
-2.1.5. Install the extension
+ ```bash
+ cargo install cargo-pgx
+ ```
-```bash
-git clone https://github.com/spa5k/uids-postgres \
-&& cd uids-postgres \
-&& cargo pgx install
-```
+4. **Initialize pgx for your PostgreSQL version:**
+
+ ```bash
+ cargo pgx init --pg14 $(which pg_config)
+ ```
+
+5. **Clone the repository and install the extension:**
-### 2.2 Installation on docker environment
+ ```bash
+ git clone https://github.com/spa5k/uids-postgres
+ cd uids-postgres
+ cargo pgx install
+ ```
-Check the included [Dockerfile](./docker/Dockerfile) for the installation template.
+### Docker Environment
-## 3. Functions available
+Refer to the included [Dockerfile](./docker/Dockerfile) for the installation template.
-### 3.0. Enable the extension
+## Usage
+
+### Enable the Extension
```sql
CREATE EXTENSION IF NOT EXISTS uids;
```
-### 3.1. KSUID -
+### Functions
-1. Generate a new KSUID
+#### KSUID
-```sql
-select generate_ksuid();
+1. **Generate a new KSUID:**
------------------------------
+ ```sql
+ SELECT generate_ksuid();
+ ```
-28KKKI8lpDkK2lHbAdWdgJYoLWF
-```
+ Example output:
-2. Generate a KSUID bytes.
+ ```
+ 28KKKI8lpDkK2lHbAdWdgJYoLWF
+ ```
-```sql
-select generate_ksuid_bytes();
+2. **Generate KSUID bytes:**
------------------------------
+ ```sql
+ SELECT generate_ksuid_bytes();
+ ```
-\x0ef557bc9b5b8027f222e2b32ed65e91b6bb8eb6
-```
+ Example output:
-### 3.2. NanoId -
+ ```
+ \x0ef557bc9b5b8027f222e2b32ed65e91b6bb8eb6
+ ```
-1. Generate a new NanoId with default size of 21
+#### NanoId
-```sql
-select generate_nanoid();
+1. **Generate a new NanoId (default size 21):**
------------------------------
+ ```sql
+ SELECT generate_nanoid();
+ ```
-FfuwjZHjS5j5rATHVyl8M
-```
+ Example output:
-2. Generate a NanoId with a custom size
+ ```
+ FfuwjZHjS5j5rATHVyl8M
+ ```
-```sql
-select generate_nanoid_length(10);
+2. **Generate a NanoId with a custom size:**
------------------------------
+ ```sql
+ SELECT generate_nanoid_length(10);
+ ```
-V2D2D7-dnw
-```
+ Example output:
-3. Generate a NanoId with a custom alphabets with length of 21
+ ```
+ V2D2D7-dnw
+ ```
-```sql
--- Length of the nanoid is first argument, while the alphabets one is second.
-select generate_nanoid_c('1234567890abcdef');
+3. **Generate a NanoId with custom alphabets (length 21):**
------------------------------
+ ```sql
+ SELECT generate_nanoid_c('1234567890abcdef');
+ ```
-6df80ad84587f4a20838c
-```
+ Example output:
-4. Generate a NanoId with a custom alphabets and custom length
+ ```
+ 6df80ad84587f4a20838c
+ ```
-```sql
--- Length of the nanoid is first argument, while the alphabets one is second.
-select generate_nanoid_length_c(10, '1234567890abcdef');
+4. **Generate a NanoId with custom alphabets and custom length:**
------------------------------
+ ```sql
+ SELECT generate_nanoid_length_c(10, '1234567890abcdef');
+ ```
-050487bff0
-```
+ Example output:
-### 3.3. Ulid -
+ ```
+ 050487bff0
+ ```
-1. Generate a new Ulid
+#### Ulid
-```sql
-select generate_ulid();
+1. **Generate a new Ulid:**
------------------------------
+ ```sql
+ SELECT generate_ulid();
+ ```
-01G1JE4GXWC1A9PXHG0SXQDE1J
-```
+ Example output:
-2. Generate Ulid bytes
+ ```
+ 01G1JE4GXWC1A9PXHG0SXQDE1J
+ ```
-```sql
-select generate_ulid_bytes();
+2. **Generate Ulid bytes:**
------------------------------
+ ```sql
+ SELECT generate_ulid_bytes();
+ ```
-\x018064e2bff9e6bb876aa8948e50d9c6
-```
+ Example output:
-3. Generate Ulid from a custom string
+ ```
+ \x018064e2bff9e6bb876aa8948e50d9c6
+ ```
-```sql
-select generate_ulid_from_string('01CAT3X5Y5G9A62F1rFA6Tnice');
+3. **Generate Ulid from a custom string:**
------------------------------
+ ```sql
+ SELECT generate_ulid_from_string('01CAT3X5Y5G9A62F1rFA6Tnice');
+ ```
-01CAT3X5Y5G9A62F1RFA6TN1CE
-```
+ Example output:
+
+ ```
+ 01CAT3X5Y5G9A62F1RFA6TN1CE
+ ```
+
+#### TypeId
+
+1. **Generate a TypeId with a specific prefix:**
+
+ ```sql
+ SELECT generate_typeid('user');
+ ```
+
+ Example output:
+
+ ```
+ user_01h2xcejqtf2nbrexx3vqjhp41
+ ```
+
+2. **Check if a TypeId matches a specific prefix:**
+
+ ```sql
+ SELECT check_typeid('user', 'user_01h2xcejqtf2nbrexx3vqjhp41');
+ ```
+
+ Example output:
+
+ ```
+ true
+ ```
+
+#### UUIDv7
+
+1. **Generate a new UUIDv7:**
+
+ ```sql
+ SELECT generate_uuidv7();
+ ```
+
+ Example output:
+
+ ```
+ 01809424-3e59-7c05-9219-566f82fff672
+ ```
+
+2. **Generate UUIDv7 bytes:**
+
+ ```sql
+ SELECT generate_uuidv7_bytes();
+ ```
+
+ Example output:
+
+ ```
+ \x018094243e597c059219566f82fff672
+ ```
+
+3. **Generate UUIDv7 from a string:**
+
+ ```sql
+ SELECT generate_uuidv7_from_string('67e55044-10b1-426f-9247-bb680e5fe0c8');
+ ```
+
+ Example output:
+
+ ```
+ 67e55044-10b1-426f-9247-bb680e5fe0c8
+ ```
+
+4. **Parse UUIDv7:**
+
+ ```sql
+ SELECT parse_uuidv7('67e55044-10b1-426f-9247-bb680e5fe0c8');
+ ```
+
+ Example output:
+
+ ```
+ 67e55044-10b1-426f-9247-bb680e5fe0c8
+ ```
+
+#### Cuid2
+
+1. **Generate a new Cuid2:**
+
+ ```sql
+ SELECT generate_cuid2();
+ ```
+
+ Example output:
+
+ ```
+ cl8f8y8f80000000000000000
+ ```
+
+2. **Check if a string is a valid Cuid2:**
+
+ ```sql
+ SELECT check_cuid2('cl8f8y8f80000000000000000');
+ ```
+
+ Example output:
+
+ ```
+ true
+ ```
+
+#### PushId
+
+1. **Generate a new PushId:**
+
+ ```sql
+ SELECT generate_pushid();
+ ```
+
+ Example output:
+
+ ```
+ -MZ1e2f3g4h5i6j7k8l9
+ ```
+
+2. **Generate a PushId as text:**
+
+ ```sql
+ SELECT generate_pushid_text();
+ ```
+
+ Example output:
+
+ ```
+ -MZ1e2f3g4h5i6j7k8l9
+ ```
+
+#### Timeflake
+
+1. **Generate a new Timeflake:**
+
+ ```sql
+ SELECT generate_timeflake();
+ ```
+
+ Example output:
+
+ ```
+ 01F8MECHJ8KZ9Q9J8KZ9Q9J8KZ
+ ```
+
+2. **Generate Timeflake bytes:**
+
+ ```sql
+ SELECT generate_timeflake_bytes();
+ ```
+
+ Example output:
+
+ ```
+ \x018064e2bff9e6bb876aa8948e50d9c6
+ ```
+
+3. **Generate Timeflake UUID:**
+
+ ```sql
+ SELECT generate_timeflake_uuid();
+ ```
+
+ Example output:
+
+ ```
+ 018064e2-bff9-e6bb-876a-a8948e50d9c6
+ ```
+
+#### UUIDv6
+
+1. **Generate a new UUIDv6:**
+
+ ```sql
+ SELECT generate_uuidv6();
+ ```
+
+ Example output:
+
+ ```
+ 1e4eaa4e-7c4b-6e5d-8a4e-7c4b6e5d8a4e
+ ```
+
+2. **Generate a UUIDv6 as text:**
+
+ ```sql
+ SELECT generate_uuidv6_text();
+ ```
+
+ Example output:
+
+ ```
+ 1e4eaa4e-7c4b-6e5d-8a4e-7c4b6e5d8a4e
+ ```
+
+3. **Generate a UUIDv6 as a Postgres UUID object:**
+
+ ```sql
+ SELECT generate_uuidv6_uuid();
+ ```
+
+ Example output:
+
+ ```
+ 1e4eaa4e-7c4b-6e5d-8a4e-7c4b6e5d8a4e
+ ```
+
+This setup provides a comprehensive set of functions to generate and validate various types of unique identifiers within a PostgreSQL extension using `pgx`.
diff --git a/docker/Dockerfile b/docker/Dockerfile
index b8fd3d6..64dea04 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -1,4 +1,4 @@
-FROM postgres:14
+FROM postgres:16
RUN apt-get update && apt-get upgrade -y
ENV build_deps ca-certificates \
@@ -13,10 +13,10 @@ ENV build_deps ca-certificates \
RUN apt-get install -y --no-install-recommends $build_deps pkg-config cmake
-WORKDIR /home/spark
+WORKDIR /home/spa5k
-ENV HOME=/home/spark \
- PATH=/home/spark/.cargo/bin:$PATH
+ENV HOME=/home/spa5k \
+ PATH=/home/spa5k/.cargo/bin:$PATH
RUN chown postgres:postgres /home/spark
USER postgres
@@ -27,9 +27,9 @@ RUN \
cargo --version
# PGX
-RUN cargo install cargo-pgx
+RUN cargo install cargo-pgrx
-RUN cargo pgx init --pg14 $(which pg_config)
+RUN cargo pgx init --pg16 $(which pg_config)
USER root
diff --git a/dprint.json b/dprint.json
deleted file mode 100644
index bb3430f..0000000
--- a/dprint.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "json": {},
- "markdown": {},
- "toml": {},
- "includes": [
- "**/*.{ts,tsx,js,jsx,cjs,mjs,json,md,toml,rs,sql}"
- ],
- "excludes": [
- "**/node_modules",
- "**/*-lock.json",
- "target"
- ],
- "plugins": [
- "https://plugins.dprint.dev/json-0.15.3.wasm",
- "https://plugins.dprint.dev/markdown-0.13.3.wasm",
- "https://plugins.dprint.dev/toml-0.5.4.wasm",
- "https://plugins.dprint.dev/rustfmt-0.6.2.json@886c6f3161cf020c2d75160262b0f56d74a521e05cfb91ec4f956650c8ca76ca",
- "https://plugins.dprint.dev/sql-0.1.2.wasm"
- ]
-}
diff --git a/justfile b/justfile
new file mode 100644
index 0000000..64b4549
--- /dev/null
+++ b/justfile
@@ -0,0 +1,267 @@
+# Choose sell based on platform
+shell := if os() == "macos" { "zsh" } else { "bash" }
+
+docker := env_var_or_default("DOCKER", "docker")
+git := env_var_or_default("GIT", "git")
+tar := env_var_or_default("TAR", "tar")
+strip := env_var_or_default("STRIP", "strip")
+just := env_var_or_default("JUST", just_executable())
+
+cargo := env_var_or_default("CARGO", "cargo")
+cargo_get := env_var_or_default("CARGO_GET", "cargo-get")
+cargo_generate_rpm := env_var_or_default("CARGO_GENERATE_RPM", "cargo-generate-rpm")
+cargo_watch := env_var_or_default("CARGO_WATCH", "cargo-watch")
+cargo_profile := env_var_or_default("CARGO_PROFILE", "")
+cargo_profile_arg := if cargo_profile != "" {
+ "--profile " + cargo_profile
+} else {
+ ""
+}
+cargo_features := env_var_or_default("CARGO_FEATURES", "")
+cargo_features_arg := if cargo_features != "" {
+ "--no-default-features --features " + cargo_features
+} else {
+ ""
+}
+
+changelog_file_path := absolute_path(justfile_directory() / "CHANGELOG")
+
+pkg_pg_version := env_var_or_default("PKG_PG_VERSION", "16.2")
+pkg_pg_config_path := env_var_or_default("PKG_PG_CONFIG_PATH", "~/.pgrx/" + pkg_pg_version + "/pgrx-install/bin/pg_config")
+pkg_tarball_suffix := env_var_or_default("PKG_TARBALL_SUFFIX", "")
+
+pgrx_pg_version := env_var_or_default("PGRX_PG_VERSION", "pg16")
+pgrx_pkg_path_prefix := env_var_or_default("PGRX_PKG_PATH_PREFIX", "target")
+# If /root, 'home' does not appear in the generated prefix
+pkg_user_dir_prefix := if docker_build_user == "root" { docker_build_user } else { "home/" + docker_build_user }
+pgrx_pkg_output_dir := pgrx_pkg_path_prefix / "release" / "uids-postgres" + pgrx_pg_version / pkg_user_dir_prefix / ".pgrx" / pkg_pg_version / "pgrx-install"
+
+docker_build_user := env_var_or_default('DOCKER_BUILD_USER', "root")
+
+default:
+ {{just}} --list
+
+###########
+# Tooling #
+###########
+
+_check-installed-version tool msg:
+ #!/usr/bin/env -S {{shell}} -euo pipefail
+ if [ -z "$(command -v {{tool}})" ]; then
+ echo "{{msg}}";
+ exit 1;
+ fi
+
+@_check-tool-cargo:
+ {{just}} _check-installed-version {{cargo}} "'cargo' not available, please install the Rust toolchain (see: https://github.com/rust-lang/cargo/)";
+
+@_check-tool-cargo-watch:
+ {{just}} _check-installed-version {{cargo_watch}} "'cargo-watch' not available, please install cargo-watch (https://github.com/passcod/cargo-watch)"
+
+@_check-tool-cargo-get:
+ {{just}} _check-installed-version {{cargo_get}} "'cargo-get' not available, please install cargo-get (https://crates.io/crates/cargo-get)"
+
+@_check-tool-strip:
+ {{just}} _check-installed-version {{strip}} "'strip' not available, please install strip (https://www.man7.org/linux/man-pages/man1/strip.1.html)"
+
+@_check-tool-cargo-generate-rpm:
+ {{just}} _check-installed-version {{cargo_generate_rpm}} "'cargo-generate-rpm' not available, please install cargo-generate-rpm (https://crates.io/crates/cargo-generate-rpm)"
+
+#########
+# Build #
+#########
+
+version := env_var_or_default("VERSION", `cargo get package.version`)
+revision := env_var_or_default("REVISION", `git rev-parse --short HEAD`)
+
+@get-version: _check-tool-cargo-get
+ echo -n {{version}}
+
+@get-revision: _check-tool-cargo-get
+ echo -n {{revision}}
+
+print-version:
+ #!/usr/bin/env -S {{shell}} -euo pipefail
+ echo -n `{{just}} get-version`
+
+print-revision:
+ #!/usr/bin/env -S {{shell}} -euo pipefail
+ echo -n `{{just}} get-revision`
+
+print-pkg-output-dir:
+ echo -n {{pgrx_pkg_output_dir}}
+
+changelog:
+ {{git}} cliff --unreleased --tag={{version}} --prepend={{changelog_file_path}}
+
+# Set the version on the package
+set-version version:
+ {{cargo}} set-version {{version}}
+
+lint:
+ {{cargo}} clippy {{cargo_features_arg}} {{cargo_profile_arg}} --all-targets
+
+build:
+ {{cargo}} build {{cargo_features_arg}} {{cargo_profile_arg}}
+
+build-release:
+ {{cargo}} build --release {{cargo_features_arg}}
+
+build-watch: _check-tool-cargo _check-tool-cargo-watch
+ {{cargo_watch}} -x "build $(CARGO_BUILD_FLAGS)" --watch src
+
+build-test-watch: _check-tool-cargo _check-tool-cargo-watch
+ {{cargo_watch}} -x "test $(CARGO_BUILD_FLAGS)" --watch src
+
+build-package:
+ PGRX_IGNORE_RUST_VERSIONS=y {{cargo}} pgrx package --pg-config {{pkg_pg_config_path}} -vvv
+
+package: build-package
+ mkdir -p pkg/uids-postgres$({{just}} print-version)
+ cp -r $({{just}} print-pkg-output-dir)/* pkg/uids-postgres$({{just}} print-version)
+ {{tar}} -C pkg -cvf uids-postgres$(just print-version){{pkg_tarball_suffix}}.tar.gz uids-postgres$({{just}} print-version)
+
+test:
+ {{cargo}} test {{cargo_profile_arg}}
+ {{cargo}} pgrx test
+
+pgrx-init:
+ #!/usr/bin/env -S {{shell}} -euo pipefail
+ if [ ! -d "{{pkg_pg_config_path}}" ]; then
+ echo "failed to find pgrx init dir [{{pkg_pg_config_path}}], running pgrx init...";
+ {{cargo}} pgrx init
+ fi
+
+##########
+# Docker #
+##########
+
+container_img_arch := env_var_or_default("CONTAINER_IMAGE_ARCH", "amd64")
+
+pg_image_version := env_var_or_default("POSTGRES_IMAGE_VERSION", "16.2")
+pg_os_image_version := env_var_or_default("POSTGRES_OS_IMAGE_VERSION", "alpine3.18")
+
+uids_postgres_image_name := env_var_or_default("UIDS_POSTGRES_IMAGE_NAME", "ghcr.io/spa5k/pg_idkit")
+uids_postgres_image_tag := env_var_or_default("POSGRES_IMAGE_VERSION", version + "-" + "pg" + pg_image_version + "-" + pg_os_image_version + "-" + container_img_arch)
+uids_postgres_image_tag_suffix := env_var_or_default("UIDS_POSTGRES_IMAGE_TAG_SUFFIX", "")
+uids_postgres_image_name_full := env_var_or_default("UIDS_POSTGRES_IMAGE_NAME_FULL", uids_postgres_image_name + ":" + uids_postgres_image_tag + uids_postgres_image_tag_suffix)
+uids_postgres_dockerfile_path := env_var_or_default("UIDS_POSTGRES_DOCKERFILE_PATH", "infra" / "docker" / uids_postgres_image_tag + ".Dockerfile")
+
+docker_password_path := env_var_or_default("DOCKER_PASSWORD_PATH", "secrets/docker/password.secret")
+docker_username_path := env_var_or_default("DOCKER_USERNAME_PATH", "secrets/docker/username.secret")
+docker_image_registry := env_var_or_default("DOCKER_IMAGE_REGISTRY", "ghcr.io/spa5k/pg_idkit")
+docker_config_dir := env_var_or_default("DOCKER_CONFIG", "secrets/docker")
+
+img_dockerfile_path := "infra" / "docker" / "uids-postgrespg" + pg_image_version + "-" + pg_os_image_version + "-" + container_img_arch + ".Dockerfile"
+
+# Ensure that that a given file is present
+_ensure-file file:
+ #!/usr/bin/env -S {{shell}} -euo pipefail
+ if [ ! -f "{{file}}" ] ; then
+ echo "[error] file [{{file}}] is required, but missing";
+ exit 1;
+ fi;
+
+# Log in with docker using local credentials
+docker-login:
+ {{just}} _ensure-file {{docker_password_path}}
+ {{just}} _ensure-file {{docker_username_path}}
+ cat {{docker_password_path}} | {{docker}} login {{docker_image_registry}} -u `cat {{docker_username_path}}` --password-stdin
+ cp {{docker_config_dir}}/config.json {{docker_config_dir}}/.dockerconfigjson
+
+docker_platform_arg := env_var_or_default("DOCKER_PLATFORM_ARG", "")
+docker_progress_arg := env_var_or_default("DOCKER_PROGRESS_ARG", "")
+
+##########################
+# Docker Image - builder #
+##########################
+#
+# This image is used as a cache for speeding up CI builds,
+# and for performing builds when building release artifacts
+#
+
+builder_gnu_dockerfile_path := env_var_or_default("BUILDER_DOCKERFILE_PATH", "infra" / "docker" / "builder-gnu.Dockerfile")
+builder_gnu_image_name := env_var_or_default("BUILDER_IMAGE_NAME", "ghcr.io/spa5k/pg_idkit/builder-gnu")
+builder_gnu_image_tag := env_var_or_default("BUILDER_IMAGE_TAG", "0.1.x")
+builder_gnu_image_name_full := env_var_or_default("BUILDER_IMAGE_NAME_FULL", builder_gnu_image_name + ":" + builder_gnu_image_tag)
+
+## TODO: uncomment and edit build-builder-image once musl machinery is available
+## https://github.com/spa5k/pg_idkit/issues/55
+#
+# builder_musl_dockerfile_path := env_var_or_default("BUILDER_DOCKERFILE_PATH", "infra" / "docker" / "builder-musl.Dockerfile")
+# builder_musl_image_name := env_var_or_default("BUILDER_IMAGE_NAME", "ghcr.io/spa5k/pg_idkit/builder-musl")
+# builder_musl_image_tag := env_var_or_default("BUILDER_IMAGE_TAG", "0.1.x")
+# builder_musl_image_name_full := env_var_or_default("BUILDER_IMAGE_NAME_FULL", builder_musl_image_name + ":" + builder_musl_image_tag)
+
+# Build the docker image used in BUILDER
+build-builder-image:
+ {{docker}} build -f {{builder_gnu_dockerfile_path}} -t {{builder_gnu_image_name_full}} .
+
+# Push the docker image used in BUILDER (to GitHub Container Registry)
+push-builder-image:
+ {{docker}} push {{builder_gnu_image_name_full}}
+
+###########################
+# Docker Image - base-pkg #
+###########################
+#
+# This image is used as a base for packaging flows, usually while building
+# the end-user facing Docker image that is contains Postgres & pg_idkit
+#
+
+# Determine the Dockerfile to use when building the packaging utility base image
+base_pkg_dockerfile_path := "infra/docker/base-pkg-" + pg_os_image_version + "-" + container_img_arch + ".Dockerfile"
+base_pkg_image_name := env_var_or_default("PKG_IMAGE_NAME", "ghcr.io/spa5k/pg_idkit/base-pkg")
+base_pkg_version := env_var_or_default("PKG_IMAGE_NAME", "0.1.x")
+base_pkg_image_tag := env_var_or_default("POSGRES_IMAGE_VERSION", base_pkg_version + "-" + pg_os_image_version + "-" + container_img_arch)
+base_pkg_image_name_full := env_var_or_default("PKG_IMAGE_NAME_FULL", base_pkg_image_name + ":" + base_pkg_image_tag)
+
+# Build the base image for packaging
+build-base-pkg-image:
+ {{docker}} build --build-arg USER={{docker_build_user}} -f {{base_pkg_dockerfile_path}} . -t {{base_pkg_image_name_full}};
+
+# Push the base image for packaging
+push-base-pkg-image:
+ {{docker}} push {{base_pkg_image_name_full}}
+
+###########################
+# Docker Image - pg_idkit #
+###########################
+#
+# This image is the pg_idkit image itself, normally built FROM
+# a image of base-pkg
+#
+
+# Build the docker image for pg_idkit
+build-image:
+ {{docker}} build {{docker_platform_arg}} {{docker_progress_arg}} -f {{img_dockerfile_path}} -t {{uids_postgres_image_name_full}} --build-arg USER={{docker_build_user}} --build-arg UIDS_POSTGRES_REVISION={{revision}} --build-arg UIDS_POSTGRES_VERSION={{uids_postgres_image_tag}} .
+
+# Push the docker image for pg_idkit
+push-image:
+ {{docker}} push {{uids_postgres_image_name_full}}
+
+#######
+# RPM #
+#######
+
+rpm_arch := env_var_or_default("RPM_ARCH", "x86_64")
+
+rpm_file_name := env_var_or_default("RPM_OUTPUT_PATH", "uids-postgres" + version + "-" + pgrx_pg_version + "." + rpm_arch + ".rpm")
+rpm_output_path := "target" / "generate-rpm" / rpm_file_name
+
+# Cargo.toml depends on this file being at the location below.
+rpm_scratch_location := "/tmp/pg_idkit/rpm/scratch"
+
+# Build an RPM distribution for pg_idkit
+build-rpm: _check-tool-strip _check-tool-cargo-generate-rpm
+ CARGO_FEATURES={{pgrx_pg_version}} {{just}} package
+ {{strip}} -s {{pgrx_pkg_output_dir}}/lib/postgresql/pg_idkit.so
+ mkdir -p {{rpm_scratch_location}}
+ cp -r {{pgrx_pkg_output_dir}} {{rpm_scratch_location}}
+ {{cargo_generate_rpm}} --variant {{pgrx_pg_version}}
+
+@print-rpm-output-file-name:
+ echo -n {{rpm_file_name}}
+
+@print-rpm-output-path:
+ echo -n {{rpm_output_path}}
\ No newline at end of file
diff --git a/src/cuid2.rs b/src/cuid2.rs
new file mode 100644
index 0000000..4d55402
--- /dev/null
+++ b/src/cuid2.rs
@@ -0,0 +1,36 @@
+pub mod cuid2_rs {
+ use cuid2;
+ use pgrx::prelude::*;
+
+ #[pg_extern]
+ pub(crate) fn generate_cuid2() -> String {
+ let id = cuid2::create_id();
+ id.to_string()
+ }
+
+ #[pg_extern]
+ pub(crate) fn check_cuid2(id_str: &str) -> bool {
+ cuid2::is_cuid2(id_str)
+ }
+
+ // tests
+ #[cfg(any(test, feature = "pg_test"))]
+ #[pgrx::pg_schema]
+ mod tests {
+ use pgrx::pg_test;
+
+ use crate::cuid2::cuid2_rs::{check_cuid2, generate_cuid2};
+
+ #[pg_test]
+ fn test_generate_cuid2() {
+ let cuid2_string: String = generate_cuid2();
+ assert!(cuid2_string.len() == 24);
+ }
+
+ #[pg_test]
+ fn test_check_cuid2() {
+ let cuid2_string: String = generate_cuid2();
+ assert!(check_cuid2(&cuid2_string));
+ }
+ }
+}
diff --git a/src/ksuid.rs b/src/ksuid.rs
index 71c0059..1598f64 100644
--- a/src/ksuid.rs
+++ b/src/ksuid.rs
@@ -1,5 +1,5 @@
pub mod ksuid_rs {
- use pgx::*;
+ use pgrx::prelude::*;
use svix_ksuid::{KsuidLike, KsuidMs};
#[pg_extern]
@@ -13,4 +13,25 @@ pub mod ksuid_rs {
let ksuid_bytes = KsuidMs::new(None, None);
ksuid_bytes.bytes().to_vec()
}
+
+ // tests
+ #[cfg(any(test, feature = "pg_test"))]
+ #[pgrx::pg_schema]
+ mod tests {
+ use pgrx::pg_test;
+
+ // Test Ksuid length
+ #[pg_test]
+ fn test_generate_ksuid_length() {
+ let ksuid_string: String = crate::ksuid::ksuid_rs::generate_ksuid();
+ assert_eq!(ksuid_string.len(), 27);
+ }
+
+ // Test ksuid bytes
+ #[pg_test]
+ fn test_generate_ksuid_bytes() {
+ let ksuid_bytes: Vec = crate::ksuid::ksuid_rs::generate_ksuid_bytes();
+ assert_eq!(ksuid_bytes.len(), 20);
+ }
+ }
}
diff --git a/src/lib.rs b/src/lib.rs
index 3d6dc09..c46e662 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,71 +1,19 @@
+mod cuid2;
mod ksuid;
mod nanoid;
+mod pushid;
+mod timeflake;
+mod typeid;
mod ulid;
+mod uuid_v6;
+mod uuidv7;
-use pgx::*;
+use pgrx::prelude::*;
-pg_module_magic!();
-
-#[cfg(any(test, feature = "pg_test"))]
-#[pg_schema]
-mod tests {
- use pgx::*;
-
- #[pg_test]
- fn test_generate_ulid() {
- let ulid_string: String = crate::ulid::ulid_rs::generate_ulid();
- assert_eq!(ulid_string.len(), 26);
- }
-
- #[pg_test]
- fn test_generate_ulid_from_string() {
- let ulid_string: String = "01CAT3X5Y5G9A62F1rFA6Tnice".to_string();
- // copy of ulid_string
- let ulid_from_string: String = crate::ulid::ulid_rs::generate_ulid_from_string(ulid_string);
- assert_eq!(ulid_from_string, "01CAT3X5Y5G9A62F1RFA6TN1CE");
- }
-
- #[pg_test]
- fn test_generate_ulid_bytes() {
- let ulid_bytes: Vec = crate::ulid::ulid_rs::generate_ulid_bytes();
- assert_eq!(ulid_bytes.len(), 16);
- }
-
- // Test Ksuid length
- #[pg_test]
- fn test_generate_ksuid_length() {
- let ksuid_string: String = crate::ksuid::ksuid_rs::generate_ksuid();
- assert_eq!(ksuid_string.len(), 27);
- }
-
- // Test ksuid bytes
- #[pg_test]
- fn test_generate_ksuid_bytes() {
- let ksuid_bytes: Vec = crate::ksuid::ksuid_rs::generate_ksuid_bytes();
- assert_eq!(ksuid_bytes.len(), 20);
- }
-
- // Test nanoid length
- #[pg_test]
- fn test_generate_nanoid_length() {
- let nanoid_string: String = crate::nanoid::nanoid_rs::generate_nanoid_length(10);
- assert_eq!(nanoid_string.len(), 10);
- }
-
- // test nanoid without legnth
- #[pg_test]
- fn test_generate_nanoid() {
- let nanoid_string: String = crate::nanoid::nanoid_rs::generate_nanoid();
- assert_eq!(nanoid_string.len(), 21);
- }
-
- #[pg_test]
- fn test_generate_nanoida() {
- let nanoid_string: String = crate::nanoid::nanoid_rs::generate_nanoid_c("1234567890abcdef");
- assert_eq!(nanoid_string.len(), 21);
- }
-}
+pgrx::pg_module_magic!();
+/// This module is required by `cargo pgrx test` invocations.
+/// It must be visible at the root of your extension crate.
#[cfg(test)]
pub mod pg_test {
pub fn setup(_options: Vec<&str>) {
diff --git a/src/nanoid.rs b/src/nanoid.rs
index b544a52..7f1c6fa 100644
--- a/src/nanoid.rs
+++ b/src/nanoid.rs
@@ -1,6 +1,6 @@
pub mod nanoid_rs {
use nanoid::nanoid;
- use pgx::*;
+ use pgrx::prelude::*;
#[pg_extern]
pub(crate) fn generate_nanoid() -> String {
@@ -30,4 +30,32 @@ pub mod nanoid_rs {
let id = nanoid!(21, &alphabets_vec);
id
}
+
+ // tests
+ #[cfg(any(test, feature = "pg_test"))]
+ #[pgrx::pg_schema]
+ mod tests {
+ use pgrx::pg_test;
+
+ // Test Nanoid length
+ #[pg_test]
+ fn test_generate_nanoid_length() {
+ let nanoid_string: String = crate::nanoid::nanoid_rs::generate_nanoid_length(10);
+ assert_eq!(nanoid_string.len(), 10);
+ }
+
+ // test nanoid without legnth
+ #[pg_test]
+ fn test_generate_nanoid() {
+ let nanoid_string: String = crate::nanoid::nanoid_rs::generate_nanoid();
+ assert_eq!(nanoid_string.len(), 21);
+ }
+
+ #[pg_test]
+ fn test_generate_nanoid_custom() {
+ let nanoid_string: String =
+ crate::nanoid::nanoid_rs::generate_nanoid_c("1234567890abcdef");
+ assert_eq!(nanoid_string.len(), 21);
+ }
+ }
}
diff --git a/src/pushid.rs b/src/pushid.rs
new file mode 100644
index 0000000..b4ebb53
--- /dev/null
+++ b/src/pushid.rs
@@ -0,0 +1,36 @@
+pub mod pushid_rs {
+ use pgrx::*;
+ use pushid::PushId;
+ use pushid::PushIdGen;
+
+ /// Generate a PushId
+ #[pg_extern]
+ fn generate_pushid() -> String {
+ PushId::new().get_id()
+ }
+
+ /// Generate a PushId as text
+ #[pg_extern]
+ fn generate_pushid_text() -> String {
+ generate_pushid()
+ }
+
+ // tests
+ #[cfg(any(test, feature = "pg_test"))]
+ #[pg_schema]
+ mod tests {
+ use pgrx::*;
+
+ #[pg_test]
+ fn test_pushid_len() {
+ let generated = crate::pushid::pushid_rs::generate_pushid();
+ assert_eq!(generated.len(), 20);
+ }
+
+ #[pg_test]
+ fn test_pushid_text_len() {
+ let generated = crate::pushid::pushid_rs::generate_pushid_text();
+ assert_eq!(generated.len(), 20);
+ }
+ }
+}
diff --git a/src/timeflake.rs b/src/timeflake.rs
new file mode 100644
index 0000000..0dc77b3
--- /dev/null
+++ b/src/timeflake.rs
@@ -0,0 +1,49 @@
+pub mod timeflake_rs {
+ use pgrx::prelude::*;
+ use timeflake_rs::Timeflake;
+
+ #[pg_extern]
+ pub(crate) fn generate_timeflake() -> String {
+ let result = Timeflake::random();
+ result.unwrap().to_string()
+ }
+
+ #[pg_extern]
+ pub(crate) fn generate_timeflake_bytes() -> Vec {
+ let result = Timeflake::random();
+ result.unwrap().as_uuid().as_bytes().to_vec()
+ }
+
+ #[pg_extern]
+ pub fn generate_timeflake_uuid() -> pgrx::Uuid {
+ pgrx::Uuid::from_slice(Timeflake::random().unwrap().as_uuid().as_bytes()).unwrap()
+ }
+
+ #[cfg(any(test, feature = "pg_test"))]
+ #[pgrx::pg_schema]
+ mod tests {
+ use pgrx::pg_test;
+
+ use crate::timeflake::timeflake_rs::{
+ generate_timeflake, generate_timeflake_bytes, generate_timeflake_uuid,
+ };
+
+ #[pg_test]
+ fn test_generate_timeflake() {
+ let timeflake_string: String = generate_timeflake();
+ assert!(timeflake_string.len() == 36);
+ }
+
+ #[pg_test]
+ fn test_generate_timeflake_bytes() {
+ let timeflake_bytes: Vec = generate_timeflake_bytes();
+ assert!(timeflake_bytes.len() == 16);
+ }
+
+ #[pg_test]
+ fn test_generate_timeflake_uuid() {
+ let timeflake_uuid: pgrx::Uuid = generate_timeflake_uuid();
+ assert!(timeflake_uuid.len() == 16);
+ }
+ }
+}
diff --git a/src/typeid.rs b/src/typeid.rs
new file mode 100644
index 0000000..505e0f8
--- /dev/null
+++ b/src/typeid.rs
@@ -0,0 +1,50 @@
+pub mod typeid_rs {
+ use pgrx::prelude::*;
+ use type_safe_id::{DynamicType, TypeSafeId};
+
+ #[pg_extern]
+ pub(crate) fn generate_typeid(prefix: &str) -> String {
+ let dynamic_type = DynamicType::new(prefix).expect("invalid prefix");
+ let id: TypeSafeId = TypeSafeId::new_with_type(dynamic_type);
+ id.to_string()
+ }
+
+ #[pg_extern]
+ pub(crate) fn check_typeid(prefix: &str, id_str: &str) -> bool {
+ let id: TypeSafeId = id_str.parse().expect("invalid id");
+ id.type_prefix() == prefix
+ }
+
+ // tests
+ #[cfg(any(test, feature = "pg_test"))]
+ #[pgrx::pg_schema]
+ mod tests {
+ use pgrx::pg_test;
+
+ // Test TypeId length
+ #[pg_test]
+ fn test_generate_typeid_length() {
+ let typeid_string: String = crate::typeid::typeid_rs::generate_typeid("custom");
+ assert_eq!(typeid_string.len(), 33);
+ }
+
+ // Test TypeId prefix
+ #[pg_test]
+ fn test_generate_typeid_prefix() {
+ let typeid_string: String = crate::typeid::typeid_rs::generate_typeid("custom");
+ assert!(typeid_string.starts_with("custom_"));
+ }
+
+ // Test TypeId check
+ #[pg_test]
+ fn test_check_typeid() {
+ let prefix = "custom";
+ let id: String = crate::typeid::typeid_rs::generate_typeid(prefix);
+ let is_match: bool = crate::typeid::typeid_rs::check_typeid(prefix, &id);
+ assert!(is_match);
+
+ let is_not_match: bool = crate::typeid::typeid_rs::check_typeid("other", &id);
+ assert!(!is_not_match);
+ }
+ }
+}
diff --git a/src/ulid.rs b/src/ulid.rs
index 9c2d73d..c401c78 100644
--- a/src/ulid.rs
+++ b/src/ulid.rs
@@ -1,7 +1,7 @@
pub mod ulid_rs {
use std::str::FromStr;
- use pgx::*;
+ use pgrx::prelude::*;
use rusty_ulid::{generate_ulid_bytes as ulid_bytes, Ulid};
use rusty_ulid::generate_ulid_string;
@@ -23,4 +23,35 @@ pub mod ulid_rs {
let result = Ulid::from_str(&from_str);
result.unwrap().to_string()
}
+
+ // tests
+ #[cfg(any(test, feature = "pg_test"))]
+ #[pgrx::pg_schema]
+ mod tests {
+ use pgrx::pg_test;
+
+ // Test Ulid length
+ #[pg_test]
+ fn test_generate_ulid_length() {
+ let ulid_string: String = crate::ulid::ulid_rs::generate_ulid();
+ assert_eq!(ulid_string.len(), 26);
+ }
+
+ // Test Ulid bytes
+ #[pg_test]
+ fn test_generate_ulid_bytes() {
+ let ulid_bytes: Vec = crate::ulid::ulid_rs::generate_ulid_bytes();
+ assert_eq!(ulid_bytes.len(), 16);
+ }
+
+ // Test Ulid from string
+ #[pg_test]
+ fn test_generate_ulid_from_string() {
+ let ulid_string: String = "01CAT3X5Y5G9A62F1rFA6Tnice".to_string();
+ // copy of ulid_string
+ let ulid_from_string: String =
+ crate::ulid::ulid_rs::generate_ulid_from_string(ulid_string);
+ assert_eq!(ulid_from_string, "01CAT3X5Y5G9A62F1RFA6TN1CE");
+ }
+ }
}
diff --git a/src/uuid_v6.rs b/src/uuid_v6.rs
new file mode 100644
index 0000000..499f2a9
--- /dev/null
+++ b/src/uuid_v6.rs
@@ -0,0 +1,66 @@
+pub mod uuidv6_rs {
+ use std::io::{Error as IoError, ErrorKind};
+
+ use getrandom::getrandom;
+ use pgrx::pg_extern;
+ use uuid::Uuid;
+
+ /// Generate a new UUIDv6
+ fn new_uuidv6() -> Uuid {
+ let mut buf = [0u8; 6];
+ let res = getrandom(&mut buf);
+ if res.is_err() {
+ panic!("failed to get random bytes for building uuidv6");
+ }
+ Uuid::now_v6(&buf)
+ }
+
+ /// Generate a UUID v6
+ #[pg_extern]
+ pub(crate) fn generate_uuidv6() -> String {
+ new_uuidv6().as_hyphenated().to_string()
+ }
+
+ /// Generate a UUID v6, producing a Postgres text object
+ #[pg_extern]
+ pub(crate) fn generate_uuidv6_text() -> String {
+ generate_uuidv6()
+ }
+
+ /// Generate a UUID v6, producing a Postgres uuid object
+ #[pg_extern]
+ pub(crate) fn generate_uuidv6_uuid() -> pgrx::Uuid {
+ pgrx::Uuid::from_slice(new_uuidv6().as_bytes())
+ .map_err(|e| IoError::new(ErrorKind::Other, e))
+ .unwrap()
+ }
+
+ #[cfg(any(test, feature = "pg_test"))]
+ #[pgrx::pg_schema]
+ mod tests {
+ use pgrx::pg_test;
+
+ use crate::uuid_v6::uuidv6_rs::{generate_uuidv6, generate_uuidv6_uuid};
+
+ /// Basic length test
+ #[pg_test]
+ fn test_uuidv6_len() {
+ assert_eq!(generate_uuidv6().len(), 36);
+ }
+
+ /// Basic length test for bytes
+ #[pg_test]
+ fn test_uuidv6_len_uuid() {
+ assert_eq!(generate_uuidv6_uuid().len(), 16);
+ }
+
+ /// Check version integer in UUID string
+ #[pg_test]
+ fn test_uuidv6_version_int() {
+ let generated = generate_uuidv6();
+ let c9 = generated.chars().nth(14);
+ assert!(c9.is_some());
+ assert_eq!(c9.unwrap(), '6');
+ }
+ }
+}
diff --git a/src/uuidv7.rs b/src/uuidv7.rs
new file mode 100644
index 0000000..81ac071
--- /dev/null
+++ b/src/uuidv7.rs
@@ -0,0 +1,74 @@
+pub mod uuidv7_rs {
+ use pgrx::prelude::*;
+ use uuid::Uuid;
+
+ #[pg_extern]
+ pub(crate) fn generate_uuidv7() -> String {
+ let uuid = Uuid::now_v7();
+ uuid.to_string()
+ }
+
+ #[pg_extern]
+ pub(crate) fn generate_uuidv7_bytes() -> Vec {
+ let uuid = Uuid::now_v7();
+ uuid.as_bytes().to_vec()
+ }
+
+ #[pg_extern]
+ pub fn generate_uuidv7_from_string(from_str: String) -> String {
+ let result = Uuid::parse_str(&from_str);
+ result.unwrap().to_string()
+ }
+
+ #[pg_extern]
+ pub fn parse_uuidv7(from_str: String) -> String {
+ let result = Uuid::parse_str(&from_str);
+ result.unwrap().to_string()
+ }
+
+ // tests
+ #[cfg(any(test, feature = "pg_test"))]
+ #[pgrx::pg_schema]
+ mod tests {
+ use pgrx::pg_test;
+
+ // Test UUIDv7 length
+ #[pg_test]
+ fn test_generate_uuidv7_length() {
+ let uuidv7_string: String = crate::uuidv7::uuidv7_rs::generate_uuidv7();
+ assert_eq!(uuidv7_string.len(), 36);
+ }
+
+ // Test UUIDv7 bytes
+ #[pg_test]
+ fn test_generate_uuidv7_bytes() {
+ let uuidv7_bytes: Vec = crate::uuidv7::uuidv7_rs::generate_uuidv7_bytes();
+ assert_eq!(uuidv7_bytes.len(), 16);
+ }
+
+ // Test UUIDv7 from string
+ #[pg_test]
+ fn test_generate_uuidv7_from_string() {
+ let uuidv7_string: String = "67e55044-10b1-426f-9247-bb680e5fe0c8".to_string();
+ // copy of uuidv7_string
+ let uuidv7_from_string: String =
+ crate::uuidv7::uuidv7_rs::generate_uuidv7_from_string(uuidv7_string);
+ assert_eq!(uuidv7_from_string, "67e55044-10b1-426f-9247-bb680e5fe0c8");
+ }
+
+ // Test UUIDv7 parse
+ #[pg_test]
+ fn test_parse_uuidv7() {
+ let uuidv7_string: String = "67e55044-10b1-426f-9247-bb680e5fe0c8".to_string();
+ let uuidv7: String = crate::uuidv7::uuidv7_rs::parse_uuidv7(uuidv7_string.clone());
+ assert_eq!(uuidv7, uuidv7_string);
+ }
+
+ #[pg_test]
+ fn test_generate_uuidv7() {
+ let uuid: String = crate::uuidv7::uuidv7_rs::generate_uuidv7();
+ assert_eq!(uuid.len(), 36); // UUIDv7 length
+ assert!(uuid.contains('-')); // UUID format check
+ }
+ }
+}
diff --git a/uids.control b/uids.control
index 34a516e..4fc9151 100644
--- a/uids.control
+++ b/uids.control
@@ -1,5 +1,5 @@
-comment = 'uids: Created by pgx'
+comment = 'uids: generate various types of unique identifiers'
default_version = '@CARGO_VERSION@'
module_pathname = '$libdir/uids'
relocatable = false
-superuser = false
+superuser = true