New alter_pk feature idea

24 views
Skip to first unread message

Max zimmermann

unread,
Dec 4, 2025, 8:49:45 PM (12 days ago) Dec 4
to elixir-ecto
Add `alter_pk/3` for changing primary key columns.

Hi, I'm Max new to the elixir ecosystem. I started using it with my current role, as part of it I recently had to change the primary keys of more then 40 tables in the project. To make those kind of task a bit more enjoyable I'd like to propose a new migration function which essentially just swaps the pk to a given column of a given table. 

So migrations like this: 
def up do
    # 1. Drop foreign keys
    execute "ALTER TABLE some_table DROP CONSTRAINT some_table_status_id_fkey"

    # 2. Add new FK column
    alter table(:some_table) do
      add :some_table_status_code, :string
    end

    # 3. Migrate data
    execute """
    UPDATE some_table a
    SET some_table_status_code = s.code
    FROM some_table_statuses s
    WHERE a.some_table_status_id = s.id
    """

    # 4. Drop old PK (manual SQL)
    execute "ALTER TABLE some_table_statuses DROP CONSTRAINT some_table_statuses_pkey"

    # 5. Add new PK (manual SQL)
    execute "ALTER TABLE some_table_statuses ADD PRIMARY KEY (code)"

    # 6. Recreate FK
    execute """
    ALTER TABLE some_table
    ADD CONSTRAINT some_table_status_code_fkey
    FOREIGN KEY (some_table_status_code) REFERENCES some_table_statuses(code)
    """

    # 7. Clean up
    alter table(:some_table_statuses) do
      remove :id
    end

    alter table(:some_table) do
      remove :some_table_status_id
    end
  end

Could be simplified like this:
def change do
    # 1. Drop foreign keys
    drop constraint("some_table", "some_table_status_id_fkey")

    # 2. Add new FK column
    alter table(:some_table) do
      add :some_table_status_code, :string
    end

    # 3. Migrate data
    execute """
    UPDATE some_table a
    SET some_table_status_code = s.code
    FROM some_table_statuses s
    WHERE a.some_table_status_id = s.id
    """

    # 4-5. Change PK (simplified!)
    alter_pk table(:some_table_statuses), :id, to: :code

    # 6. Recreate FK
    alter table(:some_table) do
      modify :some_table_status_code, references(:some_table_statuses, column: :code)
    end

    # 7. Clean up
    alter table(:some_table_statuses) do
      remove :id
    end

    alter table(:some_table) do
      remove :some_table_status_id
    end
  end

Features:
1. No CASCADE - Fails if foreign keys exist (forces manual handling)
2. No automatic data migration - User handle data themselves 
3. Structure-only - Only changes the primary key constraint
4. Reversible - Works with change/0 migrations

Implementation Status:
I have a working implementation for Postgres including:

- Ecto.Migration.alter_pk/3
- Ecto.Adapter.Postgres.Connection.execute_ddl/1
- Ecto.Migration.Runner.command/1
- Ecto.Migration.Runner.reverse/1 

I would extend to MySQL, MSSQL, tests, and documentation

I'm excited to hear your thoughts on this!
Best wishes Max Zimmermann

Reply all
Reply to author
Forward
0 new messages