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
54 changes: 36 additions & 18 deletions lib/ecto/adapters/postgres/connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1602,10 +1602,11 @@ if Code.ensure_loaded?(Postgrex) do
"ALTER COLUMN ",
quote_name(name),
" TYPE ",
column_type(type, opts),
modify_column_type(type, opts),
modify_null(name, opts),
modify_default(name, type, opts),
collation_expr(collation)
collation_expr(collation),
modify_identity(name, type, opts)
]
end

Expand All @@ -1632,6 +1633,19 @@ if Code.ensure_loaded?(Postgrex) do
defp column_change(_table, {:remove_if_exists, name}),
do: ["DROP COLUMN IF EXISTS ", quote_name(name)]

# `:identity`'s "GENERATED BY DEFAULT AS IDENTITY" is valid as part of a column
# definition (CREATE TABLE/ADD COLUMN), but PostgreSQL rejects it combined with
# ALTER COLUMN ... TYPE - it must be added via a separate ALTER COLUMN ... ADD
# GENERATED clause (see modify_identity/3 below).
defp modify_column_type(:identity, opts), do: column_type_name(:identity, opts)
defp modify_column_type(type, opts), do: column_type(type, opts)

defp modify_identity(name, :identity, opts) do
[", ALTER COLUMN ", quote_name(name), " ADD", identity_generated_expr(opts)]
end

defp modify_identity(_name, _type, _opts), do: []

defp modify_null(name, opts) do
case Keyword.get(opts, :null) do
true -> [", ALTER COLUMN ", quote_name(name), " DROP NOT NULL"]
Expand Down Expand Up @@ -1798,22 +1812,7 @@ if Code.ensure_loaded?(Postgrex) do

case Keyword.get(opts, :generated) do
nil when type == :identity ->
cleanup = fn v -> is_integer(v) and v > 0 end

sequence =
[Keyword.get(opts, :start_value)]
|> Enum.filter(cleanup)
|> Enum.map(&"START WITH #{&1}")
|> Kernel.++(
[Keyword.get(opts, :increment)]
|> Enum.filter(cleanup)
|> Enum.map(&"INCREMENT BY #{&1}")
)

case sequence do
[] -> [type_name, " GENERATED BY DEFAULT AS IDENTITY"]
_ -> [type_name, " GENERATED BY DEFAULT AS IDENTITY(", Enum.join(sequence, " "), ") "]
end
[type_name, identity_generated_expr(opts)]

nil ->
type_name
Expand All @@ -1827,6 +1826,25 @@ if Code.ensure_loaded?(Postgrex) do
end
end

defp identity_generated_expr(opts) do
cleanup = fn v -> is_integer(v) and v > 0 end

sequence =
[Keyword.get(opts, :start_value)]
|> Enum.filter(cleanup)
|> Enum.map(&"START WITH #{&1}")
|> Kernel.++(
[Keyword.get(opts, :increment)]
|> Enum.filter(cleanup)
|> Enum.map(&"INCREMENT BY #{&1}")
)

case sequence do
[] -> " GENERATED BY DEFAULT AS IDENTITY"
_ -> [" GENERATED BY DEFAULT AS IDENTITY(", Enum.join(sequence, " "), ") "]
end
end

defp column_type_name({:array, type}, opts) do
[column_type_name(type, opts), "[]"]
end
Expand Down
29 changes: 29 additions & 0 deletions test/ecto/adapters/postgres_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2391,6 +2391,35 @@ defmodule Ecto.Adapters.PostgresTest do
]
end

test "alter table modify to identity" do
alter =
{:alter, table(:posts),
[
{:modify, :field, :identity, []}
]}

assert execute_ddl(alter) == [
"""
ALTER TABLE "posts"
ALTER COLUMN "field" TYPE bigint,
ALTER COLUMN "field" ADD GENERATED BY DEFAULT AS IDENTITY
"""
|> remove_newlines
]
end

test "alter table modify to identity with options" do
alter =
{:alter, table(:posts),
[
{:modify, :field, :identity, [start_value: 1_000, increment: 10]}
]}

assert execute_ddl(alter) == [
~s|ALTER TABLE "posts" ALTER COLUMN "field" TYPE bigint, ALTER COLUMN "field" ADD GENERATED BY DEFAULT AS IDENTITY(START WITH 1000 INCREMENT BY 10) |
]
end

test "create table with generated column" do
create =
{:create, table(:posts),
Expand Down
Loading