Skip to content

fix: Upgrade existing publications to publish generated columns on PG18+#4045

Open
alco wants to merge 7 commits intomainfrom
fix-generated-cols-publication
Open

fix: Upgrade existing publications to publish generated columns on PG18+#4045
alco wants to merge 7 commits intomainfrom
fix-generated-cols-publication

Conversation

@alco
Copy link
Member

@alco alco commented Mar 23, 2026

Summary

  • When Electric starts with an existing publication that was created before PostgreSQL 18 generated columns support was added, the publication lacks publish_generated_columns = stored
  • This PR automatically detects this condition and runs ALTER PUBLICATION ... SET (publish_generated_columns = stored) to upgrade the publication
  • Adds pg_supports_generated_columns? field to publication_status to distinguish "PG < 18" from "PG >= 18 but publication not configured"

Context

Electric only sets publish_generated_columns = stored during CREATE PUBLICATION (added in PR #3297). If a user was on PG < 18 when the publication was created, then upgraded to PG18, the existing publication wouldn't have this setting. This would cause shapes with generated columns to fail with a 503 error.

Fixes #4038

Test plan

  • Added test for pg_supports_generated_columns? field in check_publication_status!
  • Added test for alter_publication_set_generated_columns/2 function
  • Added integration test that simulates the upgrade scenario end-to-end
  • All existing tests pass
  • Tests are conditional on PG18+ (skipped on older versions)

🤖 Generated with Claude Code

erik-the-implementer and others added 3 commits March 23, 2026 18:42
When Electric starts with an existing publication that was created before
PostgreSQL 18 generated columns support, automatically ALTER the publication
to add `publish_generated_columns = stored` if the database is PG18+.

Fixes #4038

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@alco alco added the claude label Mar 23, 2026
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@codecov
Copy link

codecov bot commented Mar 23, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.67%. Comparing base (9335ae6) to head (621966c).
⚠️ Report is 3 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4045      +/-   ##
==========================================
+ Coverage   85.78%   88.67%   +2.88%     
==========================================
  Files          51       25      -26     
  Lines        3399     2438     -961     
  Branches      611      609       -2     
==========================================
- Hits         2916     2162     -754     
+ Misses        481      274     -207     
  Partials        2        2              
Flag Coverage Δ
elixir ?
elixir-client ?
packages/experimental 87.73% <ø> (ø)
packages/react-hooks 86.48% <ø> (ø)
packages/start 82.83% <ø> (ø)
packages/typescript-client 93.81% <ø> (ø)
packages/y-electric 56.05% <ø> (ø)
typescript 88.67% <ø> (ø)
unit-tests 88.67% <ø> (+2.88%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 63fa650530

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +259 to +263
%{
pg_supports_generated_columns?: true,
publishes_generated_columns?: false,
can_alter_publication?: true
} = status,

Choose a reason for hiding this comment

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

P2 Badge Skip the generated-column upgrade in manual publishing mode

When ELECTRIC_MANUAL_TABLE_PUBLISHING=true, the rest of this module avoids mutating the publication via can_update_publication?/1, but this new branch bypasses that guard and will still run ALTER PUBLICATION whenever a PG18 publication reports publish_generated_columns = none. In practice, simply starting Electric against a manually managed publication can now rewrite it, which violates the manual-mode contract and creates drift from the operator-managed SQL.

Useful? React with 👍 / 👎.

"Upgrading publication #{state.publication_name} to publish generated columns (PostgreSQL 18+ detected)"
)

Configuration.alter_publication_set_generated_columns(state.db_pool, state.publication_name)

Choose a reason for hiding this comment

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

P2 Badge Don't auto-upgrade publications explicitly set to none

This upgrade path cannot distinguish a publication that predates PG18 from one that an operator intentionally configured with publish_generated_columns = none: check_publication_status!/2 only gives us a boolean, and every false value reaches this ALTER PUBLICATION. That means a deployment relying on the existing none behavior (for example, to keep generated columns excluded and use the columns= workaround) will be silently flipped to stored on startup or the next shape addition, changing replication semantics for all tables instead of preserving the configured mode.

Useful? React with 👍 / 👎.

The test was checking that a 503 is returned when the publication
doesn't publish generated columns. With the auto-upgrade fix, the
configurator now automatically upgrades the publication, so the shape
request succeeds with 200 instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Mar 23, 2026

TEST BODY

@claude
Copy link

claude bot commented Mar 23, 2026

Claude Code Review

Summary

This PR fixes a real upgrade-path bug: when a publication was created on PG < 18 and the user later upgrades to PG18+, Electric now automatically issues ALTER PUBLICATION ... SET (publish_generated_columns = stored) to bring the publication up to date. The fix is minimal, well-placed, and well-tested. One important error handling issue needs attention before merge.

What's Working Well

  • Correct placement: Putting the upgrade logic in the configurator (not connection setup) is the right architectural decision — the configurator already handles periodic publication status checks and has the appropriate error-handling infrastructure.
  • Idiomatic pattern matching: maybe_upgrade_generated_columns/2 cleanly expresses the upgrade condition via a three-field destructure with a no-op fallthrough — very readable.
  • SQL injection safety: alter_publication_set_generated_columns/2 uses Utils.quote_name/1 to escape the publication name.
  • Appropriate logging: Logger.notice for an automatic schema-level change is the right severity.
  • Changeset included: .changeset/upgrade-publication-generated-columns.md is present and correctly scoped as a patch.

Issues Found

Important (Should Fix)

1. ALTER failure escalates to a global configuration error affecting all shapes

File: packages/sync-service/lib/electric/replication/publication_manager/configurator.ex:270

Issue: alter_publication_set_generated_columns/2 uses Postgrex.query!/3. When it raises (e.g., transient connection blip, unexpected permission error), the exception propagates out of the lambda passed to Configuration.run_handling_db_connection_errors/1. That function's rescue err -> {:error, err} clause catches it and returns {:error, %Postgrex.Error{}} from check_publication_status/1.

The caller in handle_continue({:check_publication, filters}, state) then calls notify_global_configuration_error(err, state) which signals a fatal error to all pending shapes, not just shapes that use generated columns.

Impact: A transient failure to upgrade the publication's generated-column setting would cause all shapes to fail with a configuration error until the connection subsystem restarts. Without this PR, a transient failure would leave publishes_generated_columns?: false in the status and only shapes that actually use generated columns would be affected.

Suggested fix: Change alter_publication_set_generated_columns/2 to use Postgrex.query/3 (non-bang) and return {:ok, _} | {:error, reason}, then handle the error gracefully in maybe_upgrade_generated_columns/2. On failure, returning status unchanged means only shapes that actually need generated columns are affected.

2. Unquoted publication name in test setup SQL

Files: test/electric/replication/publication_manager_test.exs:384 and test/electric/postgres/configuration_test.exs:338

The test setup uses unquoted string interpolation for the publication name. Production code uses Utils.quote_name/1; tests should be consistent for robustness.

Suggestions (Nice to Have)

3. PG < 18 tests silently pass with zero assertions: All three new tests wrap their body in if pg_version >= 180_000 do ... end with no else branch. On older PG versions, the tests pass vacuously. A comment or explicit skip tag would make the intent clear.

4. pg_supports_generated_columns? exposes an implementation detail: publication_status carries this field but only maybe_upgrade_generated_columns/2 uses it. Benign but worth noting.

Issue Conformance

Issue #4038 describes a user on PG18 getting a 400 error for shapes with stored generated columns. The PR correctly identifies that the user's reported 400 was likely due to running a pre-v1.2.0 Electric image, while the codebase had a separate real bug in the publication upgrade path (would produce 503s on PG18 with a pre-existing publication). The fix addresses the real bug precisely and the changeset accurately describes the scope. Issue conformance is solid.

Previous Review Status

No prior review — first iteration.


Review iteration: 1 | 2026-03-23

Use non-bang Postgrex.query/3 for the publication upgrade so that a
transient failure only affects shapes needing generated columns, rather
than escalating to a global configuration error affecting all shapes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Mar 23, 2026

Accidentally modified - please ignore

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Electric shape endpoint rejects stored generated columns on PostgreSQL 18

2 participants