Skip to content
Merged
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
33 changes: 25 additions & 8 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,36 @@ jobs:
build:
runs-on: ubuntu-latest

# Matrix validates the MSRV-ish floor (1.88.0) AND the toolchain used by
# the Dockerfile (1.95). Keep the upper bound in sync with `Dockerfile`.
strategy:
fail-fast: false
matrix:
rust: ["1.88.0", "1.95.0"]

steps:
- uses: actions/checkout@v4

- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.88.0
toolchain: ${{ matrix.rust }}
components: rustfmt, clippy

- name: Check format
run:
run:
cd ${{ github.workspace }}/app ;
cargo fmt -- --check
- name: Check clippy
run:
run:
cd ${{ github.workspace }}/app ;
cargo clippy --all-targets --all-features
- name: Build
run:
run:
cd ${{ github.workspace }}/app ;
cargo build --verbose
- name: Run tests
run:
run:
cd ${{ github.workspace }}/app ;
cargo test --verbose

Expand All @@ -48,7 +55,13 @@ jobs:

steps:
- uses: actions/checkout@v4


- name: Read crate version
id: crate
run: |
version=$(grep -m1 '^version' app/Cargo.toml | cut -d '"' -f2)
echo "version=${version}" >> "$GITHUB_OUTPUT"

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

Expand Down Expand Up @@ -79,6 +92,8 @@ jobs:
push: false
tags: pgc:test
labels: ${{ steps.meta.outputs.labels }}
build-args: |
PGC_VERSION=${{ steps.crate.outputs.version }}
cache-from: type=gha
cache-to: type=gha,mode=max
load: true
Expand All @@ -98,6 +113,8 @@ jobs:
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
PGC_VERSION=${{ steps.crate.outputs.version }}
cache-from: type=gha
cache-to: type=gha,mode=max
continue-on-error: true
73 changes: 73 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,76 @@
2026-04-23 v1.0.17

Bug fixes:
- PostgreSQL version agnostic handling of attstattarget
attribute.
- Replaced double `.unwrap()` in the dump serialization
path with proper error propagation so a `serde_json`
failure surfaces as a readable error instead of a
panic.
- `compare_types` no longer emits a stray
`-- Multirange … is created automatically` comment for
new range types. PostgreSQL auto-creates the
companion multirange row when `CREATE TYPE … AS RANGE`
runs, so the comparer now skips new-in-`to`
multiranges in the CREATE branch (symmetric to the
existing skip in the DROP branch). The skip is
deliberately scoped to the new-type branch only — an
existing multirange whose owner, comment, or ACL
differs between dumps still reaches
`get_alter_script` and emits the corresponding
`COMMENT ON TYPE` / `ALTER TYPE … OWNER TO`. This
was the leading cause of diffs looking "empty" on
schemas that added a new range type.

Reliability:
- Added `FILL_SIBLING_BRANCH_COUNT` constant with a
compile-time arity guard against the outer `try_join!`
in `Dump::fill`, plus a runtime warning when
`--max-connections` / `MAX_CONNECTIONS` is below the
number of parallel dump branches (pool starvation
would otherwise go unnoticed).
- `Config::load` now warns when `FROM_*` and `TO_*`
resolve to the same host+port+database+schema, which
almost always means a typo in the config file and
silently produces an empty diff.

Tooling:
- CI build job runs against both Rust 1.88.0 (floor)
and 1.95.0 (matches the Dockerfile toolchain), so the
shipped Docker image toolchain is CI-validated.
- Dockerfile `version` / `org.opencontainers.image.version`
labels are now filled from `ARG PGC_VERSION` wired
through GitHub Actions from `app/Cargo.toml` — no
more hard-coded version skew on release.

UX / Docs:
- Fixed copy-pasted `--server` help text on `--port`
and `--scheme`; documented the `SIMILAR TO` matching
behaviour of `--scheme` (e.g. `public|app`) and
expanded `--grants-mode` to describe ignore /
addonly / full explicitly.
- Added explanatory comments at every
`recreated_tables.insert()` site documenting the
coupling with `compare_grants` default-ACL handling
for dropped-and-recreated tables.

