Skip to content

fix(postgres): correct SQL for modifying a column to :identity#744

Open
nabsei wants to merge 1 commit into
elixir-ecto:masterfrom
nabsei:fix/postgres-modify-identity-syntax
Open

fix(postgres): correct SQL for modifying a column to :identity#744
nabsei wants to merge 1 commit into
elixir-ecto:masterfrom
nabsei:fix/postgres-modify-identity-syntax

Conversation

@nabsei

@nabsei nabsei commented Jul 21, 2026

Copy link
Copy Markdown

Fixes #4751.

What

column_change/2 for {:modify, name, type, opts} reused column_type/2 as-is for the ALTER COLUMN ... TYPE ... clause. For type == :identity, that function produces "bigint GENERATED BY DEFAULT AS IDENTITY", which is valid inside a CREATE TABLE/ADD COLUMN column definition, but PostgreSQL rejects it combined with ALTER COLUMN ... TYPE:

ALTER TABLE "my_table" ALTER COLUMN "field" TYPE bigint GENERATED BY DEFAULT AS IDENTITY
** (Postgrex.Error) ERROR 42601 (syntax_error) syntax error at or near "GENERATED"

The identity clause must instead be added via a separate ALTER COLUMN ... ADD GENERATED BY DEFAULT AS IDENTITY action within the same ALTER TABLE statement.

Fix

  • modify_column_type/2 emits just the base type (column_type_name/2) for the TYPE clause when modifying to :identity, leaving column_type/2's behavior for CREATE TABLE/ADD COLUMN untouched.
  • New modify_identity/3 emits the separate ALTER COLUMN ... ADD GENERATED BY DEFAULT AS IDENTITY(...) clause, appended after the existing null/default/collation clauses.
  • The sequence-option logic (start_value/increment) is extracted into identity_generated_expr/1 and shared between the CREATE TABLE path and the new ALTER path, unchanged in behavior.

Verification

  • Full test suite: 683 passed, no regressions (confirmed pre-existing unrelated warnings/formatting diffs are identical on unmodified master).
  • Added two tests in test/ecto/adapters/postgres_test.exs covering {:modify, :field, :identity, []} and with start_value/increment options, matching the exact SQL PostgreSQL requires.
  • Ran the actual generated SQL against a real local PostgreSQL 16 instance (not just string-matched): confirmed both statements execute successfully, the column becomes a working identity column, and INSERT ... RETURNING shows the configured start_value is honored. Also confirmed the PostgreSQL requirement that the column be NOT NULL before adding an identity, and that this composes correctly with Ecto's existing null: false modify option on a nullable column.

This PR was prepared with the assistance of Claude Code (Anthropic's CLI-based coding agent). The root-cause analysis, fix design, and verification steps above were reviewed and confirmed by me before submitting.

column_change/2 for :modify reused column_type/2 as-is, which for
:identity produces "bigint GENERATED BY DEFAULT AS IDENTITY" - valid
in a CREATE TABLE/ADD COLUMN column definition, but PostgreSQL
rejects that combined with ALTER COLUMN ... TYPE:

  ERROR 42601 (syntax_error) syntax error at or near "GENERATED"

The identity clause must instead be added via a separate
ALTER COLUMN ... ADD GENERATED BY DEFAULT AS IDENTITY action.

Split the two concerns: modify_column_type/2 emits just the base
type for the TYPE clause when modifying to :identity, and the new
modify_identity/3 emits the separate ADD GENERATED clause. The
sequence-option logic (start_value/increment) is shared via the
extracted identity_generated_expr/1, unchanged from before.

Fixes #4751
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.

1 participant