Skip to content
Open
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
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,48 @@ defmodule Weather do # is for later at now
end
```

Posibility to rename a table during development
-------------------------------------------------

`ecto_migrate` supports ability to rename a database table during development process. For this case add the
`old_tablename/0` function that will just return old table name.

For example your old model looks like this:

```elixir
defmodule Weather do # is for later at now
use Ecto.Model
use Ecto.Migration.Auto.Index

schema "weather" do
field :city
field :temp_lo, :integer
field :temp_hi, :integer
field :prcp, :float, default: 0.0
end
end
```

Now you can add `old_tablename/0` function and after recompilation `ecto_migrate` will update table name in the database:

```elixir
defmodule Weather do # is for later at now
use Ecto.Model
use Ecto.Migration.Auto.Index

schema "weather_updated" do
field :city
field :temp_lo, :integer
field :temp_hi, :integer
field :prcp, :float, default: 0.0
end

def old_tablename do
"weather"
end
end
```

Upgrades in 0.3.x versions
--------------------------

Expand Down
53 changes: 37 additions & 16 deletions lib/ecto/migration/auto.ex
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,13 @@ defmodule Ecto.Migration.Auto do
{related_field, tablename} = get_tablename(module, tablename, opts)
tableatom = tablename |> String.to_atom
for_opts = {related_field, opts[:for]}

{fields_changes, relateds} = repo.get(SystemTable, tablename) |> Field.check(tableatom, module, for_opts)
index_changes = (from s in SystemTable.Index, where: ^tablename == s.tablename) |> repo.all |> Index.check(tableatom, module)

if migration_module = check_gen(tableatom, module, fields_changes, index_changes, opts) do
Ecto.Migrator.up(repo, random, migration_module)
Field.update_meta(repo, module, tablename, relateds) # May be in transaction?
Index.update_meta(repo, module, tablename, index_changes)
Ecto.Migrator.up(repo, random, migration_module)
{_, rename?, _} = fields_changes
Field.update_meta(repo, module, tablename, relateds, rename?) # May be in transaction?
Index.update_meta(repo, module, tablename, index_changes, rename?)
end
end
end
Expand All @@ -77,7 +76,11 @@ defmodule Ecto.Migration.Auto do
tablename = module.__schema__(:source)
case function_exported?(module, :__sources__, 0) do
true -> module.__sources__()
false -> [tablename]
false ->
case function_exported?(module, :old_tablename, 0) do
true -> [module.old_tablename]
false -> [tablename]
end
end
end

Expand All @@ -98,8 +101,8 @@ defmodule Ecto.Migration.Auto do
defp related_mod?(%Ecto.Association.Has{related: mod, queryable: {_, _}}, mod), do: true
defp related_mod?(_, _), do: false

defp check_gen(_tablename, _module, {false, []}, {[], []}, _opts), do: nil
defp check_gen(tablename, module, {create?, changed_fields}, {create_indexes, delete_indexes}, opts) do
defp check_gen(_tablename, _module, {false, false, []}, {[], []}, _opts), do: nil
defp check_gen(tablename, module, {create?, _rename?, changed_fields}, {create_indexes, delete_indexes}, opts) do
migration_module = migration_module(module, opts)
up = gen_up(module, tablename, create?, changed_fields, create_indexes, delete_indexes)
quote do
Expand All @@ -125,20 +128,27 @@ defmodule Ecto.Migration.Auto do
end
end

defp gen_up_table(module, tablename, true, changed_fields) do
key? = module.__schema__(:primary_key) == [:id]
defp gen_up_table(module, tablename, changed_fields) do
rename = rename_table(module, module.__schema__(:source), tablename)
quote do
create table(unquote(tablename), primary_key: unquote(key?)) do
alter table(unquote(tablename)) do
unquote(changed_fields)
end
unquote(rename)
end
end

defp gen_up_table(_module, tablename, false, changed_fields) do
quote do
alter table(unquote(tablename)) do
unquote(changed_fields)
end
defp gen_up_table(module, tablename, create?, changed_fields) do
case create? do
false ->
gen_up_table(module, tablename, changed_fields)
true ->
key? = module.__schema__(:primary_key) == [:id]
quote do
create table(unquote(tablename), primary_key: unquote(key?)) do
unquote(changed_fields)
end
end
end
end

Expand All @@ -149,6 +159,17 @@ defmodule Ecto.Migration.Auto do
end
end

defp rename_table(module, new_tablename, old_tablename) do
case function_exported?(module, :old_tablename, 0) do
true ->
quote do
rename table(unquote(old_tablename)), to: table(unquote(new_tablename |> String.to_atom))
end
false ->
""
end
end

@migration_tables [{SystemTable.Index, SystemTable.Index.Migration}, {SystemTable, SystemTable.Migration}]
defp ensure_exists(repo) do
for {model, migration} <- @migration_tables do
Expand Down
11 changes: 7 additions & 4 deletions lib/ecto/migration/auto/field.ex
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
defmodule Ecto.Migration.Auto.Field do
import Ecto.Query
alias Ecto.Migration.SystemTable

@doc """
Update meta information in repository
"""
def update_meta(repo, module, tablename, relateds) do
def update_meta(repo, module, tablename, relateds, table_renamed?) do
metainfo = module.__schema__(:fields) |> Stream.map(&field_to_meta(&1, module, relateds)) |> Enum.join(",")
updated_info = %SystemTable{tablename: tablename, metainfo: metainfo}
if repo.get(SystemTable, tablename) do
repo.update!(updated_info)
else
repo.insert!(updated_info)
end
case table_renamed? do
true -> repo.update_all(from(s in SystemTable, where: s.tablename == ^tablename), set: [tablename: module.__schema__(:source)])
_ -> :ok
end
end

defp field_to_meta(field, module, relateds) do
Expand All @@ -30,11 +35,9 @@ defmodule Ecto.Migration.Auto.Field do
relateds = associations(module)
metainfo = old_keys(old_fields)
new_fields = module.__schema__(:fields)

add_fields = add(module, new_fields, metainfo, relateds, for_opts)
remove_fields = remove(new_fields, metainfo)

{{old_fields == nil, remove_fields ++ add_fields}, relateds}
{{old_fields == nil, function_exported?(module, :old_tablename, 0), remove_fields ++ add_fields}, relateds}
end

defp old_keys(nil), do: []
Expand Down
15 changes: 11 additions & 4 deletions lib/ecto/migration/auto/index.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
defmodule Ecto.Migration.Auto.Index do
import Ecto.Query

alias Ecto.Migration.SystemTable
alias Ecto.Migration.SystemTable.Index

defmacro __using__(_opts) do
Expand Down Expand Up @@ -27,12 +29,19 @@ defmodule Ecto.Migration.Auto.Index do
@doc """
Update meta information in repository
"""
def update_meta(_repo, _module, _tablename, {[], []}), do: nil
def update_meta(repo, module, tablename, _) do
def update_meta(_repo, _module, _tablename, {[], []}, false), do: nil
def update_meta(repo, module, tablename, _, true) do
from(s in SystemTable.Index, where: s.tablename == ^tablename) |> repo.update_all(set: [tablename: module.__schema__(:source)])
end
def update_meta(repo, module, tablename, _, table_renamed?) do
(from s in Index, where: s.tablename == ^tablename) |> repo.delete_all
for {columns, opts} <- all(module) do
repo.insert!(Map.merge(%Index{tablename: tablename, index: Enum.join(columns, ",")}, :maps.from_list(opts)))
end
case table_renamed? do
true -> from(s in SystemTable.Index, where: s.tablename == ^tablename) |> repo.update_all(set: [tablename: module.__schema__(:source)])
_ -> :ok
end
end

@doc """
Expand All @@ -41,10 +50,8 @@ defmodule Ecto.Migration.Auto.Index do
def check(old_indexes, tablename, module) do
new_indexes = all(module) |> Enum.map(&merge_default/1)
old_indexes = old_indexes |> Enum.map(&transform_from_db/1)

create_indexes = (new_indexes -- old_indexes) |> create(tablename)
delete_indexes = (old_indexes -- new_indexes) |> delete(tablename)

{create_indexes, delete_indexes}
end

Expand Down
2 changes: 1 addition & 1 deletion lib/ecto/migration/system_table.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ defmodule Ecto.Migration.SystemTable.Index.Migration do
add :tablename, :string
add :index, :string
add :name, :string
add :concurrently
add :concurrently, :boolean
add :unique, :boolean
add :using, :string
end
Expand Down
13 changes: 12 additions & 1 deletion test/ecto_migrate_test.exs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
defmodule TestModel do
use Ecto.Schema
use Ecto.Model
use Ecto.Migration.Auto.Index

index(:l, using: "hash")
Expand Down Expand Up @@ -72,5 +72,16 @@ defmodule EctoMigrateTest do
assert tags.id == 1
assert tags.model == "Elixir.MyModel"
assert tags.name == "test_tag"

[query] = (from t in Ecto.Migration.SystemTable, where: t.tablename == "ecto_migrate_test_table") |> EctoIt.Repo.all
assert query.tablename == "ecto_migrate_test_table"

Code.require_file(File.cwd! <> "/test/test_model.exs")
Ecto.Migration.Auto.migrate(EctoIt.Repo, TestModel)

[query] = (from t in Ecto.Migration.SystemTable, where: t.tablename == "test_table_2") |> EctoIt.Repo.all
assert query.tablename == "test_table_2"
assert query.metainfo == "id:id,f:string,l:boolean"
assert (from t in Ecto.Migration.SystemTable, where: t.tablename == "ecto_migrate_test_table") |> EctoIt.Repo.all == []
end
end
19 changes: 19 additions & 0 deletions test/test_model.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# We use this for testing of the renaming table. The ecto_migrate_test.exs
# contains the same module but with the other table name and set of the
# schema fields.
defmodule TestModel do
use Ecto.Model
use Ecto.Migration.Auto.Index

index(:l, using: "hash")
index(:f)

schema "test_table_2" do
field :f, :string
field :l, :boolean
end

def old_tablename do
"ecto_migrate_test_table"
end
end