diff --git a/lib/ecto/adapters/postgres/connection.ex b/lib/ecto/adapters/postgres/connection.ex index deae2379..f4575cee 100644 --- a/lib/ecto/adapters/postgres/connection.ex +++ b/lib/ecto/adapters/postgres/connection.ex @@ -1269,7 +1269,9 @@ if Code.ensure_loaded?(Postgrex) do table_name = quote_name(table.prefix, table.name) query = [ - "CREATE TABLE ", + "CREATE ", + modifiers_expr(table.modifiers), + "TABLE ", if_do(command == :create_if_not_exists, "IF NOT EXISTS "), table_name, ?\s, @@ -1760,6 +1762,10 @@ if Code.ensure_loaded?(Postgrex) do defp include_expr(literal), do: quote_name(literal) + defp modifiers_expr(nil), do: [] + defp modifiers_expr(<<_, _::binary>> = value), do: [String.upcase(String.trim(value)), ?\s] + defp modifiers_expr(value), do: error!(nil, "PostgreSQL adapter expects :modifiers to be a non-empty string or nil, got #{inspect(value)}") + defp options_expr(nil), do: [] diff --git a/lib/ecto/migration.ex b/lib/ecto/migration.ex index c36dcfdd..c97893ef 100644 --- a/lib/ecto/migration.ex +++ b/lib/ecto/migration.ex @@ -479,7 +479,7 @@ defmodule Ecto.Migration do To define a table in a migration, see `Ecto.Migration.table/2`. """ - defstruct name: nil, prefix: nil, comment: nil, primary_key: true, engine: nil, options: nil + defstruct name: nil, prefix: nil, comment: nil, primary_key: true, engine: nil, options: nil, modifiers: nil @type t :: %__MODULE__{ name: String.t(), @@ -487,7 +487,8 @@ defmodule Ecto.Migration do comment: String.t() | nil, primary_key: boolean | keyword(), engine: atom, - options: String.t() + options: String.t(), + modifiers: String.t() | nil } end @@ -824,6 +825,10 @@ defmodule Ecto.Migration do * `:options` - provide custom options that will be appended after the generated statement. For example, "WITH", "INHERITS", or "ON COMMIT" clauses. "PARTITION BY" can be provided for databases that support table partitioning. + * `:modifiers` - provide custom modifiers that should be inserted to the + table creation statement, between the tokens "CREATE" and "TABLE". For + example, "UNLOGGED", "GLOBAL", "TEMPORARY", or "GLOBAL TEMPORARY" in + PostgreSQL. """ def table(name, opts \\ []) diff --git a/lib/ecto/migration/runner.ex b/lib/ecto/migration/runner.ex index 98138ef9..085d6734 100644 --- a/lib/ecto/migration/runner.ex +++ b/lib/ecto/migration/runner.ex @@ -434,6 +434,12 @@ defmodule Ecto.Migration.Runner do defp command(ddl) when is_binary(ddl) or is_list(ddl), do: "execute #{inspect(ddl)}" + defp command({:create, %Table{modifiers: <<_, _::binary>>} = table, _}), + do: "create #{render_modifiers(table.modifiers)} table #{quote_name(table.prefix, table.name)}" + + defp command({:create, %Table{modifiers: value}, _}) when not is_nil(value), + do: raise(ArgumentError, "the value of :modifiers must be a non-empty string or nil, got #{inspect(value)}") + defp command({:create, %Table{} = table, _}), do: "create table #{quote_name(table.prefix, table.name)}" @@ -502,4 +508,6 @@ defmodule Ecto.Migration.Runner do defp quote_name(prefix, name), do: quote_name(prefix) <> "." <> quote_name(name) defp quote_name(name) when is_atom(name), do: quote_name(Atom.to_string(name)) defp quote_name(name), do: name + + defp render_modifiers(value), do: String.downcase(String.trim(value)) end diff --git a/test/ecto/adapters/postgres_test.exs b/test/ecto/adapters/postgres_test.exs index 2c4695d0..d283cafb 100644 --- a/test/ecto/adapters/postgres_test.exs +++ b/test/ecto/adapters/postgres_test.exs @@ -2285,6 +2285,22 @@ defmodule Ecto.Adapters.PostgresTest do ] end + test "create table with modifiers" do + create = + {:create, table(:posts, modifiers: "UNLOGGED"), + [ + {:add, :id, :serial, [primary_key: true]}, + {:add, :created_at, :naive_datetime, []} + ]} + + assert execute_ddl(create) == [ + """ + CREATE UNLOGGED TABLE "posts" ("id" serial, "created_at" timestamp(0), PRIMARY KEY ("id")) + """ + |> remove_newlines + ] + end + test "create table with composite key" do create = {:create, table(:posts), diff --git a/test/ecto/migrator_test.exs b/test/ecto/migrator_test.exs index a33d6530..98b52530 100644 --- a/test/ecto/migrator_test.exs +++ b/test/ecto/migrator_test.exs @@ -188,6 +188,16 @@ defmodule Ecto.MigratorTest do def change, do: flush() end + defmodule CreateTableWithModifiersMigration do + use Ecto.Migration + + def change do + create table(:sessions, modifiers: "UNLOGGED") do + add :id, :id + end + end + end + @moduletag migrated_versions: [{1, nil}, {2, nil}, {3, nil}] setup context do @@ -339,6 +349,15 @@ defmodule Ecto.MigratorTest do assert output =~ "== Running 12 Ecto.MigratorTest.UpDownMigration.down/0" assert output =~ "execute \"foo\"" assert output =~ ~r"== Migrated 12 in \d.\ds" + + output = + capture_log(fn -> + :ok = up(TestRepo, 13, CreateTableWithModifiersMigration) + end) + + assert output =~ "== Running 13 Ecto.MigratorTest.CreateTableWithModifiersMigration.change/0" + assert output =~ "create unlogged table sessions" + assert output =~ ~r"== Migrated 13 in \d.\ds" end test "logs ddl notices" do