From 4c361dfa088b98f76e62b4f3495cec6d1350fbfd Mon Sep 17 00:00:00 2001 From: blits Date: Sun, 29 Jun 2025 18:19:43 +0400 Subject: [PATCH 1/5] fix: drop support for 11 and 12 and add migration test --- .github/workflows/ci.yaml | 166 +++++++++++++++++++++++++++------ .github/workflows/release.yaml | 2 +- src/bin/pgrx_embed.rs | 2 +- 3 files changed, 141 insertions(+), 29 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 740a0d5..465a0f0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -18,8 +18,8 @@ jobs: strategy: fail-fast: false # We want all of them to run, even if one fails matrix: - os: [ "ubuntu-latest" ] - pg: [ "12", "13", "14", "15", "16" ] + os: [ "buildjet-4vcpu-ubuntu-2204" ] + pg: [ "13", "14", "15", "16", "17" ] runs-on: ${{ matrix.os }} env: @@ -28,6 +28,30 @@ jobs: RUST_TOOLCHAIN: ${{ matrix.rust || 'stable' }} steps: - uses: actions/checkout@v4 + - name: Set up Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: Install PostgreSQL (Linux) + if: runner.os == 'Linux' + run: | + sudo apt-get install -y wget gnupg + 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 --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - + sudo apt-get update -y -qq --fix-missing + sudo apt-get install -y postgresql-${{ matrix.pg }} postgresql-server-dev-${{ matrix.pg }} + + sudo chmod a+rwx `/usr/lib/postgresql/${{ matrix.pg }}/bin/pg_config --pkglibdir` `/usr/lib/postgresql/${{ matrix.pg }}/bin/pg_config --sharedir`/extension /var/run/postgresql/ + + - name: Install PostgreSQL (macOS) + if: runner.os == 'macOS' + run: | + brew install postgresql@${{ matrix.pg_version }} + echo "/usr/local/opt/postgresql@${{ matrix.pg_version }}/bin" >> $GITHUB_PATH + + - name: Set up prerequisites and environment run: | sudo apt-get update -y -qq --fix-missing @@ -62,26 +86,6 @@ jobs: env echo "" - - name: Install release version of PostgreSQL - run: | - echo "----- Set up PostgreSQL Apt repository -----" - sudo apt-get install -y wget gnupg - 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 --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - - sudo apt-get update -y -qq --fix-missing - echo "" - - sudo apt-get install -y \ - postgresql-${{ matrix.pg }} \ - postgresql-server-dev-${{ matrix.pg }} - - echo "" - echo "----- pg_config -----" - pg_config - echo "" - - name: Set up PostgreSQL permissions - run: sudo chmod a+rwx `/usr/lib/postgresql/${{ matrix.pg }}/bin/pg_config --pkglibdir` `/usr/lib/postgresql/${{ matrix.pg }}/bin/pg_config --sharedir`/extension /var/run/postgresql/ - - name: Cache cargo registry uses: actions/cache@v4 continue-on-error: false @@ -139,17 +143,41 @@ jobs: run: sccache --stop-server || true Install: runs-on: ubuntu-latest + strategy: + matrix: + os: [ "buildjet-4vcpu-ubuntu-2204" ] + pg: [ "16" ] steps: - uses: actions/checkout@v4 - - name: Install PostgreSQL headers + + - name: Set up Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: Install PostgreSQL (Linux) + if: runner.os == 'Linux' run: | - sudo apt-get update - sudo apt-get install postgresql-server-dev-14 + sudo apt-get install -y wget gnupg + 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 --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - + sudo apt-get update -y -qq --fix-missing + sudo apt-get install -y postgresql-${{ matrix.pg }} postgresql-server-dev-${{ matrix.pg }} + + sudo chmod a+rwx `/usr/lib/postgresql/${{ matrix.pg }}/bin/pg_config --pkglibdir` `/usr/lib/postgresql/${{ matrix.pg }}/bin/pg_config --sharedir`/extension /var/run/postgresql/ + + - name: Install PostgreSQL (macOS) + if: runner.os == 'macOS' + run: | + brew install postgresql@${{ matrix.pg }} + echo "/usr/local/opt/postgresql@${{ matrix.pg }}/bin" >> $GITHUB_PATH + - name: Install cargo-pgrx run: | PGRX_VERSION=$(cargo metadata --format-version 1 | jq -r '.packages[]|select(.name=="pgrx")|.version') cargo install --locked --version=$PGRX_VERSION cargo-pgrx --debug --force - cargo pgrx init --pg14 $(which pg_config) + cargo pgrx init --pg${{ matrix.pg }} $(which pg_config) - name: Install TypeID/pgrx run: | cargo pgrx install --no-default-features --release --sudo @@ -170,4 +198,88 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - name: Run rustfmt - run: cargo fmt -- --check \ No newline at end of file + run: cargo fmt -- --check + Migration: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install PostgreSQL + run: | + sudo apt-get update + sudo apt-get install -y wget gnupg + 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 --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - + sudo apt-get update -y -qq --fix-missing + sudo apt-get install postgresql-16 postgresql-server-dev-16 + + - name: Install cargo-pgrx + run: | + PGRX_VERSION=$(cargo metadata --format-version 1 | jq -r '.packages[]|select(.name=="pgrx")|.version') + cargo install --locked --version=$PGRX_VERSION cargo-pgrx --debug --force + cargo pgrx init --pg16 $(which pg_config) + + - name: Build and install current version + run: | + # Build and install the current version which supports both v0.1.0 and v0.2.0 + cargo pgrx package --features pg16 --pg-config $(which pg_config) + + # Install the manual SQL files for migration testing + sudo cp sql/typeid--0.1.0.sql /usr/share/postgresql/16/extension/ + sudo cp sql/typeid--0.1.0--0.2.0.sql /usr/share/postgresql/16/extension/ + sudo cp sql/typeid--0.2.0.sql /usr/share/postgresql/16/extension/ + + sudo cp target/release/typeid-pg16/usr/share/postgresql/16/extension/* /usr/share/postgresql/16/extension/ + sudo cp target/release/typeid-pg16/usr/lib/postgresql/16/lib/* /usr/lib/postgresql/16/lib/ + + - name: Start PostgreSQL and create test database + run: | + sudo systemctl start postgresql.service + sudo -u postgres createuser -s -d -r -w runner + createdb -U runner test_migration + + - name: Install v0.1.0 and create test data + run: | + psql -U runner -d test_migration -c "CREATE EXTENSION typeid VERSION '0.1.0';" + psql -U runner -d test_migration -c " + CREATE TABLE migration_test (id typeid, name text); + INSERT INTO migration_test VALUES + (typeid_generate('user'), 'Alice'), + (typeid_generate('admin'), 'Bob'), + (typeid_generate(''), 'Anonymous'); + " + # Verify v0.1.0 works + psql -U runner -d test_migration -c "SELECT COUNT(*) FROM migration_test;" + + - name: Verify extension files are installed + run: | + # Check that all extension files are properly installed + echo "=== Extension SQL files ===" + ls -la /usr/share/postgresql/16/extension/typeid* + echo "=== Extension library ===" + ls -la /usr/lib/postgresql/16/lib/typeid* + echo "=== Available versions ===" + cat /usr/share/postgresql/16/extension/typeid.control + + - name: Run migration to v0.2.0 + run: | + psql -U runner -d test_migration -c "ALTER EXTENSION typeid UPDATE TO '0.2.0';" + psql -U runner -d test_migration -c "SELECT extversion FROM pg_extension WHERE extname = 'typeid';" + + - name: Verify migration worked + run: | + # Test old data still works + psql -U runner -d test_migration -c "SELECT COUNT(*) FROM migration_test;" + psql -U runner -d test_migration -c "SELECT typeid_prefix(id) FROM migration_test;" + + # Test new v0.2.0 functions work + psql -U runner -d test_migration -c "SELECT typeid_generate_nil();" + psql -U runner -d test_migration -c "SELECT typeid_is_valid('user_01h455vb4pex5vsknk084sn02q');" + psql -U runner -d test_migration -c "SELECT COUNT(*) FROM migration_test WHERE id @> 'user';" + psql -U runner -d test_migration -c "SELECT typeid_has_prefix(typeid_generate('test'), 'test');" + + # Test that old and new work together + psql -U runner -d test_migration -c " + INSERT INTO migration_test VALUES (typeid_generate_nil(), 'NewUser'); + SELECT COUNT(*) FROM migration_test WHERE typeid_is_nil_prefix(id); + " \ No newline at end of file diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index b619892..40bef22 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -19,7 +19,7 @@ jobs: strategy: fail-fast: false # We want all of them to run, even if one fails matrix: - pg_version: [12, 13, 14, 15, 16] + pg_version: [13, 14, 15, 16, 17] os: [buildjet-4vcpu-ubuntu-2204, buildjet-4vcpu-ubuntu-2204-arm, macos-latest] runs-on: ${{ matrix.os }} diff --git a/src/bin/pgrx_embed.rs b/src/bin/pgrx_embed.rs index 57483f1..5f5c4d8 100644 --- a/src/bin/pgrx_embed.rs +++ b/src/bin/pgrx_embed.rs @@ -1 +1 @@ -::pgrx::pgrx_embed!(); \ No newline at end of file +::pgrx::pgrx_embed!(); From 089afddc5ea5840589e5b4496f0b7a0c9ac29418 Mon Sep 17 00:00:00 2001 From: blits Date: Mon, 30 Jun 2025 20:11:05 +0400 Subject: [PATCH 2/5] fix: migration scripts --- sql/typeid--0.1.0--0.2.0.sql | 238 ++++++++++++++++++----------------- sql/typeid--0.2.0.sql | 46 +++---- 2 files changed, 137 insertions(+), 147 deletions(-) diff --git a/sql/typeid--0.1.0--0.2.0.sql b/sql/typeid--0.1.0--0.2.0.sql index d1dd89c..a3b1d73 100644 --- a/sql/typeid--0.1.0--0.2.0.sql +++ b/sql/typeid--0.1.0--0.2.0.sql @@ -1,133 +1,135 @@ -/*───────────────────────────────────────────────────────────────────────────── - typeid 0.1.0 → 0.2.0 upgrade - * preserves camel-case TypeID identifier * -─────────────────────────────────────────────────────────────────────────────*/ -BEGIN; +/* + typeid v0.1.0 -> v0.2.0 + This script handles the following changes: + 1. Drops the old `min`/`max` aggregates which are incompatible with the new library. + 2. Drops the old state functions for those aggregates. + 3. Creates new state and combine functions for parallel-safe `min`/`max` aggregates. + 4. Creates the new parallel-safe `min`/`max` aggregates. + 5. Adds binary `SEND`/`RECEIVE` functions to the `TypeID` type for replication. + 6. Adds all new v0.2.0 utility functions (`typeid_prefix`, `typeid_is_valid`, etc.). + 7. Adds the new `@>` prefix-matching operator. + 8. Adds an implicit `CAST` from `text` to `typeid`. + 9. Marks all existing functions as `IMMUTABLE` and `PARALLEL SAFE`. + 10. Adds comments to the new functions and operators. +*/ + +-- Step 1: Drop old aggregates and their functions from v0.1.0 +DROP AGGREGATE IF EXISTS min(TypeID); +DROP AGGREGATE IF EXISTS max(TypeID); +DROP FUNCTION IF EXISTS type_id_min_state(TypeID, TypeID); +DROP FUNCTION IF EXISTS type_id_max_state(TypeID, TypeID); + +-- Step 2: Create new functions for v0.2.0 --- 1 ── binary protocol ------------------------------------------------------- +-- Binary protocol functions CREATE FUNCTION typeid_recv(internal) -RETURNS TypeID -IMMUTABLE STRICT PARALLEL SAFE -LANGUAGE c -AS 'MODULE_PATHNAME', 'typeid_recv_wrapper'; +RETURNS TypeID IMMUTABLE STRICT PARALLEL SAFE +LANGUAGE c AS 'MODULE_PATHNAME', 'typeid_recv_wrapper'; CREATE FUNCTION typeid_send(TypeID) -RETURNS bytea -IMMUTABLE STRICT PARALLEL SAFE -LANGUAGE c -AS 'MODULE_PATHNAME', 'typeid_send_wrapper'; - -ALTER TYPE TypeID - SET (RECEIVE = typeid_recv, SEND = typeid_send); - --- 2 ── helper: prefix as text ------------------------------------------------ -CREATE FUNCTION typeid_prefix("typeid" TypeID) -RETURNS text -IMMUTABLE STRICT PARALLEL SAFE -LANGUAGE c -AS 'MODULE_PATHNAME', 'typeid_prefix_wrapper'; - --- 3 ── implicit cast text → TypeID ----------------------------------------- -CREATE CAST (text AS TypeID) - WITH INOUT - AS IMPLICIT; - --- 4 ── parallel-safe aggregates --------------------------------------------- -CREATE FUNCTION type_id_min_type_id_min_combine(this TypeID, v TypeID) -RETURNS TypeID -LANGUAGE c -AS 'MODULE_PATHNAME', 'type_id_min_type_id_min_combine_wrapper'; +RETURNS bytea IMMUTABLE STRICT PARALLEL SAFE +LANGUAGE c AS 'MODULE_PATHNAME', 'typeid_send_wrapper'; -CREATE FUNCTION type_id_max_type_id_max_combine(this TypeID, v TypeID) -RETURNS TypeID -LANGUAGE c -AS 'MODULE_PATHNAME', 'type_id_max_type_id_max_combine_wrapper'; +-- New utility functions +CREATE FUNCTION typeid_prefix(typeid TypeID) +RETURNS TEXT IMMUTABLE STRICT PARALLEL SAFE +LANGUAGE c AS 'MODULE_PATHNAME', 'typeid_prefix_wrapper'; -DROP AGGREGATE IF EXISTS min(TypeID); -CREATE AGGREGATE min (TypeID) -( - SFUNC = type_id_min_type_id_min_state, - STYPE = TypeID, - COMBINEFUNC = type_id_min_type_id_min_combine, - PARALLEL = SAFE +CREATE FUNCTION typeid_generate_nil() +RETURNS TypeID STRICT VOLATILE PARALLEL SAFE +LANGUAGE c AS 'MODULE_PATHNAME', 'typeid_generate_nil_wrapper'; + +CREATE FUNCTION typeid_is_valid(input TEXT) +RETURNS bool IMMUTABLE STRICT PARALLEL SAFE +LANGUAGE c AS 'MODULE_PATHNAME', 'typeid_is_valid_wrapper'; + +CREATE FUNCTION typeid_has_prefix(typeid TypeID, prefix TEXT) +RETURNS bool IMMUTABLE STRICT PARALLEL SAFE +LANGUAGE c AS 'MODULE_PATHNAME', 'typeid_has_prefix_wrapper'; + +CREATE FUNCTION typeid_is_nil_prefix(typeid TypeID) +RETURNS bool IMMUTABLE STRICT PARALLEL SAFE +LANGUAGE c AS 'MODULE_PATHNAME', 'typeid_is_nil_prefix_wrapper'; + +CREATE FUNCTION typeid_generate_batch(prefix TEXT, count INT) +RETURNS TypeID[] STRICT VOLATILE PARALLEL SAFE +LANGUAGE c AS 'MODULE_PATHNAME', 'typeid_generate_batch_wrapper'; + +-- New aggregate helper functions +CREATE FUNCTION "type_id_max_type_id_max_combine"( + "this" TypeID, + "v" TypeID +) RETURNS TypeID +LANGUAGE c AS 'MODULE_PATHNAME', 'type_id_max_type_id_max_combine_wrapper'; + +CREATE FUNCTION "type_id_max_type_id_max_state"( + "this" TypeID, + "arg_one" TypeID +) RETURNS TypeID +LANGUAGE c AS 'MODULE_PATHNAME', 'type_id_max_type_id_max_state_wrapper'; + +CREATE FUNCTION "type_id_min_type_id_min_combine"( + "this" TypeID, + "v" TypeID +) RETURNS TypeID +LANGUAGE c AS 'MODULE_PATHNAME', 'type_id_min_type_id_min_combine_wrapper'; + +CREATE FUNCTION "type_id_min_type_id_min_state"( + "this" TypeID, + "arg_one" TypeID +) RETURNS TypeID +LANGUAGE c AS 'MODULE_PATHNAME', 'type_id_min_type_id_min_state_wrapper'; + + +-- Step 3: Update the TypeID type definition +ALTER TYPE TypeID SET (RECEIVE = typeid_recv, SEND = typeid_send); + +-- Step 4: Re-create aggregates with new parallel-safe functions +CREATE AGGREGATE max (TypeID) ( + SFUNC = "type_id_max_type_id_max_state", + STYPE = TypeID, + COMBINEFUNC = "type_id_max_type_id_max_combine", + PARALLEL = SAFE ); -DROP AGGREGATE IF EXISTS max(TypeID); -CREATE AGGREGATE max (TypeID) -( - SFUNC = type_id_max_type_id_max_state, - STYPE = TypeID, - COMBINEFUNC = type_id_max_type_id_max_combine, - PARALLEL = SAFE +CREATE AGGREGATE min (TypeID) ( + SFUNC = "type_id_min_type_id_min_state", + STYPE = TypeID, + COMBINEFUNC = "type_id_min_type_id_min_combine", + PARALLEL = SAFE ); --- 5 ── mark helpers IMMUTABLE & PARALLEL SAFE -------------------------------- -ALTER FUNCTION typeid_cmp(TypeID,TypeID) IMMUTABLE PARALLEL SAFE; -ALTER FUNCTION typeid_lt(TypeID,TypeID) IMMUTABLE PARALLEL SAFE; -ALTER FUNCTION typeid_le(TypeID,TypeID) IMMUTABLE PARALLEL SAFE; -ALTER FUNCTION typeid_eq(TypeID,TypeID) IMMUTABLE PARALLEL SAFE; -ALTER FUNCTION typeid_ge(TypeID,TypeID) IMMUTABLE PARALLEL SAFE; -ALTER FUNCTION typeid_gt(TypeID,TypeID) IMMUTABLE PARALLEL SAFE; -ALTER FUNCTION typeid_ne(TypeID,TypeID) IMMUTABLE PARALLEL SAFE; -ALTER FUNCTION typeid_hash(TypeID) IMMUTABLE PARALLEL SAFE; -ALTER FUNCTION typeid_hash_extended(TypeID,bigint) IMMUTABLE PARALLEL SAFE; -ALTER FUNCTION uuid_to_typeid(text,uuid) IMMUTABLE PARALLEL SAFE; -ALTER FUNCTION typeid_to_uuid(TypeID) IMMUTABLE PARALLEL SAFE; - --- 6 ── NEW v0.2.0 utility functions ------------------------------------------ - --- Generate TypeID with empty prefix (UUID-only format) -CREATE FUNCTION typeid_generate_nil() -RETURNS TypeID -STRICT VOLATILE PARALLEL SAFE -LANGUAGE c -AS 'MODULE_PATHNAME', 'typeid_generate_nil_wrapper'; - --- Validate TypeID format without parsing -CREATE FUNCTION typeid_is_valid("input" TEXT) -RETURNS bool -IMMUTABLE STRICT PARALLEL SAFE -LANGUAGE c -AS 'MODULE_PATHNAME', 'typeid_is_valid_wrapper'; - --- Check if TypeID has a specific prefix -CREATE FUNCTION typeid_has_prefix("typeid" TypeID, "prefix" TEXT) -RETURNS bool -IMMUTABLE STRICT PARALLEL SAFE -LANGUAGE c -AS 'MODULE_PATHNAME', 'typeid_has_prefix_wrapper'; - --- Check if TypeID has empty prefix -CREATE FUNCTION typeid_is_nil_prefix("typeid" TypeID) -RETURNS bool -IMMUTABLE STRICT PARALLEL SAFE -LANGUAGE c -AS 'MODULE_PATHNAME', 'typeid_is_nil_prefix_wrapper'; - - - --- Generate multiple TypeIDs efficiently -CREATE FUNCTION typeid_generate_batch("prefix" TEXT, "count" INT) -RETURNS TypeID[] -STRICT VOLATILE PARALLEL SAFE -LANGUAGE c -AS 'MODULE_PATHNAME', 'typeid_generate_batch_wrapper'; - --- 7 ── NEW v0.2.0 operators and documentation -------------------------------- - --- Create prefix matching operator (follows PostgreSQL "contains" semantics) +-- Step 5: Update existing functions to be parallel safe +ALTER FUNCTION typeid_in(cstring) IMMUTABLE PARALLEL SAFE; +ALTER FUNCTION typeid_out(TypeID) IMMUTABLE PARALLEL SAFE; +ALTER FUNCTION typeid_generate(TEXT) VOLATILE PARALLEL SAFE; +ALTER FUNCTION uuid_to_typeid(TEXT, uuid) IMMUTABLE PARALLEL SAFE; +ALTER FUNCTION typeid_to_uuid(TypeID) IMMUTABLE PARALLEL SAFE; +ALTER FUNCTION typeid_cmp(TypeID, TypeID) IMMUTABLE PARALLEL SAFE; +ALTER FUNCTION typeid_lt(TypeID, TypeID) IMMUTABLE PARALLEL SAFE; +ALTER FUNCTION typeid_le(TypeID, TypeID) IMMUTABLE PARALLEL SAFE; +ALTER FUNCTION typeid_eq(TypeID, TypeID) IMMUTABLE PARALLEL SAFE; +ALTER FUNCTION typeid_ge(TypeID, TypeID) IMMUTABLE PARALLEL SAFE; +ALTER FUNCTION typeid_gt(TypeID, TypeID) IMMUTABLE PARALLEL SAFE; +ALTER FUNCTION typeid_ne(TypeID, TypeID) IMMUTABLE PARALLEL SAFE; +ALTER FUNCTION typeid_hash(TypeID) IMMUTABLE PARALLEL SAFE; +ALTER FUNCTION typeid_hash_extended(TypeID, bigint) IMMUTABLE PARALLEL SAFE; + + +-- Step 6: Create new operators and casts +CREATE CAST (text AS TypeID) +WITH INOUT AS IMPLICIT; + CREATE OPERATOR @> ( LEFTARG = typeid, RIGHTARG = text, - PROCEDURE = typeid_has_prefix, - COMMUTATOR = '@<' + PROCEDURE = typeid_has_prefix ); --- Add function documentation -COMMENT ON FUNCTION typeid_prefix(typeid) IS 'Extract the prefix from a TypeID for indexing and filtering'; -COMMENT ON FUNCTION typeid_has_prefix(typeid, text) IS 'Check if TypeID has a specific prefix - useful for filtering'; -COMMENT ON FUNCTION typeid_is_valid(text) IS 'Validate TypeID format without parsing - useful for constraints'; -COMMENT ON FUNCTION typeid_generate_nil() IS 'Generate TypeID with empty prefix (UUID-only format)'; -COMMENT ON FUNCTION typeid_generate_batch(text, int) IS 'Generate multiple TypeIDs with the same prefix efficiently'; - -COMMIT; +-- Step 7: Add comments for new objects +COMMENT ON FUNCTION typeid_prefix(typeid) IS 'Extract the prefix from a TypeID for indexing and filtering.'; +COMMENT ON FUNCTION typeid_has_prefix(typeid, text) IS 'Check if a TypeID has a specific prefix.'; +COMMENT ON FUNCTION typeid_is_valid(text) IS 'Validate if a string is a valid TypeID representation.'; +COMMENT ON FUNCTION typeid_generate_nil() IS 'Generate a TypeID with an empty prefix.'; +COMMENT ON FUNCTION typeid_generate_batch(text, int) IS 'Generate a batch of TypeIDs with the same prefix.'; +COMMENT ON OPERATOR @>(typeid, text) IS 'Does the TypeID have the specified prefix?'; diff --git a/sql/typeid--0.2.0.sql b/sql/typeid--0.2.0.sql index 65a370a..573b84f 100644 --- a/sql/typeid--0.2.0.sql +++ b/sql/typeid--0.2.0.sql @@ -105,7 +105,7 @@ AS 'MODULE_PATHNAME', 'type_id_min_type_id_min_state_wrapper'; /* */ /* */ --- src/lib.rs:104 +-- src/lib.rs:93 -- typeid::typeid_cmp CREATE FUNCTION "typeid_cmp"( "a" TypeID, /* typeid::typeid::TypeID */ @@ -117,7 +117,7 @@ AS 'MODULE_PATHNAME', 'typeid_cmp_wrapper'; /* */ /* */ --- src/lib.rs:119 +-- src/lib.rs:108 -- typeid::typeid_eq CREATE FUNCTION "typeid_eq"( "a" TypeID, /* typeid::typeid::TypeID */ @@ -129,7 +129,7 @@ AS 'MODULE_PATHNAME', 'typeid_eq_wrapper'; /* */ /* */ --- src/lib.rs:124 +-- src/lib.rs:113 -- typeid::typeid_ge CREATE FUNCTION "typeid_ge"( "a" TypeID, /* typeid::typeid::TypeID */ @@ -152,7 +152,7 @@ AS 'MODULE_PATHNAME', 'typeid_generate_wrapper'; /* */ /* */ --- src/lib.rs:532 +-- src/lib.rs:521 -- typeid::typeid_generate_batch CREATE FUNCTION "typeid_generate_batch"( "prefix" TEXT, /* &str */ @@ -173,7 +173,7 @@ AS 'MODULE_PATHNAME', 'typeid_generate_nil_wrapper'; /* */ /* */ --- src/lib.rs:129 +-- src/lib.rs:118 -- typeid::typeid_gt CREATE FUNCTION "typeid_gt"( "a" TypeID, /* typeid::typeid::TypeID */ @@ -185,7 +185,7 @@ AS 'MODULE_PATHNAME', 'typeid_gt_wrapper'; /* */ /* */ --- src/lib.rs:508 +-- src/lib.rs:497 -- typeid::typeid_has_prefix CREATE FUNCTION "typeid_has_prefix"( "typeid" TypeID, /* typeid::typeid::TypeID */ @@ -197,7 +197,7 @@ AS 'MODULE_PATHNAME', 'typeid_has_prefix_wrapper'; /* */ /* */ --- src/lib.rs:140 +-- src/lib.rs:129 -- typeid::typeid_hash CREATE FUNCTION "typeid_hash"( "typeid" TypeID /* typeid::typeid::TypeID */ @@ -208,7 +208,7 @@ AS 'MODULE_PATHNAME', 'typeid_hash_wrapper'; /* */ /* */ --- src/lib.rs:147 +-- src/lib.rs:136 -- typeid::typeid_hash_extended CREATE FUNCTION "typeid_hash_extended"( "typeid" TypeID, /* typeid::typeid::TypeID */ @@ -220,7 +220,7 @@ AS 'MODULE_PATHNAME', 'typeid_hash_extended_wrapper'; /* */ /* */ --- src/lib.rs:520 +-- src/lib.rs:509 -- typeid::typeid_is_nil_prefix CREATE FUNCTION "typeid_is_nil_prefix"( "typeid" TypeID /* typeid::typeid::TypeID */ @@ -242,7 +242,7 @@ AS 'MODULE_PATHNAME', 'typeid_is_valid_wrapper'; /* */ /* */ --- src/lib.rs:114 +-- src/lib.rs:103 -- typeid::typeid_le CREATE FUNCTION "typeid_le"( "a" TypeID, /* typeid::typeid::TypeID */ @@ -254,7 +254,7 @@ AS 'MODULE_PATHNAME', 'typeid_le_wrapper'; /* */ /* */ --- src/lib.rs:109 +-- src/lib.rs:98 -- typeid::typeid_lt CREATE FUNCTION "typeid_lt"( "a" TypeID, /* typeid::typeid::TypeID */ @@ -266,7 +266,7 @@ AS 'MODULE_PATHNAME', 'typeid_lt_wrapper'; /* */ /* */ --- src/lib.rs:134 +-- src/lib.rs:123 -- typeid::typeid_ne CREATE FUNCTION "typeid_ne"( "a" TypeID, /* typeid::typeid::TypeID */ @@ -278,7 +278,7 @@ AS 'MODULE_PATHNAME', 'typeid_ne_wrapper'; /* */ /* */ --- src/lib.rs:77 +-- src/lib.rs:66 -- typeid::typeid_prefix CREATE FUNCTION "typeid_prefix"( "typeid" TypeID /* typeid::typeid::TypeID */ @@ -289,7 +289,7 @@ AS 'MODULE_PATHNAME', 'typeid_prefix_wrapper'; /* */ /* */ --- src/lib.rs:83 +-- src/lib.rs:72 -- typeid::typeid_to_uuid CREATE FUNCTION "typeid_to_uuid"( "typeid" TypeID /* typeid::typeid::TypeID */ @@ -300,7 +300,7 @@ AS 'MODULE_PATHNAME', 'typeid_to_uuid_wrapper'; /* */ /* */ --- src/lib.rs:155 +-- src/lib.rs:144 -- typeid::typeid_uuid_generate_v7 CREATE FUNCTION "typeid_uuid_generate_v7"() RETURNS uuid /* pgrx::datum::uuid::Uuid */ STRICT @@ -309,18 +309,7 @@ AS 'MODULE_PATHNAME', 'typeid_uuid_generate_v7_wrapper'; /* */ /* */ --- src/lib.rs:71 --- typeid::typeid_uuid_string -CREATE FUNCTION "typeid_uuid_string"( - "typeid" TypeID /* typeid::typeid::TypeID */ -) RETURNS TEXT /* alloc::string::String */ -IMMUTABLE STRICT PARALLEL SAFE -LANGUAGE c /* Rust */ -AS 'MODULE_PATHNAME', 'typeid_uuid_string_wrapper'; -/* */ - -/* */ --- src/lib.rs:88 +-- src/lib.rs:77 -- typeid::uuid_to_typeid CREATE FUNCTION "uuid_to_typeid"( "prefix" TEXT, /* &str */ @@ -360,7 +349,7 @@ CREATE AGGREGATE min ( /* */ /* */ --- src/lib.rs:160 +-- src/lib.rs:149 -- finalize /* ────────────────────────────────────────────────────────────── @@ -392,7 +381,6 @@ COMMENT ON FUNCTION typeid_prefix(typeid) IS 'Extract the prefix from a TypeID f COMMENT ON FUNCTION typeid_has_prefix(typeid, text) IS 'Check if TypeID has a specific prefix - useful for filtering'; COMMENT ON FUNCTION typeid_is_valid(text) IS 'Validate TypeID format without parsing - useful for constraints'; COMMENT ON FUNCTION typeid_generate_nil() IS 'Generate TypeID with empty prefix (UUID-only format)'; -COMMENT ON FUNCTION typeid_uuid_string(typeid) IS 'Extract UUID as string from TypeID'; CREATE OPERATOR < ( LEFTARG = typeid, From e8c18a7b77bcf9f787cb809f30b5d2caa20d0c00 Mon Sep 17 00:00:00 2001 From: blits Date: Mon, 30 Jun 2025 20:11:30 +0400 Subject: [PATCH 3/5] chore: adapt CI workflows & scripts --- .github/release-drafter.yml | 25 +++ .github/workflows/ci.yaml | 91 +------- .github/workflows/release.yaml | 296 ++++++++++++++++--------- .github/workflows/test-migrations.yaml | 78 +++++++ Cargo.toml | 3 - install.sh | 50 ++++- 6 files changed, 336 insertions(+), 207 deletions(-) create mode 100644 .github/release-drafter.yml create mode 100644 .github/workflows/test-migrations.yaml diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 0000000..718623d --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,25 @@ +# Uses Conventional Commits to decide semver + group sections +name-template: 'v$RESOLVED_VERSION' +tag-template: 'v$RESOLVED_VERSION' + +template: | + ## 🚀 Features + $CHANGES + _(Automatically generated from Conventional Commits)_ + +version-resolver: + major: + - '^.*!:' # breaking changes + minor: + - '^feat' + patch: + - '^fix' + - '^refactor' + - '^perf' + default: patch + +# Show each change as “- commit-title (#PR) by @author” +change-template: '- $TITLE (#$NUMBER) @$AUTHOR' + +# Optional: if nothing matched +no-changes-template: '- Internal improvements only' diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 465a0f0..2ac4769 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -3,10 +3,11 @@ name: CI on: push: branches: - - main + - next pull_request: branches: - main + - next env: CARGO_TERM_COLOR: always @@ -26,6 +27,8 @@ jobs: RUSTC_WRAPPER: sccache SCCACHE_DIR: /home/runner/.cache/sccache RUST_TOOLCHAIN: ${{ matrix.rust || 'stable' }} + # Github token is there to avoid hitting the rate limits (which does happen for some reason when querying the latest release) + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@v4 - name: Set up Rust @@ -198,88 +201,4 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - name: Run rustfmt - run: cargo fmt -- --check - Migration: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Install PostgreSQL - run: | - sudo apt-get update - sudo apt-get install -y wget gnupg - 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 --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - - sudo apt-get update -y -qq --fix-missing - sudo apt-get install postgresql-16 postgresql-server-dev-16 - - - name: Install cargo-pgrx - run: | - PGRX_VERSION=$(cargo metadata --format-version 1 | jq -r '.packages[]|select(.name=="pgrx")|.version') - cargo install --locked --version=$PGRX_VERSION cargo-pgrx --debug --force - cargo pgrx init --pg16 $(which pg_config) - - - name: Build and install current version - run: | - # Build and install the current version which supports both v0.1.0 and v0.2.0 - cargo pgrx package --features pg16 --pg-config $(which pg_config) - - # Install the manual SQL files for migration testing - sudo cp sql/typeid--0.1.0.sql /usr/share/postgresql/16/extension/ - sudo cp sql/typeid--0.1.0--0.2.0.sql /usr/share/postgresql/16/extension/ - sudo cp sql/typeid--0.2.0.sql /usr/share/postgresql/16/extension/ - - sudo cp target/release/typeid-pg16/usr/share/postgresql/16/extension/* /usr/share/postgresql/16/extension/ - sudo cp target/release/typeid-pg16/usr/lib/postgresql/16/lib/* /usr/lib/postgresql/16/lib/ - - - name: Start PostgreSQL and create test database - run: | - sudo systemctl start postgresql.service - sudo -u postgres createuser -s -d -r -w runner - createdb -U runner test_migration - - - name: Install v0.1.0 and create test data - run: | - psql -U runner -d test_migration -c "CREATE EXTENSION typeid VERSION '0.1.0';" - psql -U runner -d test_migration -c " - CREATE TABLE migration_test (id typeid, name text); - INSERT INTO migration_test VALUES - (typeid_generate('user'), 'Alice'), - (typeid_generate('admin'), 'Bob'), - (typeid_generate(''), 'Anonymous'); - " - # Verify v0.1.0 works - psql -U runner -d test_migration -c "SELECT COUNT(*) FROM migration_test;" - - - name: Verify extension files are installed - run: | - # Check that all extension files are properly installed - echo "=== Extension SQL files ===" - ls -la /usr/share/postgresql/16/extension/typeid* - echo "=== Extension library ===" - ls -la /usr/lib/postgresql/16/lib/typeid* - echo "=== Available versions ===" - cat /usr/share/postgresql/16/extension/typeid.control - - - name: Run migration to v0.2.0 - run: | - psql -U runner -d test_migration -c "ALTER EXTENSION typeid UPDATE TO '0.2.0';" - psql -U runner -d test_migration -c "SELECT extversion FROM pg_extension WHERE extname = 'typeid';" - - - name: Verify migration worked - run: | - # Test old data still works - psql -U runner -d test_migration -c "SELECT COUNT(*) FROM migration_test;" - psql -U runner -d test_migration -c "SELECT typeid_prefix(id) FROM migration_test;" - - # Test new v0.2.0 functions work - psql -U runner -d test_migration -c "SELECT typeid_generate_nil();" - psql -U runner -d test_migration -c "SELECT typeid_is_valid('user_01h455vb4pex5vsknk084sn02q');" - psql -U runner -d test_migration -c "SELECT COUNT(*) FROM migration_test WHERE id @> 'user';" - psql -U runner -d test_migration -c "SELECT typeid_has_prefix(typeid_generate('test'), 'test');" - - # Test that old and new work together - psql -U runner -d test_migration -c " - INSERT INTO migration_test VALUES (typeid_generate_nil(), 'NewUser'); - SELECT COUNT(*) FROM migration_test WHERE typeid_is_nil_prefix(id); - " \ No newline at end of file + run: cargo fmt -- --check \ No newline at end of file diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 40bef22..c7c1a59 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,130 +1,208 @@ -name: Build and Publish TypeID Extension +name: Build • Publish • Release + +# We still build + release when a tag like v1.2.3 is pushed +on: + push: + tags: ['v*'] permissions: + contents: write packages: write id-token: write - contents: write - -on: - push: - tags: - - 'v*' env: EXTENSION_NAME: typeid - DOCKER_IMAGE: ghcr.io/blitss/typeid-pg + DOCKER_IMAGE: ghcr.io/blitss/typeid-pg jobs: + + # ------------------------------------------------------------- + # 1. Build binaries for every PG version / OS / arch + # ------------------------------------------------------------- build-and-publish: strategy: - fail-fast: false # We want all of them to run, even if one fails + fail-fast: false matrix: pg_version: [13, 14, 15, 16, 17] - os: [buildjet-4vcpu-ubuntu-2204, buildjet-4vcpu-ubuntu-2204-arm, macos-latest] + os: [buildjet-4vcpu-ubuntu-2204, + buildjet-4vcpu-ubuntu-2204-arm, + macos-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 - - - name: Set up Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true - - - name: Install PostgreSQL (Linux) - if: runner.os == 'Linux' - run: | - sudo apt-get install -y wget gnupg - 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 --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - - sudo apt-get update -y -qq --fix-missing - sudo apt-get install -y postgresql-${{ matrix.pg_version }} postgresql-server-dev-${{ matrix.pg_version }} - - sudo chmod a+rwx `/usr/lib/postgresql/${{ matrix.pg_version }}/bin/pg_config --pkglibdir` `/usr/lib/postgresql/${{ matrix.pg_version }}/bin/pg_config --sharedir`/extension /var/run/postgresql/ - - - name: Install PostgreSQL (macOS) - if: runner.os == 'macOS' - run: | - brew install postgresql@${{ matrix.pg_version }} - echo "/usr/local/opt/postgresql@${{ matrix.pg_version }}/bin" >> $GITHUB_PATH - - - name: Install cargo-pgrx - run: | - if [ "${{ runner.os }}" == "Linux" ]; then - PG_CONFIG_PATH="/usr/lib/postgresql/${{ matrix.pg_version }}/bin/pg_config" - else - PG_CONFIG_PATH="/opt/homebrew/opt/postgresql@${{ matrix.pg_version }}/bin/pg_config" - fi - PGRX_VERSION=$(cargo metadata --format-version 1 | jq -r '.packages[]|select(.name=="pgrx")|.version') - cargo install --locked --version=$PGRX_VERSION cargo-pgrx --debug --force - cargo pgrx init --pg${{ matrix.pg_version }} $PG_CONFIG_PATH - - - name: Build - run: | - if [ "${{ runner.os }}" == "Linux" ]; then - PG_CONFIG_PATH="/usr/lib/postgresql/${{ matrix.pg_version }}/bin/pg_config" - else - PG_CONFIG_PATH="/opt/homebrew/opt/postgresql@${{ matrix.pg_version }}/bin/pg_config" - fi - cargo pgrx package --features pg${{ matrix.pg_version }} --pg-config $PG_CONFIG_PATH - - - name: Format OS name for release - run: | - LOWERCASE_OS=$(uname -s | tr '[:upper:]' '[:lower:]') - - echo "LOWERCASE_OS=$LOWERCASE_OS" >> $GITHUB_ENV - - - name: Format arch - run: | - ARCH=$(uname -m) - - if [ "$ARCH" = "x86_64" ]; then - ARCH="amd64" - elif [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then - ARCH="arm64" - fi - - echo "ARCH=$ARCH" >> $GITHUB_ENV - - - name: Package Extension - run: | - mkdir -p release - tar -czvf release/${{ env.EXTENSION_NAME }}-pg${{ matrix.pg_version }}-${{ env.LOWERCASE_OS }}-${{ env.ARCH }}.tar.gz -C target/release/${{ env.EXTENSION_NAME }}-pg${{ matrix.pg_version }} . - - - name: Upload Release Asset - uses: softprops/action-gh-release@v1 - with: - files: release/${{ env.EXTENSION_NAME }}-pg${{ matrix.pg_version }}-${{ env.LOWERCASE_OS }}-${{ env.ARCH }}.tar.gz - + - uses: actions/checkout@v4 + + # --- Rust toolchain ---------------------------------------------- + - name: Set up Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + # --- PostgreSQL (Linux) ------------------------------------------ + - name: Install PostgreSQL (Linux) + if: runner.os == 'Linux' + run: | + sudo apt-get install -y wget gnupg + 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 --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - + sudo apt-get update -y -qq --fix-missing + sudo apt-get install -y postgresql-${{ matrix.pg_version }} \ + postgresql-server-dev-${{ matrix.pg_version }} + sudo chmod a+rwx \ + `/usr/lib/postgresql/${{ matrix.pg_version }}/bin/pg_config --pkglibdir` \ + `/usr/lib/postgresql/${{ matrix.pg_version }}/bin/pg_config --sharedir`/extension \ + /var/run/postgresql/ + + # --- PostgreSQL (macOS) ------------------------------------------ + - name: Install PostgreSQL (macOS) + if: runner.os == 'macOS' + run: | + brew install postgresql@${{ matrix.pg_version }} + echo "/usr/local/opt/postgresql@${{ matrix.pg_version }}/bin" >> $GITHUB_PATH + + # --- cargo-pgrx --------------------------------------------------- + - name: Install cargo-pgrx + run: | + if [ "${{ runner.os }}" == "Linux" ]; then + PG_CONFIG_PATH="/usr/lib/postgresql/${{ matrix.pg_version }}/bin/pg_config" + else + PG_CONFIG_PATH="/opt/homebrew/opt/postgresql@${{ matrix.pg_version }}/bin/pg_config" + fi + PGRX_VERSION=$(cargo metadata --format-version 1 | jq -r '.packages[] | select(.name=="pgrx") | .version') + cargo install --locked --version "$PGRX_VERSION" cargo-pgrx --debug --force + cargo pgrx init --pg${{ matrix.pg_version }} "$PG_CONFIG_PATH" + + # --- Build -------------------------------------------------------- + - name: Build + run: | + if [ "${{ runner.os }}" == "Linux" ]; then + PG_CONFIG_PATH="/usr/lib/postgresql/${{ matrix.pg_version }}/bin/pg_config" + else + PG_CONFIG_PATH="/opt/homebrew/opt/postgresql@${{ matrix.pg_version }}/bin/pg_config" + fi + cargo pgrx package --features pg${{ matrix.pg_version }} \ + --pg-config "$PG_CONFIG_PATH" + + # --- Normalise OS + arch names ----------------------------------- + - name: Format OS name for release + run: echo "LOWERCASE_OS=$(uname -s | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV + + - name: Format arch + run: | + ARCH=$(uname -m) + case "$ARCH" in + x86_64) ARCH="amd64" ;; + aarch64|arm64) ARCH="arm64" ;; + esac + echo "ARCH=$ARCH" >> $GITHUB_ENV + + # --- Tarball artefact -------------------------------------------- + - name: Package Extension + run: | + mkdir -p release + tar -czvf \ + release/${{ env.EXTENSION_NAME }}-pg${{ matrix.pg_version }}-${{ env.LOWERCASE_OS }}-${{ env.ARCH }}.tar.gz \ + -C target/release/${{ env.EXTENSION_NAME }}-pg${{ matrix.pg_version }} . + + # --- Upload artefact for later ----------------------------------- + - name: Upload artefact + uses: actions/upload-artifact@v4 + with: + name: release-assets + path: release/*.tar.gz + retention-days: 1 + + + # ------------------------------------------------------------- + # 2. Build & push the multi-arch Docker image + # ------------------------------------------------------------- build-and-push-docker: needs: build-and-publish - runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - # It's fine to use QEMU here because we're not building the extension inside the Dockerfile - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to GHCR - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and push Docker image - uses: docker/build-push-action@v4 - with: - context: . - push: true - platforms: linux/amd64,linux/arm64 - tags: | - ${{ env.DOCKER_IMAGE }}:latest - ${{ env.DOCKER_IMAGE }}:${{ github.ref_name }} \ No newline at end of file + - uses: actions/checkout@v4 + + # It’s just copying built files, so QEMU is fine + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build & push + uses: docker/build-push-action@v4 + with: + context: . + push: true + platforms: linux/amd64,linux/arm64 + tags: | + ${{ env.DOCKER_IMAGE }}:latest + ${{ env.DOCKER_IMAGE }}:${{ github.ref_name }} + + + # ------------------------------------------------------------- + # 3. Draft / publish the GitHub Release + # ------------------------------------------------------------- + release: + needs: [build-and-publish, build-and-push-docker] + runs-on: ubuntu-latest + permissions: + contents: write # create / update the release + packages: write # attach artefacts + steps: + - uses: actions/checkout@v4 + with: + # We need full history to check ancestry against main + fetch-depth: 0 + + # --- 3-a. Pull all artefacts built in the matrix ----------------- + - uses: actions/download-artifact@v4 + with: + name: release-assets + path: release + + # --- 3-b. Draft release notes from Conventional Commits ---------- + - name: Release Drafter (Conventional Commits) + id: notes + uses: release-drafter/release-drafter@v6 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag: ${{ github.ref_name }} # the tag we just pushed + publish: false # keep as draft for now + prerelease: false # we’ll flip below + config-name: .github/release-drafter.yml + + # --- 3-c. Flip logic: ONLY “main” ⇒ final ------------------------ + - name: Is this tag reachable from main? + id: prerelease + run: | + git fetch origin main --quiet || true + if git merge-base --is-ancestor origin/main "$GITHUB_SHA"; then + echo "is_pre=false" >> "$GITHUB_OUTPUT" # on main → latest + else + echo "is_pre=true" >> "$GITHUB_OUTPUT" # not main → prerelease + fi + + # --- 3-d. Publish / update Release & attach artefacts ------------ + - name: Publish GitHub Release + uses: softprops/action-gh-release@v2 + with: + name: ${{ steps.notes.outputs.name }} + tag_name: ${{ steps.notes.outputs.tag_name }} + body: ${{ steps.notes.outputs.body }} + prerelease: ${{ steps.prerelease.outputs.is_pre }} + draft: false + files: | + release/*.tar.gz + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test-migrations.yaml b/.github/workflows/test-migrations.yaml new file mode 100644 index 0000000..6050521 --- /dev/null +++ b/.github/workflows/test-migrations.yaml @@ -0,0 +1,78 @@ +name: Test migration SQL scripts + +on: + pull_request: + branches: + - main + +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: 1 + CARGO_INCREMENTAL: "false" + +jobs: + Migration: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install PostgreSQL + run: | + sudo apt-get update + sudo apt-get install -y wget gnupg + 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 --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - + sudo apt-get update -y -qq --fix-missing + sudo apt-get install postgresql-16 postgresql-server-dev-16 + + - name: Install v0.1.0 + run: | + # Install.sh will by default install the latest stable release that we'll need to test against. + sudo bash install.sh + + - name: Start PostgreSQL and create test database + run: | + sudo systemctl start postgresql.service + sudo -u postgres createuser -s -d -r -w runner + createdb -U runner test_extension + + - name: Install v0.1.0 and create test data + run: | + psql -U runner -d test_extension -c "CREATE EXTENSION typeid;" + psql -U runner -d test_extension -c " + CREATE TABLE migration_test (id typeid, name text); + INSERT INTO migration_test VALUES + (typeid_generate('user'), 'Alice'), + (typeid_generate('admin'), 'Bob'), + (typeid_generate(''), 'Anonymous');" + # Verify v0.1.0 works + psql -U runner -d test_extension -c "SELECT COUNT(*) FROM migration_test;" + psql -U runner -d test_extension -c "SELECT typeid_generate('user') FROM migration_test;" + + + - name: Install cargo-pgrx + run: | + PGRX_VERSION=$(cargo metadata --format-version 1 | jq -r '.packages[]|select(.name=="pgrx")|.version') + cargo install --locked --version=$PGRX_VERSION cargo-pgrx --debug --force + cargo pgrx init --pg16 $(which pg_config) + + - name: Install latest version and test migration + run: | + # Build the new version and install + cargo pgrx package --features pg16 --pg-config $(which pg_config) + cargo pgrx install --features pg16 --release --sudo + + # Perform the migration + psql -U runner -d test_extension -c "ALTER EXTENSION typeid UPDATE;" + psql -U runner -d test_extension -c "SELECT extversion FROM pg_extension WHERE extname = 'typeid';" + + # Verify old data still works + psql -U runner -d test_extension -c "SELECT COUNT(*) FROM migration_test;" + psql -U runner -d test_extension -c "SELECT typeid_prefix(id) FROM migration_test;" + + # Test new functions + # todo: should extract that to a test sql file + psql -U runner -d test_extension -c "SELECT typeid_generate_nil();" + psql -U runner -d test_extension -c "SELECT typeid_is_valid('user_01h455vb4pex5vsknk084sn02q');" + psql -U runner -d test_extension -c "SELECT COUNT(*) FROM migration_test WHERE id @> 'user';" + psql -U runner -d test_extension -c "SELECT typeid_has_prefix(typeid_generate('test'), 'test');" \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index dace583..04c1cb7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,3 @@ codegen-units = 1 name = "spec" path = "tests/spec.rs" harness = false - -[package.metadata.pgrx] -extension-version = "0.0.0" \ No newline at end of file diff --git a/install.sh b/install.sh index 3d204e0..8710301 100644 --- a/install.sh +++ b/install.sh @@ -6,14 +6,29 @@ set -e if [ $# -eq 1 ]; then PG_CONFIG="$1" else - PG_CONFIG=$(which pg_config) + PG_CONFIG=$(which pg_config 2>/dev/null || true) fi +# Check if pg_config exists and is executable +if [ -z "$PG_CONFIG" ] || [ ! -x "$PG_CONFIG" ]; then + echo "Error: pg_config not found or not executable." + echo "Please install PostgreSQL development packages or provide the path to pg_config as an argument." + echo "Usage: $0 [/path/to/pg_config]" + exit 1 +fi + +echo "Using pg_config: $PG_CONFIG" + # Function to get the latest release version get_latest_release() { - curl --silent "https://api.github.com/repos/blitss/typeid-postgres/releases/latest" | - grep '"tag_name":' | - sed -E 's/.*"([^"]+)".*/\1/' + if [ -n "$GITHUB_TOKEN" ]; then + curl --silent -H "Authorization: Bearer $GITHUB_TOKEN" \ + "https://api.github.com/repos/blitss/typeid-postgres/releases/latest" + else + curl --silent "https://api.github.com/repos/blitss/typeid-postgres/releases/latest" + fi \ + | grep '"tag_name":' \ + | sed -E 's/.*"([^"]+)".*/\1/' } # Detect OS and architecture @@ -37,8 +52,17 @@ if [ -z "$PG_VERSION" ]; then exit 1 fi -# Get the latest release version -RELEASE_VERSION=$(get_latest_release) +# Determine which TypeID-Postgres release to install. +# If the user sets the RELEASE_VERSION environment variable we honour it, +# otherwise fall back to fetching the latest release tag from GitHub. +if [ -z "$RELEASE_VERSION" ]; then + echo "RELEASE_VERSION not provided – fetching the latest release tag from GitHub …" + RELEASE_VERSION=$(get_latest_release) + + echo "Latest release version: $RELEASE_VERSION" +else + echo "Using user-supplied RELEASE_VERSION: $RELEASE_VERSION" +fi # Download URL DOWNLOAD_URL="https://github.com/blitss/typeid-postgres/releases/download/${RELEASE_VERSION}/typeid-pg${PG_VERSION}-${OS}-${ARCH}.tar.gz" @@ -56,9 +80,17 @@ LIB_DIR=$("$PG_CONFIG" --pkglibdir) # Install files echo "Installing TypeID extension..." -cp "$TMP_DIR"/usr/share/postgresql/*/extension/typeid.control "$EXTENSION_DIR" -cp "$TMP_DIR"/usr/share/postgresql/*/extension/typeid--*.sql "$EXTENSION_DIR" -cp "$TMP_DIR"/usr/lib/postgresql/*/lib/typeid.so "$LIB_DIR" +if [ "$OS" = "darwin" ]; then + # macOS with Homebrew paths + cp "$TMP_DIR"/opt/homebrew/opt/postgresql*/share/postgresql*/extension/typeid.control "$EXTENSION_DIR" + cp "$TMP_DIR"/opt/homebrew/opt/postgresql*/share/postgresql*/extension/typeid--*.sql "$EXTENSION_DIR" + cp "$TMP_DIR"/opt/homebrew/opt/postgresql*/lib/postgresql/typeid.dylib "$LIB_DIR" +else + # Linux paths + cp "$TMP_DIR"/usr/share/postgresql/*/extension/typeid.control "$EXTENSION_DIR" + cp "$TMP_DIR"/usr/share/postgresql/*/extension/typeid--*.sql "$EXTENSION_DIR" + cp "$TMP_DIR"/usr/lib/postgresql/*/lib/typeid.so "$LIB_DIR" +fi # Clean up rm -rf "$TMP_DIR" From f9dc0083c1d51732ebb394cea380cf4f05259628 Mon Sep 17 00:00:00 2001 From: blits Date: Mon, 30 Jun 2025 20:43:12 +0400 Subject: [PATCH 4/5] chore: update readme docs --- .github/workflows/release.yaml | 29 +++++------ Readme.md | 90 ++++++++++++++++++++++------------ 2 files changed, 73 insertions(+), 46 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index c7c1a59..862ff93 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -98,7 +98,7 @@ jobs: esac echo "ARCH=$ARCH" >> $GITHUB_ENV - # --- Tarball artefact -------------------------------------------- + # --- Tarball artifact -------------------------------------------- - name: Package Extension run: | mkdir -p release @@ -106,13 +106,13 @@ jobs: release/${{ env.EXTENSION_NAME }}-pg${{ matrix.pg_version }}-${{ env.LOWERCASE_OS }}-${{ env.ARCH }}.tar.gz \ -C target/release/${{ env.EXTENSION_NAME }}-pg${{ matrix.pg_version }} . - # --- Upload artefact for later ----------------------------------- - - name: Upload artefact + # --- Upload artifact for later ----------------------------------- + - name: Upload artifact uses: actions/upload-artifact@v4 with: - name: release-assets + name: release-assets-pg${{ matrix.pg_version }}-${{ env.LOWERCASE_OS }}-${{ env.ARCH }} path: release/*.tar.gz - retention-days: 1 + retention-days: 29 # ------------------------------------------------------------- @@ -124,7 +124,7 @@ jobs: steps: - uses: actions/checkout@v4 - # It’s just copying built files, so QEMU is fine + # It's just copying built files, so QEMU is fine - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -153,22 +153,23 @@ jobs: # 3. Draft / publish the GitHub Release # ------------------------------------------------------------- release: - needs: [build-and-publish, build-and-push-docker] + needs: [build-and-publish] runs-on: ubuntu-latest permissions: contents: write # create / update the release - packages: write # attach artefacts + packages: write # attach artifacts steps: - uses: actions/checkout@v4 with: # We need full history to check ancestry against main fetch-depth: 0 - # --- 3-a. Pull all artefacts built in the matrix ----------------- + # --- 3-a. Pull all artifacts built in the matrix ----------------- - uses: actions/download-artifact@v4 with: - name: release-assets path: release + pattern: release-assets-* + merge-multiple: true # --- 3-b. Draft release notes from Conventional Commits ---------- - name: Release Drafter (Conventional Commits) @@ -179,10 +180,10 @@ jobs: with: tag: ${{ github.ref_name }} # the tag we just pushed publish: false # keep as draft for now - prerelease: false # we’ll flip below - config-name: .github/release-drafter.yml + prerelease: false # we'll flip below + config-name: release-drafter.yml - # --- 3-c. Flip logic: ONLY “main” ⇒ final ------------------------ + # --- 3-c. Flip logic: ONLY "main" ⇒ final ------------------------ - name: Is this tag reachable from main? id: prerelease run: | @@ -193,7 +194,7 @@ jobs: echo "is_pre=true" >> "$GITHUB_OUTPUT" # not main → prerelease fi - # --- 3-d. Publish / update Release & attach artefacts ------------ + # --- 3-d. Publish / update Release & attach artifacts ------------ - name: Publish GitHub Release uses: softprops/action-gh-release@v2 with: diff --git a/Readme.md b/Readme.md index 34a85ce..58337f1 100644 --- a/Readme.md +++ b/Readme.md @@ -7,9 +7,9 @@ This extension intends to add TypeIDs to the Postgres using [pgrx](https://githu Here's an example of a TypeID of type user: ``` - user_2x4y6z8a0b1c2d3e4f5g6h7j8k - └──┘ └────────────────────────┘ - type uuid suffix (base32) +user_2x4y6z8a0b1c2d3e4f5g6h7j8k +└──┘ └────────────────────────┘ +type uuid suffix (base32) ``` ID tag converts to `5d278df4-280b-0b04-d1b8-8f2c0d13c913` UUID holding a timestamp which allows to sort it. @@ -39,11 +39,36 @@ FROM series; Obviously it adds some overhead because of decoding/ encoding base52 (because the data is stored as UUID) so keep that in mind. But upon testing I don't think the performance implications are very noticable, inserting the 100k records took me around 800ms. -### Installation -Installation should be performed from source. +### Installation/ upgrade + +There are multiple ways to use Postgres with TypeID extension: + +1) Using pre-built postgres image with TypeID: + +```bash +docker pull ghcr.io/blitss/typeid-pg:latest +``` + +You can see how it's built [here](https://github.com/blitss/typeid-postgres/blob/main/Dockerfile) + +2) By using install script and downloading pre-built extension (must have Postgres installed and `pg_config` exposed in path) + +```bash +curl -sSL https://github.com/blitss/typeid-postgres/blob/main/install.sh | sudo bash +``` + +Or you can specify the pg_config directly: + +```bash +curl -sSL https://github.com/blitss/typeid-postgres/blob/main/install.sh | sudo bash -s -- /usr/pgsql-16/bin/pg_config +``` + +You can upgrade the same way and then run `ALTER EXTENSION typeid UPDATE;` in your Postgres database to run migration scripts. + +3) By building extension manually Prerequisites: -* Postgres 11.x-16.x installed (probably from your package manager), including the "-server" package +* Postgres 13.x-17.x installed (probably from your package manager), including the "-server" package * The Rust toolchain @@ -51,41 +76,42 @@ Prerequisites: Windows is not supported due to pgrx limitations. -Run these commands (replace pg12 with your pg version and `which pg_config` part with your pg path if necessary): +Run these commands (replace pg13 with your pg version and `which pg_config` part with your pg path if necessary): ```bash git clone https://github.com/blitss/typeid-postgres-extension.git cd typeid-postgres-extension cargo install cargo-pgx -cargo pgx init --pg12=`which pg_config` -cargo pgx install --release +cargo pgx init --pg13=`which pg_config` +cargo pgx install --release --sudo ``` After that use `CREATE EXTENSION typeid` to initialize an extension. ### Exposed functions -``` - Schema | Name | Result data type | Argument data types | Type | Volatility | Parallel | Owner | Security | Access privileges | Langu -age | Internal name | Description ---------+------------------+------------------+---------------------+------+------------+----------+-----------+----------+-------------------+------ -----+--------------------------+------------- - public | typeid_cmp | integer | a typeid, b typeid | func | volatile | unsafe | codespace | invoker | | c - | typeid_cmp_wrapper | - public | typeid_eq | boolean | a typeid, b typeid | func | volatile | unsafe | codespace | invoker | | c - | typeid_eq_wrapper | - public | typeid_ge | boolean | a typeid, b typeid | func | volatile | unsafe | codespace | invoker | | c - | typeid_ge_wrapper | - public | typeid_generate | typeid | prefix text | func | volatile | unsafe | codespace | invoker | | c - | typeid_generate_wrapper | - public | typeid_gt | boolean | a typeid, b typeid | func | volatile | unsafe | codespace | invoker | | c - | typeid_gt_wrapper | - public | typeid_in | typeid | input cstring | func | immutable | safe | codespace | invoker | | c | typeid_in_wrapper | - public | typeid_le | boolean | a typeid, b typeid | func | volatile | unsafe | codespace | invoker | | c | typeid_le_wrapper | - public | typeid_lt | boolean | a typeid, b typeid | func | volatile | unsafe | codespace | invoker | | c | typeid_lt_wrapper | - public | typeid_ne | boolean | a typeid, b typeid | func | volatile | unsafe | codespace | invoker | | c | typeid_ne_wrapper | - public | typeid_out | cstring | input typeid | func | immutable | safe | codespace | invoker | | c | typeid_out_wrapper | - public | typeid_uuid_generate_v7 | uuid | | func | volatile | unsafe | codespace | invoker | | c | uuid_generate_v7_wrapper | -(11 rows) -``` \ No newline at end of file +| Function | Return Type | Arguments | Description | +|---|---|---|---| +| `typeid_generate(prefix TEXT)` | `typeid` | `prefix TEXT` | Generate a new TypeID using the supplied prefix. The `prefix` must be lowercase ASCII letters, 1–63 chars. | +| `typeid_generate_nil()` | `typeid` | | Generate a new TypeID with an empty prefix. Equivalent to `typeid_generate('')`. | +| `typeid_is_valid(input TEXT)` | `BOOLEAN` | `input TEXT` | Check if a TypeID string is valid without parsing it. | +| `typeid_prefix(typeid)` | `TEXT` | `typeid typeid` | Extract the prefix part from a TypeID. | +| `typeid_to_uuid(typeid)` | `UUID` | `typeid typeid` | Convert a TypeID to a UUID. | +| `uuid_to_typeid(prefix TEXT, uuid UUID)` | `typeid` | `prefix TEXT`, `uuid UUID` | Convert a UUID to a TypeID with a given prefix. | +| `typeid_uuid_generate_v7()` | `UUID` | | Generate a UUID v7. | +| `typeid_has_prefix(typeid, prefix TEXT)` | `BOOLEAN` | `typeid typeid`, `prefix TEXT` | Check if a TypeID has a specific prefix. | +| `typeid_is_nil_prefix(typeid)` | `BOOLEAN` | `typeid typeid` | Check if a TypeID has a nil prefix. | +| `typeid_generate_batch(prefix TEXT, count INTEGER)` | `SETOF typeid` | `prefix TEXT`, `count INTEGER` | Generate a batch of TypeIDs. | + +### Exposed Aggregates + +| Aggregate | Return Type | Arguments | Description | +|---|---|---|---| +| `min(typeid)` | `typeid` | `typeid` | Returns the minimum TypeID in a group. | +| `max(typeid)` | `typeid` | `typeid` | Returns the maximum TypeID in a group. | + +### Exposed Operators + +This extension also creates a set of operators (`<`, `<=`, `=`, `>=`, `>`, `<>`) for comparing TypeIDs, and a `@>` operator for checking if a `typeid` has a certain prefix (e.g., `id @> 'user'`). +It also creates a `btree` operator class for indexing TypeIDs. \ No newline at end of file From 64fc422252fbd7ac2db314ec4f44cd1b7186ece5 Mon Sep 17 00:00:00 2001 From: blits Date: Mon, 30 Jun 2025 21:42:39 +0400 Subject: [PATCH 5/5] chore: update release drafter --- .github/release-drafter.yml | 91 +++++++++++++++++++++++++++++++------ 1 file changed, 76 insertions(+), 15 deletions(-) diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 718623d..f854005 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -1,25 +1,86 @@ # Uses Conventional Commits to decide semver + group sections name-template: 'v$RESOLVED_VERSION' -tag-template: 'v$RESOLVED_VERSION' +tag-template: 'v$RESOLVED_VERSION' -template: | - ## 🚀 Features - $CHANGES - _(Automatically generated from Conventional Commits)_ +# https://github.com/release-drafter/release-drafter/tree/master/docs/configuration#autolabeler +autolabeler: + - label: 'breaking' + branch: + - '/^.*!:.*/' + title: + - '/^.*!:.*/' + - label: 'feature' + branch: + - '/^feat(:.*)?\/.+/' + title: + - '/^feat(:.*)?\s.+/' + - label: 'fix' + branch: + - '/^fix(:.*)?\/.+/' + title: + - '/^fix(:.*)?\s.+/' + - label: 'chore' + branch: + - '/^chore(:.*)?\/.+/' + title: + - '/^chore(:.*)?\s.+/' + - label: 'docs' + branch: + - '/^docs(:.*)?\/.+/' + title: + - '/^docs(:.*)?\s.+/' + - label: 'refactor' + branch: + - '/^refactor(:.*)?\/.+/' + - '/^ref(:.*)?\/.+/' + title: + - '/^refactor(:.*)?\s.+/' + - '/^ref(:.*)?\s.+/' + - label: 'performance' + branch: + - '/^perf(:.*)?\/.+/' + title: + - '/^perf(:.*)?\s.+/' + +# What categories to include in the release notes. +categories: + - title: '💥 Breaking Changes' + label: 'breaking' + - title: '🚀 Features' + label: 'feature' + - title: '🐛 Bug Fixes' + label: 'fix' + - title: '📝 Documentation' + label: 'docs' + - title: '🔄 Refactors' + label: 'refactor' + - title: '⚡️ Performance Improvements' + label: 'performance' + - title: '🧰 Maintenance' + label: 'chore' + +# Show each change as "- commit-title (#PR) by @author" +change-template: '- $TITLE (#$NUMBER) by @$AUTHOR' + +# Optional: if nothing matched +no-changes-template: 'No changes in this release.' version-resolver: major: - - '^.*!:' # breaking changes + labels: + - 'breaking' minor: - - '^feat' + labels: + - 'feature' patch: - - '^fix' - - '^refactor' - - '^perf' + labels: + - 'fix' + - 'docs' + - 'refactor' + - 'performance' + - 'chore' default: patch -# Show each change as “- commit-title (#PR) by @author” -change-template: '- $TITLE (#$NUMBER) @$AUTHOR' - -# Optional: if nothing matched -no-changes-template: '- Internal improvements only' +template: | + ## What's Changed + $CHANGES \ No newline at end of file