Tests:
- New buffer-ordering tests pin the emission order of
the comparer's post-script buffers:
sequence_post_script → type_post_script →
enum_post_script → trigger_post_script.
- Replaced `.find().unwrap()` assertions in the
clear-script tests with a helper that panics with
the full script, so ordering-test failures are
actionable.
- New multirange regression tests covering both sides
of the fix:
`compare_types_multirange_not_created_independently`
(symptom fix) and
`compare_types_multirange_comment_change_still_emits_alter`
(guards against over-broad skipping — a metadata
ALTER on an existing multirange must still emit).

2026-04-20 v1.0.16

Performance:
Expand Down
10 changes: 7 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Build stage
FROM rust:1.93-slim AS builder
FROM rust:1.95-slim AS builder

# Install system dependencies needed for building
RUN apt-get update && apt-get install -y \
Expand Down Expand Up @@ -65,11 +65,15 @@ VOLUME ["/home/pgc/data"]
CMD ["pgc", "--help"]

# Metadata
# PGC_VERSION is supplied at build time from app/Cargo.toml so the image
# labels stay in sync with the crate version. Override locally with
# `docker build --build-arg PGC_VERSION=$(grep '^version' app/Cargo.toml | head -1 | cut -d '"' -f2) .`
ARG PGC_VERSION=unknown
LABEL maintainer="nettrash" \
description="PostgreSQL Database Comparer (PGC) - A tool for comparing PostgreSQL database schemas" \
version="1.0.16" \
version="${PGC_VERSION}" \
org.opencontainers.image.title="pgc" \
org.opencontainers.image.description="PostgreSQL Database Comparer" \
org.opencontainers.image.version="1.0.16" \
org.opencontainers.image.version="${PGC_VERSION}" \
org.opencontainers.image.source="https://github.com/nettrash/pgc" \
org.opencontainers.image.licenses="MIT"
2 changes: 1 addition & 1 deletion app/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion app/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pgc"
version = "1.0.16"
version = "1.0.17"
edition = "2024"
license = "MIT"

Expand Down
22 changes: 22 additions & 0 deletions app/src/comparer/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,16 @@ impl Comparer {
}
}
} else {
// New-in-`to` multirange: PostgreSQL auto-creates the
// multirange row when its range type is created, so there is
// nothing to emit here (and `get_script` for 'm' would only
// return a no-op comment). Deliberately narrower than the
// drop loop's blanket 'm' skip: multiranges that exist in
// BOTH dumps but have metadata drift (owner / comment / ACL)
// still reach the ALTER branch above and emit those deltas.
if (to_type.typtype as u8 as char) == 'm' {
continue;
}
self.script.push_str(
format!("/* Type: {}.{} */\n", to_type.schema, to_type.typname).as_str(),
);
Expand Down Expand Up @@ -1578,6 +1588,10 @@ impl Comparer {
let to_table = &self.to.tables[tidx];
if table.partition_key != to_table.partition_key {
recreated_parents.insert(Self::table_key(&table.schema, &table.name));
// Mark for grants: recreated tables auto-inherit default
// privileges when the new row is created, so compare_grants
// must treat the from-side ACL as "default", not the old
// explicit ACL. See the `is_recreated` branch there.
self.recreated_tables
.insert(Self::table_key(&table.schema, &table.name));
}
Expand Down Expand Up @@ -1682,6 +1696,10 @@ impl Comparer {
);

if parent_recreated {
// Mark for grants: this partition child is being
// re-created (its parent was dropped+recreated), so it
// inherits default privileges. compare_grants keys off
// `recreated_tables` to swap in the default ACL.
self.recreated_tables
.insert(Self::table_key(&table.schema, &table.name));
self.script
Expand All @@ -1696,6 +1714,10 @@ impl Comparer {
// and the table is a partitioned parent, record it so its
// children will be recreated instead of altered.
if alter_script.to_lowercase().contains("drop table if exists") {
// Mark for grants: same reasoning as the branch
// above — the dropped+recreated table inherits
// default privileges, so compare_grants must use
// the default ACL as the effective `from_acl`.
self.recreated_tables
.insert(Self::table_key(&table.schema, &table.name));
if table.partition_key.is_some() {
Expand Down
Loading
Loading