Skip to content

feat(pgmq): vendor PGMQ v1.13.0 and carry upstream table fixups in the upgrade - #462

Open
mhenrixon wants to merge 3 commits into
mainfrom
issue-459-vendor-pgmq-1-13-0
Open

mhenrixon wants to merge 3 commits into
mainfrom
issue-459-vendor-pgmq-1-13-0

Conversation

@mhenrixon

@mhenrixon mhenrixon commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

Vendors PGMQ 1.13.0 and teaches the upgrade migration to carry upstream's table-level fixups.

  • lib/pgbus/pgmq_schema/pgmq_v1.13.0.sql — byte-exact copy of upstream pgmq-extension/sql/pgmq.sql at tag v1.13.0 (commit 32c075bb, blob 1fde328bd238e1732b10a02bbe1c8672b66bd973, verified with git hash-object). Version discovery is a glob, so this alone makes 1.13.0 the install target for :embedded and the :auto fallback.
  • lib/pgbus/pgmq_schema.rb — new fixup_versions and fixups_sql(after:, upto:), discovering lib/pgbus/pgmq_schema/fixups/pgmq_v<VERSION>.sql with the same glob-and-sort the schema files use.
  • lib/pgbus/pgmq_schema/fixups/pgmq_v1.13.0.sql — upstream's guarded DO block from pgmq--1.12.0--1.13.0.sql, verbatim.
  • lib/generators/pgbus/templates/upgrade_pgmq.rb.erb — a fourth step between the re-install and the NOTIFY repair, applying every fixup between the version recorded in pgbus_pgmq_schema_versions and the target.

Closes #459.

Why the fixup step exists

The upgrade works by dropping every PGMQ function and composite type and re-running the target version's schema. That reproduces every function and type change — including 1.13.0's new create_partitioned signature and the metrics_result attribute — but it can never touch an existing table. Upstream's 1.13.0 migration alters one: partitioned queues' msg_id moves from GENERATED ALWAYS to GENERATED BY DEFAULT so pg_partman's partition_data_* tooling can move rows out of the default partition. Without a table step that change was silently skipped.

The mechanism is per-hop rather than inlined for this release because the generator is version-agnostic and always targets latest_version; a one-off would have to be hand-removed at the next hop.

What does not change

The whole 1.12.0 → 1.13.0 delta is partitioned-queue work plus one metrics attribute. Nothing pgbus calls through Pgbus::Client changes shape:

  • create_partitioned gains premake and drops its three-argument form. pgmq-ruby 0.7.2 passes three arguments, which bind to the new signature with the default.
  • metrics_result gains default_partition_length. PGMQ::Metrics builds from named columns, so the extra one is ignored.
  • pgbus creates partitioned archives, not partitioned queues, and their msg_id is a plain BIGINT NOT NULL.

No pgmq-ruby bump (0.7.2 is current), no Pgbus::Client change, no workflow change — dependency-watch.yml derives the vendored version from the same glob and main.yml installs with no version argument.

Test plan

  • bundle exec rspec spec/pgbus/pgmq_schema_spec.rb spec/pgbus/pgmq_versions_task_spec.rb spec/generators — green (41 + 206)
  • PGBUS_DATABASE_URL=... bundle exec rspec spec/integration/pgmq_schema_upgrade_spec.rb — green (14), rewritten for the 1.12.0 → 1.13.0 hop
  • bundle exec rspec (bare, full) — green
  • PGBUS_DATABASE_URL=... bundle exec rspec spec/integration (non-streams) — green
  • bundle exec rubocop app benchmarks config gemfiles lib spec Gemfile Rakefile pgbus.gemspec — no offenses
  • cd docs && bundle exec rake lint && bundle exec rspec — green
  • git hash-object lib/pgbus/pgmq_schema/pgmq_v1.13.0.sql matches the upstream blob
  • On a real install at 1.12.0: rake pgbus:pgmq:status reports the update, rails generate pgbus:upgrade_pgmq + rails db:migrate moves to 1.13.0, and NOTIFY-gated wakeups still fire afterwards.

New integration coverage proves the fixup end to end: that dropping and re-creating the functions alone leaves msg_id at ALWAYS, that the full migration moves it to BY DEFAULT, that an ordinary queue is left alone, and that replaying the migration is safe.

Deviations & judgment calls

Discoveries the plan did not know

  1. pgbus.gemspec needed no change. It selects files with git ls-files filtered on lib/, so the new fixups/ subdirectory ships once committed. Verified before relying on it rather than assumed.
  2. An ordinary queue still uses GENERATED ALWAYS in 1.13.0. Only the partitioned path moved to BY DEFAULT. My first integration assertion expected the whole schema to have moved and failed; the corrected example now pins the more useful property — that the fixup is scoped by pgmq.meta.is_partitioned and does not touch ordinary queues.

