Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 44 additions & 8 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -133,31 +133,56 @@ jobs:
echo "ARCH=$ARCH" >> $GITHUB_ENV

# --- Tarball artifact --------------------------------------------
# The layout install.sh expects, independent of the build host's paths: lib/<library> and
# share/extension/<control and scripts>, files only (no directory entries), plus a `<asset>.sha256`
# sidecar ("<digest> <asset>").
- name: Package Extension
shell: bash
env:
COPYFILE_DISABLE: "1" # no macOS ._ entries
run: |
set -euo pipefail
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
PKG="target/release/${{ env.EXTENSION_NAME }}-pg${{ matrix.pg_version }}"
STAGE="$(mktemp -d)"
mkdir -p "$STAGE/lib" "$STAGE/share/extension"
cp "$PKG$("$PG_CONFIG_PATH" --pkglibdir)"/${{ env.EXTENSION_NAME }}.{so,dylib} "$STAGE/lib/" 2>/dev/null || true
[ "$(ls "$STAGE/lib" | wc -l)" -eq 1 ] || { echo "::error::expected exactly one library in $PKG"; ls -R "$PKG"; exit 1; }
cp "$PKG$("$PG_CONFIG_PATH" --sharedir)"/extension/${{ env.EXTENSION_NAME }}* "$STAGE/share/extension/"
ASSET="${{ env.EXTENSION_NAME }}-pg${{ matrix.pg_version }}-${{ env.LOWERCASE_OS }}-${{ env.ARCH }}.tar.gz"
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 }} .
(cd "$STAGE" && find lib share -type f | LC_ALL=C sort | tar -czf "$GITHUB_WORKSPACE/release/$ASSET" -T -)
cd release
if command -v sha256sum >/dev/null; then sha256sum "$ASSET" > "$ASSET.sha256"; else shasum -a 256 "$ASSET" > "$ASSET.sha256"; fi
tar -tzf "$ASSET"; cat "$ASSET.sha256"