Judgment calls

  • The fixup is proven against a stand-in, not a real partitioned queue. CI installs no pg_partman, so the spec creates a plain table registered as is_partitioned in pgmq.meta. The fixup's loop selects purely on that flag, so the stand-in drives the identical code path without adding an extension dependency to CI.
  • Only upstream's first block is vendored as the fixup. The rest of pgmq--1.12.0--1.13.0.sql (the DROP FUNCTION, the new create_partitioned, the ALTER TYPE, the new metrics()) is already produced by drop-and-reapply; carrying it twice would fail on the second run.
  • Fixups must be idempotent, and that is load-bearing. An install whose version was never recorded gets every fixup up to the target, since its history is unknowable. Stated in the directory's header comment and pinned by a replay example.
  • The stale "Next release not yet cut" callout on the 1.11.1 → 1.12.0 docs section was removed while editing that file; 1.12.0 shipped in v0.12.0.

Review round (cubic, 10 threads)

Five threads landed on lib/pgbus/pgmq_schema/pgmq_v1.13.0.sql. That file is a byte-exact copy of upstream (git hash-object = 1fde328b…), so all five were declined: patching it forks pgbus from upstream and breaks the verification that is the file's whole contract. Four of the five flag constructs that are byte-identical in pgmq_v1.12.0.sql, shipped in v0.12.0 — pre-existing upstream behaviour, not introduced by this hop. The fifth (default_partition_length) is genuinely new here but is still upstream's code; pgbus never creates partitioned queues, so it never runs.

Five were accepted, in 57ecb1b:

  1. The fixup now resolves the queue table by OID. It matched information_schema.columns.table_name against a formatted name, but validate_queue_name caps the queue at 47 characters while PostgreSQL truncates identifiers at 63 bytes — so a multibyte name could be silently skipped and left at GENERATED ALWAYS, the exact failure the fixup exists to prevent. Now to_regclass + pg_attribute.attidentity. This makes the fixup a deliberate deviation from upstream rather than a verbatim copy, so its header no longer claims otherwise.
  2. The replay test never proved what it claimed. On the second migration run the recorded version has already advanced, so fixups_sql returns "" and the fixup SQL never ran twice. Added an example that re-applies it directly, which is what actually pins the load-bearing idempotence invariant.
  3. available_versions and fixup_versions now share a private versions_in(dir), so discovery rules cannot drift.
  4. The "excludes a fixup newer than the target" example was a literal duplicate; it now pins the ceiling against the nil-floor path, which nothing covered.
  5. CHANGELOG scopes the upgrade instructions to embedded installs and names the extension path (ALTER EXTENSION pgmq UPDATE).

Declined as out of scope: making the generator reject extension-tracked databases. The drop-and-reinstall path predates this PR (#364) — this PR adds only the fixup step — and it fails loudly rather than silently, since PostgreSQL refuses to drop extension-owned functions. Worth a follow-up issue.


Summary by cubic

Vendors PGMQ v1.13.0 and adds a table-fixup step to the generated upgrade migration so upstream's 1.13.0 table change actually lands. The upgrade previously dropped every pgmq function and composite type and re-ran the target version's schema, which reproduces all function and type changes but never alters an existing table; the migration now applies every fixup shipped for versions between the recorded installed version and the target.

What changes

  • The 1.12.0 → 1.13.0 delta is partitioned-queue work plus one metrics attribute: create_partitioned gains premake (dropping its three-argument form), metrics_result gains default_partition_length, and new partitioned queues use msg_id … GENERATED BY DEFAULT instead of GENERATED ALWAYS.
  • Fixups live in lib/pgbus/pgmq_schema/fixups/; version discovery now shares versions_in(dir) with schema files so the two can't drift.
  • The fixup resolves the queue table by OID via to_regclass instead of comparing formatted names, so multibyte queue names aren't silently skipped; this is a documented deviation from upstream's guard.
  • An install with no recorded version gets every fixup up to the target, so each one must stay idempotent; integration coverage now re-applies the fixup SQL directly to prove it.
  • Nothing pgbus calls through Pgbus::Client changes shape, so no pgmq-ruby bump is needed.

Migration

  • Fresh :embedded and versionless :auto installs get 1.13.0 automatically.
  • Existing :embedded installs: run rake pgbus:pgmq:status, then rails generate pgbus:upgrade_pgmq and rails db:migrate (or rails db:migrate:pgbus on a separate-database install). :extension installs upgrade via ALTER EXTENSION pgmq UPDATE instead.

Integration coverage proves the fixup end to end: dropping and re-creating the functions alone leaves msg_id at ALWAYS, the full migration moves it to BY DEFAULT, an ordinary queue is untouched, and replaying the migration is safe. Closes #459.

Written for commit 57ecb1b. Summary will update on new commits.

Review in cubic

…e upgrade

## Summary
Vendors pgmq-extension/sql/pgmq.sql at v1.13.0 byte-exact. The delta is
entirely partitioned-queue work plus one metrics attribute; nothing pgbus
calls through Pgbus::Client changes shape.

The upgrade migration drops every pgmq function and composite type and
re-runs the target schema, which can never alter an existing table.
Upstream's 1.13.0 migration does (partitioned queues' msg_id moves to
GENERATED BY DEFAULT), so PgmqSchema gains per-hop fixups discovered from
lib/pgbus/pgmq_schema/fixups/pgmq_v<VERSION>.sql, and the generated
migration applies every fixup between the recorded installed version and
the target, after the re-install and before the NOTIFY repair. Fixups
must be idempotent: an install with no recorded version gets them all.