# --- Upload artifact for later -----------------------------------
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: release-assets-pg${{ matrix.pg_version }}-${{ env.LOWERCASE_OS }}-${{ env.ARCH }}
path: release/*.tar.gz
path: |
release/*.tar.gz
release/*.tar.gz.sha256
retention-days: 29

# -------------------------------------------------------------
# 2. Build & push the multi-arch Docker image
# -------------------------------------------------------------
build-and-push-docker:
needs: build-and-publish
# The image downloads the release's assets, so the release must exist first.
needs: release
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
pg_version: [14, 15, 16, 17, 18]
# Both Debian bases of the official postgres image: a data directory must keep its glibc (collation).
debian: [bookworm, trixie]
steps:
- uses: actions/checkout@v7

Expand All @@ -175,15 +200,23 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Release version
id: version
run: echo "release=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"

- name: Docker meta
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ env.DOCKER_IMAGE }}
# `<pg>-<debian>` follows the latest release, `<version>-<pg>-<debian>` pins one (the one to deploy).
# The bare `<pg>`, `latest` and `<version>` tags stay as they were: trixie, so nobody's glibc changes.
tags: |
type=raw,value=${{ matrix.pg_version }}
type=raw,value=latest,enable=${{ matrix.pg_version == 18 }}
type=raw,value=${{ github.ref_name }},enable=${{ matrix.pg_version == 18 }}
type=raw,value=${{ matrix.pg_version }}-${{ matrix.debian }}
type=raw,value=${{ github.ref_name }}-${{ matrix.pg_version }}-${{ matrix.debian }}
type=raw,value=${{ matrix.pg_version }},enable=${{ matrix.debian == 'trixie' }}
type=raw,value=latest,enable=${{ matrix.pg_version == 18 && matrix.debian == 'trixie' }}
type=raw,value=${{ github.ref_name }},enable=${{ matrix.pg_version == 18 && matrix.debian == 'trixie' }}

- name: Build & push
uses: docker/build-push-action@v7
Expand All @@ -193,6 +226,8 @@ jobs:
platforms: linux/amd64,linux/arm64
build-args: |
PG_VERSION=${{ matrix.pg_version }}
DEBIAN=${{ matrix.debian }}
RELEASE_VERSION=${{ steps.version.outputs.release }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

Expand Down Expand Up @@ -282,5 +317,6 @@ jobs:
draft: false
files: |
release/*.tar.gz
release/*.tar.gz.sha256
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
17 changes: 15 additions & 2 deletions .github/workflows/test-migrations.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,22 @@ jobs:
sudo apt-get install postgresql-16 postgresql-server-dev-16

- name: Install previous latest version
env:
GH_TOKEN: ${{ github.token }}
run: |
# Install.sh will by default install the latest stable release that we'll need to test against.
sudo bash install.sh
set -euo pipefail
# The latest published release is what installs upgrade from.
VERSION="$(gh release view --repo "${{ github.repository }}" --json tagName --jq .tagName)"
VERSION="${VERSION#v}"
ASSET="typeid-pg16-linux-amd64.tar.gz"
BASE="https://github.com/${{ github.repository }}/releases/download/v${VERSION}"
if curl -fsIL "$BASE/$ASSET.sha256" >/dev/null; then
sudo RELEASE_VERSION="$VERSION" bash install.sh
else
# Releases up to 0.4.1 ship no checksum and lay the tarball out as the Debian paths.
curl -fsSL "$BASE/$ASSET" | sudo tar -xz -C /
fi
echo "previous version: $VERSION"

- name: Start PostgreSQL and create test database
run: |
Expand Down
16 changes: 12 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
ARG PG_VERSION=18
FROM postgres:${PG_VERSION}
# The Debian release under Postgres. Pin it to keep a data directory's glibc: another glibc collates text differently,
# so text indexes built before would need a REINDEX (Postgres warns "collation version mismatch").
ARG DEBIAN=trixie
FROM postgres:${PG_VERSION}-${DEBIAN}

RUN apt update && apt install -y curl
RUN curl -sSL https://raw.githubusercontent.com/blitss/typeid-postgres/main/install.sh | bash
# The release to install, without the leading "v" (e.g. 0.4.2); its assets must exist (the release job runs first).
ARG RELEASE_VERSION

RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates && rm -rf /var/lib/apt/lists/*
COPY install.sh /tmp/install.sh
RUN test -n "${RELEASE_VERSION}" || { echo "build-arg RELEASE_VERSION is required (e.g. 0.4.2)" >&2; exit 1; } \
&& RELEASE_VERSION="${RELEASE_VERSION}" bash /tmp/install.sh && rm /tmp/install.sh

# Enable the extension
RUN echo "shared_preload_libraries = 'typeid'" >> /usr/share/postgresql/postgresql.conf.sample
RUN echo "shared_preload_libraries = 'typeid'" >> /usr/share/postgresql/postgresql.conf.sample
4 changes: 4 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ There are multiple ways to use Postgres with TypeID extension:
docker pull ghcr.io/blitss/typeid-pg:latest
```

Tags: `<pg>-<debian>` follows the latest release (e.g. `16-bookworm`), `<version>-<pg>-<debian>` pins one (e.g. `v0.4.1-16-bookworm`). The bare `<pg>`, `latest` and `<version>` tags are the trixie builds. For an existing data directory keep the Debian release it was created on: a different glibc collates text differently, and Postgres then warns about a collation version mismatch (text indexes need a `REINDEX`). The images before 0.2.0 (`v0.1.0-docker`) are bookworm.

After switching to a newer image, run `ALTER EXTENSION typeid UPDATE;` in each database right away: the new library no longer exports some functions older versions' SQL objects point to (`min`/`max` over typeids fail until the update), and the update brings the IMMUTABLE comparisons (0.2.0, without them Postgres never hash- or merge-joins on a typeid) and the operators' selectivity estimators (0.4.0).

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)
Expand Down
64 changes: 29 additions & 35 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,6 @@ EXTENSION_DIR="$SHARE_DIR/extension"

ASSET_BASENAME="typeid-pg${PG_MAJOR}-${OS}-${ARCH}.tar.gz"
CHECKSUM_BASENAME="${ASSET_BASENAME}.sha256"
LIBRARY_NAME=typeid.so
if [ "$OS" = darwin ]; then
LIBRARY_NAME=typeid.dylib
fi

if [ "$OS" = linux ]; then
require_command sha256sum
Expand Down Expand Up @@ -185,16 +181,12 @@ DIGEST=${CHECKSUM_LINE:0:64}
"${CHECKSUM_COMMAND[@]}" "$CHECKSUM_BASENAME"
)

EXPECTED_MEMBERS=(
"lib/$LIBRARY_NAME"
# The archive holds files only: one library (lib/typeid.so or lib/typeid.dylib), the control file, the full script of
# RELEASE_VERSION and any number of other versioned / upgrade scripts (share/extension/typeid--*.sql).
SQL_MEMBER_PATTERN='^share/extension/typeid--[0-9A-Za-z.+-]+(--[0-9A-Za-z.+-]+)?\.sql$'
REQUIRED_MEMBERS=(
"share/extension/typeid.control"
"share/extension/typeid--0.1.0.sql"
"share/extension/typeid--0.2.0.sql"
"share/extension/typeid--0.3.0.sql"
"share/extension/typeid--0.4.0.sql"
"share/extension/typeid--0.1.0--0.2.0.sql"
"share/extension/typeid--0.2.0--0.3.0.sql"
"share/extension/typeid--0.3.0--0.4.0.sql"
"share/extension/typeid--${RELEASE_VERSION}.sql"
)

MEMBER_LIST="$TMP_DIR/members"
Expand All @@ -213,27 +205,28 @@ while IFS= read -r member || [ -n "$member" ]; do
[[ ! "$member" =~ (^|/)\.\.(/|$) ]] ||
die "Archive contains a parent-path component: $member"

expected=false
for expected_member in "${EXPECTED_MEMBERS[@]}"; do
if [ "$member" = "$expected_member" ]; then
expected=true
break
fi
done
[ "$expected" = true ] || die "Archive contains an unexpected member: $member"
case "$member" in
lib/typeid.so | lib/typeid.dylib)
[ -z "${LIBRARY_MEMBER:-}" ] || die "Archive contains more than one library"
LIBRARY_MEMBER=$member
;;
share/extension/typeid.control) ;;
*)
[[ "$member" =~ $SQL_MEMBER_PATTERN ]] || die "Archive contains an unexpected member: $member"
;;
esac

case "$SEEN_MEMBERS" in
*$'\n'"$member"$'\n'*) die "Archive contains a duplicate member: $member" ;;
esac
SEEN_MEMBERS+="$member"$'\n'
done <"$MEMBER_LIST"

[ "$MEMBER_COUNT" -eq "${#EXPECTED_MEMBERS[@]}" ] ||
die "Archive is missing one or more required members"
for expected_member in "${EXPECTED_MEMBERS[@]}"; do
[ -n "${LIBRARY_MEMBER:-}" ] || die "Archive is missing the library (lib/typeid.so or lib/typeid.dylib)"
for required_member in "${REQUIRED_MEMBERS[@]}"; do
case "$SEEN_MEMBERS" in
*$'\n'"$expected_member"$'\n'*) ;;
*) die "Archive is missing required member: $expected_member" ;;
*$'\n'"$required_member"$'\n'*) ;;
*) die "Archive is missing required member: $required_member" ;;
esac
done

Expand All @@ -251,17 +244,18 @@ install -d -m 0700 "$EXTRACT_DIR"
tar -xzf "$ARCHIVE_PATH" -C "$EXTRACT_DIR" ||
die "Unable to extract archive"

for expected_member in "${EXPECTED_MEMBERS[@]}"; do
extracted_path="$EXTRACT_DIR/$expected_member"
while IFS= read -r member || [ -n "$member" ]; do
extracted_path="$EXTRACT_DIR/$member"
[ -f "$extracted_path" ] && [ ! -L "$extracted_path" ] ||
die "Extracted member is not a regular file: $expected_member"
done
die "Extracted member is not a regular file: $member"
done <"$MEMBER_LIST"

install -m 0755 "$EXTRACT_DIR/lib/$LIBRARY_NAME" "$LIB_DIR/$LIBRARY_NAME"
for expected_member in "${EXPECTED_MEMBERS[@]:1}"; do
extension_name=${expected_member##*/}
install -m 0644 "$EXTRACT_DIR/$expected_member" "$EXTENSION_DIR/$extension_name"
done
install -m 0755 "$EXTRACT_DIR/$LIBRARY_MEMBER" "$LIB_DIR/${LIBRARY_MEMBER#lib/}"
while IFS= read -r member || [ -n "$member" ]; do
case "$member" in
share/extension/*) install -m 0644 "$EXTRACT_DIR/$member" "$EXTENSION_DIR/${member##*/}" ;;
esac
done <"$MEMBER_LIST"

printf 'TypeID %s installed for PostgreSQL %s (%s/%s).\n' \
"$RELEASE_VERSION" "$PG_MAJOR" "$OS" "$ARCH"
Loading