## Test Coverage
- pgmq_schema_spec: 1.13.0 discovery, install_sql content, fixup_versions,
  fixups_sql window semantics (nil after, same version, no-fixup hop)
- upgrade_pgmq_generator_spec: fixup step present, ordered after install
  and before the NOTIFY repair, reads the tracking table defensively
- pgmq_schema_upgrade_spec (integration): 1.12.0 -> 1.13.0 hop, new
  create_partitioned signature, default_partition_length in metrics,
  fixup proven on a partitioned stand-in, ordinary queue untouched,
  replay safe

## Verification
- [x] rubocop (CI command) clean
- [x] bare rspec green (4886)
- [x] integration green (222)
- [x] docs rake lint + rspec green

Closes #459
@cubic-dev-ai

cubic-dev-ai Bot commented Sep 10, 2026

Copy link
Copy Markdown

Running ultrareview automatically — This PR changes the core upgrade migration path: it introduces fixup version discovery, applies upstream table-level ALTERs (partitioned queues' msg_id identity), and vendors 2,193 lines of new schema SQL.... I'll post findings when complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ultrareview completed in 15m 33s

All reported issues were addressed across 10 files

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

Comment thread lib/pgbus/pgmq_schema/pgmq_v1.13.0.sql
Comment thread lib/pgbus/pgmq_schema/pgmq_v1.13.0.sql
Comment thread lib/pgbus/pgmq_schema/pgmq_v1.13.0.sql
Comment thread lib/pgbus/pgmq_schema/pgmq_v1.13.0.sql
Comment thread lib/pgbus/pgmq_schema/pgmq_v1.13.0.sql
Comment thread CHANGELOG.md Outdated
Comment thread lib/pgbus/pgmq_schema/fixups/pgmq_v1.13.0.sql Outdated
Comment thread spec/pgbus/pgmq_schema_spec.rb Outdated
Comment thread lib/pgbus/pgmq_schema.rb Outdated
Comment thread spec/integration/pgmq_schema_upgrade_spec.rb
CHANGELOG.md [Unreleased] resolved as a union: main's `coalesce:` and
concurrency-visibility entries kept alongside this branch's PGMQ 1.13.0
entry under the single `### Added` heading.

Claude-Session: https://claude.ai/code/session_017jHYpRCkQkiZHb2etMFH8p
- pgmq_schema.rb: extract `versions_in(dir)` so schema-file and fixup
  version discovery cannot drift into different rules.
- fixups/pgmq_v1.13.0.sql: resolve the queue table by OID via to_regclass
  instead of matching a formatted name against information_schema.
  validate_queue_name caps at 47 characters but PostgreSQL truncates
  identifiers at 63 bytes, so a multibyte queue name could be silently
  skipped and left at GENERATED ALWAYS — the exact failure the fixup
  exists to prevent. Header updated: this is now a documented deviation
  from upstream, not a verbatim copy.
- pgmq_schema_spec.rb: the "excludes a fixup newer than the target"
  example duplicated the hop-with-no-fixup one; it now pins the ceiling
  against the nil-floor path, which nothing else covered.
- pgmq_schema_upgrade_spec.rb: replaying the migration re-runs no fixup
  (the recorded version has already advanced), so the replay example
  never proved fixup idempotence. Added an example that re-applies the
  fixup SQL directly.
- CHANGELOG: scope the upgrade instructions to embedded installs and
  name the extension upgrade path.

Claude-Session: https://claude.ai/code/session_017jHYpRCkQkiZHb2etMFH8p
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Upstream release: PGMQ 1.13.0 — vendor + per-hop table fixups in the upgrade migration

1 participant