diff --git a/lib/yacto/migration/gen_migration.ex b/lib/yacto/migration/gen_migration.ex index e17bb45..1e13031 100644 --- a/lib/yacto/migration/gen_migration.ex +++ b/lib/yacto/migration/gen_migration.ex @@ -1,17 +1,32 @@ defmodule Yacto.Migration.GenMigration do require Logger - defp convert_fields(types, attrs) do + defmodule GenMigrationError do + defexception [:message, :from, :to] + def message(error) do + "#{error.message} from: #{inspect error.from} to: #{inspect error.to}" + end + end + + defp convert_fields(types, attrs, primary_keys, autogenerate_id) do # types: # %{del: %{field: type}, # ins: %{field: type}} # attrs: # %{ins: %{field: attr}, # del: %{field: attr}} + # primary_keys: + # [eq: [field]] + # or + # [ins: [field]] + # autogenerate_id: + # :not_changed + # or + # {:create, {ecto_schema_field_name, database_field_name, type}} # result: - # [{field, {:add, {type, attr}} | + # [{field, {:add, {type, attr, is_primary_key, is_autogenerate}} | # :remove | - # {:modify, attr}}] + # {:modify, attr, is_primary_key, is_autogenerate}}] # get all field names type_fields = @@ -26,13 +41,23 @@ defmodule Yacto.Migration.GenMigration do field end - fields = type_fields ++ attr_fields + primary_key_fields = Keyword.get(primary_keys, :ins, []) + + autogenerate_field = + case autogenerate_id do + {:create, {_, field, _}} -> [field] + :not_changed -> [] + end + + fields = type_fields ++ attr_fields ++ primary_key_fields ++ autogenerate_field fields = fields |> Enum.sort() |> Enum.dedup() changes = for field <- fields do in_type_del = Map.has_key?(types.del, field) in_type_ins = Map.has_key?(types.ins, field) + is_primary_key = Enum.member?(primary_key_fields, field) + is_autogenerate = Enum.member?(autogenerate_field, field) cond do in_type_del && in_type_ins -> @@ -46,7 +71,7 @@ defmodule Yacto.Migration.GenMigration do [] end - [{field, :remove}, {field, {:add, type, attr}}] + [{field, :remove}, {field, {:add, type, attr, is_primary_key, is_autogenerate}}] in_type_del && !in_type_ins -> # :remove @@ -63,7 +88,7 @@ defmodule Yacto.Migration.GenMigration do [] end - [{field, {:add, type, attr}}] + [{field, {:add, type, attr, is_primary_key, is_autogenerate}}] !in_type_del && !in_type_ins -> # :modify @@ -75,55 +100,58 @@ defmodule Yacto.Migration.GenMigration do [] end - [{field, {:modify, attr}}] + [{field, {:modify, attr, is_primary_key, is_autogenerate}}] end end List.flatten(changes) end - def generate_fields(types, attrs, structure_to, _migration_opts) do - ops = convert_fields(types, attrs) + def generate_fields(types, attrs, primary_keys, autogenerate_id, structure_to, _migration_opts) do + ops = convert_fields(types, attrs, primary_keys, autogenerate_id) + + {remove_ops, other_ops} = Enum.split_with(ops, fn {_, op} -> op == :remove end) lines = - for {field, op} <- ops do - case op do - {:add, type, attr} -> - opts = attr - - is_primary_key = Enum.find(structure_to.primary_key, &(&1 == field)) != nil - opts = opts ++ if(is_primary_key, do: [primary_key: true], else: []) - - is_autogenerate = - if( - structure_to.autogenerate_id, - do: elem(structure_to.autogenerate_id, 0) == field, - else: false - ) - - opts = opts ++ if(is_autogenerate, do: [autogenerate: true], else: []) - - [" add(:#{field}, #{inspect(type)}, #{inspect(opts)})"] - - :remove -> - lines = [" remove(:#{field})"] - - if field == :id do - [" add(:_gen_migration_dummy, :integer, [])"] ++ - lines ++ - ["end"] ++ - ["alter table(#{inspect(structure_to.source)}) do"] ++ - [" remove(:_gen_migration_dummy)"] - else - lines - end + if length(remove_ops) > 0 do + lines = + for {field, :remove} <- remove_ops do + [" remove(:#{field})"] + end - {:modify, attr} -> - type = Map.fetch!(structure_to.types, field) - [" modify(:#{field}, :#{type}, #{inspect(attr)})"] - end + [" add(:_gen_migration_dummy, :integer, [])"] ++ + lines ++ + ["end"] ++ + ["alter table(#{inspect(structure_to.source)}) do"] ++ + [" remove(:_gen_migration_dummy)"] + else + [] end + lines = + lines ++ + for {field, op} <- other_ops do + case op do + {:add, type, attr, is_primary_key, is_autogenerate} -> + opts = attr + + opts = opts ++ if(is_primary_key, do: [primary_key: true], else: []) + + type = if type == :id && is_autogenerate, do: :bigserial, else: type + + [" add(:#{field}, #{inspect(type)}, #{inspect(opts)})"] + + {:modify, attr, is_primary_key, is_autogenerate} -> + type = Map.fetch!(structure_to.types, field) + opts = attr + opts = opts ++ if(is_primary_key, do: [primary_key: true], else: []) + + type = if type == :id && is_autogenerate, do: :bigserial, else: type + + [" modify(:#{field}, :#{type}, #{inspect(opts)})"] + end + end + List.flatten(lines) end @@ -389,6 +417,39 @@ defmodule Yacto.Migration.GenMigration do diff = Yacto.Migration.Structure.diff(structure_from, structure_to) rdiff = Yacto.Migration.Structure.diff(structure_to, structure_from) + case {structure_from.primary_key, structure_to.primary_key} do + {[], []} -> + :ok + + {[], _to} -> + :ok + + {_from, []} -> + :ok + + # TODO このロジックテストされてないのでテスト追加する + {from, to} when from == to -> + case diff.autogenerate_id do + :not_changed -> + :ok + + {:create, _to_value} -> + :ok + + _ -> + raise Yacto.Migration.GenMigration.GenMigrationError, + message: "AutogenerateID Primary key can not be changed", + from: structure_from, + to: structure_to + end + + {from, to} when from != to -> + raise Yacto.Migration.GenMigration.GenMigrationError, + message: "Primary key can not be changed", + from: structure_from, + to: structure_to + end + if diff == rdiff do :not_changed else @@ -404,7 +465,11 @@ defmodule Yacto.Migration.GenMigration do ["drop table(#{inspect(from_value)})"] {:create, _to_value} -> - ["create table(#{inspect(structure_to.source)})"] + [ + "create table(#{inspect(structure_to.source)}, primary_key: false) do", + " add(:id, :id)", + "end" + ] end lines = @@ -415,7 +480,14 @@ defmodule Yacto.Migration.GenMigration do _ -> ["alter table(#{inspect(structure_to.source)}) do"] ++ - generate_fields(diff.types, diff.meta.attrs, structure_to, migration_opts) ++ + generate_fields( + diff.types, + diff.meta.attrs, + diff.primary_key, + diff.autogenerate_id, + structure_to, + migration_opts + ) ++ ["end"] ++ generate_indices(diff.meta.indices, structure_to, migration_opts) end diff --git a/lib/yacto/migration/migrator.ex b/lib/yacto/migration/migrator.ex index 3f364b6..761bbcd 100644 --- a/lib/yacto/migration/migrator.ex +++ b/lib/yacto/migration/migrator.ex @@ -61,8 +61,8 @@ defmodule Yacto.Migration.Migrator do end defp run(repo, schema, migration, direction, operation, migrator_direction, opts) do - level = Keyword.get(opts, :log, :info) - sql = Keyword.get(opts, :log_sql, false) + level = Keyword.get(opts, :log, :debug) + sql = Keyword.get(opts, :log_sql, true) log = %{level: level, sql: sql} args = [self(), repo, direction, migrator_direction, log] diff --git a/lib/yacto/migration/structure.ex b/lib/yacto/migration/structure.ex index 1ab9c9b..7664e4a 100644 --- a/lib/yacto/migration/structure.ex +++ b/lib/yacto/migration/structure.ex @@ -1,14 +1,14 @@ defmodule Yacto.Migration.Structure do defstruct source: nil, prefix: nil, - primary_key: [:id], + primary_key: [], fields: [:id], field_sources: %{id: :id}, types: %{id: :id}, associations: [], embeds: [], read_after_writes: [], - autogenerate_id: {:id, :id, :id}, + autogenerate_id: nil, meta: %{attrs: %{}, indices: %{}} # undocumented keys: diff --git a/test/apps/custom_table_name/test/custom_table_name_test.exs b/test/apps/custom_table_name/test/custom_table_name_test.exs index 7fd8558..dba5a35 100644 --- a/test/apps/custom_table_name/test/custom_table_name_test.exs +++ b/test/apps/custom_table_name/test/custom_table_name_test.exs @@ -6,9 +6,12 @@ defmodule CustomTableNameTest do use Ecto.Migration def change(CustomTableName.Player.Schema.TestData) do - create table("customtablename_player") - alter table("customtablename_player") do - add(:name, :string, [default: "hoge", null: false, size: 100]) + create table(\"customtablename_player\", primary_key: false) do + add(:id, :id) + end + alter table(\"customtablename_player\") do + modify(:id, :bigserial, [primary_key: true]) + add(:name, :string, [default: \"hoge\", null: false, size: 100]) end end @@ -18,7 +21,7 @@ defmodule CustomTableNameTest do def __migration_structures__() do [ - {CustomTableName.Player.Schema.TestData, %Yacto.Migration.Structure{field_sources: %{id: :id, name: :name}, fields: [:id, :name], meta: %{attrs: %{name: %{default: "hoge", null: false, size: 100}}, indices: %{}}, source: "customtablename_player", types: %{id: :id, name: :string}}}, + {CustomTableName.Player.Schema.TestData, %Yacto.Migration.Structure{autogenerate_id: {:id, :id, :id}, field_sources: %{id: :id, name: :name}, fields: [:id, :name], meta: %{attrs: %{name: %{default: \"hoge\", null: false, size: 100}}, indices: %{}}, primary_key: [:id], source: \"customtablename_player\", types: %{id: :id, name: :string}}}, ] end diff --git a/test/apps/gen_migration/lib/gen_migration.ex b/test/apps/gen_migration/lib/gen_migration.ex index 17b58ae..d6c2ff4 100644 --- a/test/apps/gen_migration/lib/gen_migration.ex +++ b/test/apps/gen_migration/lib/gen_migration.ex @@ -31,6 +31,22 @@ defmodule GenMigration.Player3 do end end +defmodule GenMigration.Player4 do + use Yacto.Schema + + @impl Yacto.Schema + def dbname(), do: :player + + @primary_key {:id, :binary_id, autogenerate: true} + + schema @auto_source do + field(:name3, :string, meta: [null: false, size: 100]) + field(:value, :string) + index([:value, :name3]) + index([:name3, :value], unique: true) + end +end + defmodule GenMigration.Item do use Yacto.Schema diff --git a/test/apps/gen_migration/priv/migrations/2017-04-24T155528_gen_migration.exs b/test/apps/gen_migration/priv/migrations/2017-04-24T155528_gen_migration.exs new file mode 100644 index 0000000..53878b9 --- /dev/null +++ b/test/apps/gen_migration/priv/migrations/2017-04-24T155528_gen_migration.exs @@ -0,0 +1,121 @@ +defmodule GenMigration.Migration20170424155528 do + use Ecto.Migration + + def change(GenMigration.Coin) do + create table("genmigration_coin", primary_key: false) do + add(:id, :id) + end + alter table("genmigration_coin") do + modify(:id, :bigserial, [primary_key: true]) + add(:inserted_at, :naive_datetime, []) + add(:platform, :text, [null: false]) + add(:player_id, :string, [null: false]) + add(:quantity, :integer, [default: 0, null: false]) + add(:type_id, :integer, [null: false]) + add(:updated_at, :naive_datetime, []) + end + create index("genmigration_coin", [:player_id, :type_id, :platform], [name: "player_id_t_65380c9f", unique: true]) + end + def change(GenMigration.Item) do + create table("genmigration_item", primary_key: false) do + add(:id, :id) + end + alter table("genmigration_item") do + add(:_gen_migration_dummy, :integer, []) + remove(:id) + end + alter table("genmigration_item") do + remove(:_gen_migration_dummy) + add(:id, :binary_id, [primary_key: true]) + add(:name, :string, []) + end + end + def change(GenMigration.ManyIndex) do + create table("genmigration_manyindex", primary_key: false) do + add(:id, :id) + end + alter table("genmigration_manyindex") do + add(:aaaaaa, :string, []) + add(:bbbbbb, :string, []) + add(:cccccc, :string, []) + add(:dddddd, :string, []) + modify(:id, :bigserial, [primary_key: true]) + end + create index("genmigration_manyindex", [:aaaaaa, :bbbbbb, :cccccc, :dddddd], [name: "aaaaaa_bbbb_9a4e1a2f"]) + end + def change(GenMigration.Player) do + create table("player", primary_key: false) do + add(:id, :id) + end + alter table("player") do + modify(:id, :bigserial, [primary_key: true]) + add(:inserted_at, :naive_datetime, []) + add(:name, :string, []) + add(:updated_at, :naive_datetime, []) + add(:value, :integer, []) + end + end + def change(GenMigration.Player2) do + create table("player2", primary_key: false) do + add(:id, :id) + end + alter table("player2") do + modify(:id, :bigserial, [primary_key: true]) + add(:name2, :string, []) + add(:value, :string, []) + end + end + def change(GenMigration.Player3) do + create table("genmigration_player3", primary_key: false) do + add(:id, :id) + end + alter table("genmigration_player3") do + modify(:id, :bigserial, [primary_key: true]) + add(:name3, :string, [null: false, size: 100]) + add(:value, :string, []) + end + create index("genmigration_player3", [:name3, :value], [name: "name3_value_index", unique: true]) + create index("genmigration_player3", [:value, :name3], [name: "value_name3_index"]) + end + def change(GenMigration.Player4) do + create table("genmigration_player4", primary_key: false) do + add(:id, :id) + end + alter table("genmigration_player4") do + add(:_gen_migration_dummy, :integer, []) + remove(:id) + end + alter table("genmigration_player4") do + remove(:_gen_migration_dummy) + add(:id, :binary_id, [primary_key: true]) + add(:name3, :string, [null: false, size: 100]) + add(:value, :string, []) + end + create index("genmigration_player4", [:name3, :value], [name: "name3_value_index", unique: true]) + create index("genmigration_player4", [:value, :name3], [name: "value_name3_index"]) + end + + def change(_other) do + :ok + end + + def __migration_structures__() do + [ + {GenMigration.Coin, %Yacto.Migration.Structure{autogenerate_id: {:id, :id, :id}, field_sources: %{id: :id, inserted_at: :inserted_at, platform: :platform, player_id: :player_id, quantity: :quantity, type_id: :type_id, updated_at: :updated_at}, fields: [:id, :player_id, :type_id, :platform, :quantity, :inserted_at, :updated_at], meta: %{attrs: %{platform: %{null: false}, player_id: %{null: false}, quantity: %{default: 0, null: false}, type_id: %{null: false}}, indices: %{{[:player_id, :type_id, :platform], [unique: true]} => true}}, primary_key: [:id], source: "genmigration_coin", types: %{id: :id, inserted_at: :naive_datetime, platform: :text, player_id: :string, quantity: :integer, type_id: :integer, updated_at: :naive_datetime}}}, + {GenMigration.Item, %Yacto.Migration.Structure{autogenerate_id: {:id, :id, :binary_id}, field_sources: %{id: :id, name: :name}, fields: [:id, :name], primary_key: [:id], source: "genmigration_item", types: %{id: :binary_id, name: :string}}}, + {GenMigration.ManyIndex, %Yacto.Migration.Structure{autogenerate_id: {:id, :id, :id}, field_sources: %{aaaaaa: :aaaaaa, bbbbbb: :bbbbbb, cccccc: :cccccc, dddddd: :dddddd, id: :id}, fields: [:id, :aaaaaa, :bbbbbb, :cccccc, :dddddd], meta: %{attrs: %{}, indices: %{{[:aaaaaa, :bbbbbb, :cccccc, :dddddd], []} => true}}, primary_key: [:id], source: "genmigration_manyindex", types: %{aaaaaa: :string, bbbbbb: :string, cccccc: :string, dddddd: :string, id: :id}}}, + {GenMigration.Player, %Yacto.Migration.Structure{autogenerate_id: {:id, :id, :id}, field_sources: %{id: :id, inserted_at: :inserted_at, name: :name, updated_at: :updated_at, value: :value}, fields: [:id, :name, :value, :inserted_at, :updated_at], primary_key: [:id], source: "player", types: %{id: :id, inserted_at: :naive_datetime, name: :string, updated_at: :naive_datetime, value: :integer}}}, + {GenMigration.Player2, %Yacto.Migration.Structure{autogenerate_id: {:id, :id, :id}, field_sources: %{id: :id, name2: :name2, value: :value}, fields: [:id, :name2, :value], primary_key: [:id], source: "player2", types: %{id: :id, name2: :string, value: :string}}}, + {GenMigration.Player3, %Yacto.Migration.Structure{autogenerate_id: {:id, :id, :id}, field_sources: %{id: :id, name3: :name3, value: :value}, fields: [:id, :name3, :value], meta: %{attrs: %{name3: %{null: false, size: 100}}, indices: %{{[:name3, :value], [unique: true]} => true, {[:value, :name3], []} => true}}, primary_key: [:id], source: "genmigration_player3", types: %{id: :id, name3: :string, value: :string}}}, + {GenMigration.Player4, %Yacto.Migration.Structure{autogenerate_id: {:id, :id, :binary_id}, field_sources: %{id: :id, name3: :name3, value: :value}, fields: [:id, :name3, :value], meta: %{attrs: %{name3: %{null: false, size: 100}}, indices: %{{[:name3, :value], [unique: true]} => true, {[:value, :name3], []} => true}}, primary_key: [:id], source: "genmigration_player4", types: %{id: :binary_id, name3: :string, value: :string}}}, + ] + end + + def __migration_version__() do + 20170424155528 + end + + def __migration_preview_version__() do + nil + end +end diff --git a/test/apps/gen_migration/test/gen_migration_test.exs b/test/apps/gen_migration/test/gen_migration_test.exs index f45d34e..defbe04 100644 --- a/test/apps/gen_migration/test/gen_migration_test.exs +++ b/test/apps/gen_migration/test/gen_migration_test.exs @@ -27,8 +27,8 @@ defmodule GenMigrationTest do del: %{name: :string, value: :integer}, ins: %{name2: :string, value: :string} }, - primary_key: [del: [:id], ins: [:id2]], - autogenerate_id: {:changed, {:id, :id, :id}, {:id2, :id2, :binary_id}}, + primary_key: [ins: [:id2]], + autogenerate_id: {:create, {:id2, :id2, :binary_id}}, meta: %{attrs: %{del: %{}, ins: %{}}, indices: %{del: %{}, ins: %{}}} } == diff end @@ -38,8 +38,11 @@ defmodule GenMigrationTest do use Ecto.Migration def change(GenMigration.Player) do - create table("player") + create table("player", primary_key: false) do + add(:id, :id) + end alter table("player") do + modify(:id, :bigserial, [primary_key: true]) add(:inserted_at, :naive_datetime, []) add(:name, :string, []) add(:updated_at, :naive_datetime, []) @@ -53,7 +56,7 @@ defmodule GenMigrationTest do def __migration_structures__() do [ - {GenMigration.Player, %Yacto.Migration.Structure{field_sources: %{id: :id, inserted_at: :inserted_at, name: :name, updated_at: :updated_at, value: :value}, fields: [:id, :name, :value, :inserted_at, :updated_at], source: "player", types: %{id: :id, inserted_at: :naive_datetime, name: :string, updated_at: :naive_datetime, value: :integer}}}, + {GenMigration.Player, %Yacto.Migration.Structure{autogenerate_id: {:id, :id, :id}, field_sources: %{id: :id, inserted_at: :inserted_at, name: :name, updated_at: :updated_at, value: :value}, fields: [:id, :name, :value, :inserted_at, :updated_at], primary_key: [:id], source: "player", types: %{id: :id, inserted_at: :naive_datetime, name: :string, updated_at: :naive_datetime, value: :integer}}}, ] end @@ -74,11 +77,15 @@ defmodule GenMigrationTest do def change(GenMigration.Player) do rename table("player"), to: table("player2") alter table("player2") do + add(:_gen_migration_dummy, :integer, []) remove(:inserted_at) remove(:name) - add(:name2, :string, []) remove(:updated_at) remove(:value) + end + alter table("player2") do + remove(:_gen_migration_dummy) + add(:name2, :string, []) add(:value, :string, []) end end @@ -89,7 +96,7 @@ defmodule GenMigrationTest do def __migration_structures__() do [ - {GenMigration.Player, %Yacto.Migration.Structure{field_sources: %{id: :id, name2: :name2, value: :value}, fields: [:id, :name2, :value], source: "player2", types: %{id: :id, name2: :string, value: :string}}}, + {GenMigration.Player, %Yacto.Migration.Structure{autogenerate_id: {:id, :id, :id}, field_sources: %{id: :id, name2: :name2, value: :value}, fields: [:id, :name2, :value], primary_key: [:id], source: "player2", types: %{id: :id, name2: :string, value: :string}}}, ] end @@ -110,7 +117,11 @@ defmodule GenMigrationTest do def change(GenMigration.Player) do rename table("player2"), to: table("genmigration_player3") alter table("genmigration_player3") do + add(:_gen_migration_dummy, :integer, []) remove(:name2) + end + alter table("genmigration_player3") do + remove(:_gen_migration_dummy) add(:name3, :string, [null: false, size: 100]) end create index("genmigration_player3", [:name3, :value], [name: "name3_value_index", unique: true]) @@ -123,7 +134,7 @@ defmodule GenMigrationTest do def __migration_structures__() do [ - {GenMigration.Player, %Yacto.Migration.Structure{field_sources: %{id: :id, name3: :name3, value: :value}, fields: [:id, :name3, :value], meta: %{attrs: %{name3: %{null: false, size: 100}}, indices: %{{[:name3, :value], [unique: true]} => true, {[:value, :name3], []} => true}}, source: "genmigration_player3", types: %{id: :id, name3: :string, value: :string}}}, + {GenMigration.Player, %Yacto.Migration.Structure{autogenerate_id: {:id, :id, :id}, field_sources: %{id: :id, name3: :name3, value: :value}, fields: [:id, :name3, :value], meta: %{attrs: %{name3: %{null: false, size: 100}}, indices: %{{[:name3, :value], [unique: true]} => true, {[:value, :name3], []} => true}}, primary_key: [:id], source: "genmigration_player3", types: %{id: :id, name3: :string, value: :string}}}, ] end @@ -186,6 +197,11 @@ defmodule GenMigrationTest do %Yacto.Migration.Structure{}} ] + v5 = [ + {GenMigration.Player, Yacto.Migration.Structure.from_schema(GenMigration.Player3), + Yacto.Migration.Structure.from_schema(GenMigration.Player4)} + ] + source = Yacto.Migration.GenMigration.generate_source(GenMigration, v1, 20_170_424_155_528, nil) @@ -211,15 +227,23 @@ defmodule GenMigrationTest do assert @migrate3 == source - source = - Yacto.Migration.GenMigration.generate_source( - GenMigration, - v4, - 20_170_424_155_533, - 20_170_424_155_532 - ) + source = Yacto.Migration.GenMigration.generate_source( + GenMigration, + v4, + 20_170_424_155_533, + 20_170_424_155_532 + ) assert @migrate4 == source + + # source = Yacto.Migration.GenMigration.generate_source( + # GenMigration, + # v5, + # 20_170_424_155_534, + # 20_170_424_155_553 + # ) + + # assert @migrate4 == source end @migrate5 """ @@ -227,14 +251,16 @@ defmodule GenMigrationTest do use Ecto.Migration def change(GenMigration.Item) do - create table("genmigration_item") + create table("genmigration_item", primary_key: false) do + add(:id, :id) + end alter table("genmigration_item") do add(:_gen_migration_dummy, :integer, []) remove(:id) end alter table("genmigration_item") do remove(:_gen_migration_dummy) - add(:id, :binary_id, [primary_key: true, autogenerate: true]) + add(:id, :binary_id, [primary_key: true]) add(:name, :string, []) end end @@ -245,7 +271,7 @@ defmodule GenMigrationTest do def __migration_structures__() do [ - {GenMigration.Item, %Yacto.Migration.Structure{autogenerate_id: {:id, :id, :binary_id}, field_sources: %{id: :id, name: :name}, fields: [:id, :name], source: "genmigration_item", types: %{id: :binary_id, name: :string}}}, + {GenMigration.Item, %Yacto.Migration.Structure{autogenerate_id: {:id, :id, :binary_id}, field_sources: %{id: :id, name: :name}, fields: [:id, :name], primary_key: [:id], source: "genmigration_item", types: %{id: :binary_id, name: :string}}}, ] end @@ -276,12 +302,15 @@ defmodule GenMigrationTest do use Ecto.Migration def change(GenMigration.ManyIndex) do - create table("genmigration_manyindex") + create table("genmigration_manyindex", primary_key: false) do + add(:id, :id) + end alter table("genmigration_manyindex") do add(:aaaaaa, :string, []) add(:bbbbbb, :string, []) add(:cccccc, :string, []) add(:dddddd, :string, []) + modify(:id, :bigserial, [primary_key: true]) end create index("genmigration_manyindex", [:aaaaaa, :bbbbbb, :cccccc, :dddddd], [name: "aaaaaa_bbbb_9a4e1a2f"]) end @@ -292,7 +321,7 @@ defmodule GenMigrationTest do def __migration_structures__() do [ - {GenMigration.ManyIndex, %Yacto.Migration.Structure{field_sources: %{aaaaaa: :aaaaaa, bbbbbb: :bbbbbb, cccccc: :cccccc, dddddd: :dddddd, id: :id}, fields: [:id, :aaaaaa, :bbbbbb, :cccccc, :dddddd], meta: %{attrs: %{}, indices: %{{[:aaaaaa, :bbbbbb, :cccccc, :dddddd], []} => true}}, source: "genmigration_manyindex", types: %{aaaaaa: :string, bbbbbb: :string, cccccc: :string, dddddd: :string, id: :id}}}, + {GenMigration.ManyIndex, %Yacto.Migration.Structure{autogenerate_id: {:id, :id, :id}, field_sources: %{aaaaaa: :aaaaaa, bbbbbb: :bbbbbb, cccccc: :cccccc, dddddd: :dddddd, id: :id}, fields: [:id, :aaaaaa, :bbbbbb, :cccccc, :dddddd], meta: %{attrs: %{}, indices: %{{[:aaaaaa, :bbbbbb, :cccccc, :dddddd], []} => true}}, primary_key: [:id], source: "genmigration_manyindex", types: %{aaaaaa: :string, bbbbbb: :string, cccccc: :string, dddddd: :string, id: :id}}}, ] end diff --git a/test/apps/gen_migration/test/mix_test.exs b/test/apps/gen_migration/test/mix_test.exs index 3d2b732..30dd393 100644 --- a/test/apps/gen_migration/test/mix_test.exs +++ b/test/apps/gen_migration/test/mix_test.exs @@ -21,6 +21,8 @@ defmodule Mix.Tasks.Yacto.GenMigrationTest do Yacto.Migration.Structure.from_schema(GenMigration.Player2)}, {GenMigration.Player3, %Yacto.Migration.Structure{}, Yacto.Migration.Structure.from_schema(GenMigration.Player3)}, + {GenMigration.Player4, %Yacto.Migration.Structure{}, + Yacto.Migration.Structure.from_schema(GenMigration.Player4)}, {GenMigration.Item, %Yacto.Migration.Structure{}, Yacto.Migration.Structure.from_schema(GenMigration.Item)}, {GenMigration.Coin, %Yacto.Migration.Structure{}, diff --git a/test/apps/migrator/lib/migrator.ex b/test/apps/migrator/lib/migrator.ex index 7f6482e..12eb12e 100644 --- a/test/apps/migrator/lib/migrator.ex +++ b/test/apps/migrator/lib/migrator.ex @@ -75,6 +75,30 @@ defmodule Migrator.CustomPrimaryKey do end end +defmodule Migrator.CompositePrimaryKey do + use Yacto.Schema, dbname: :default + + @primary_key false + + schema @auto_source do + field(:player_id, :string, primary_key: true) + field(:guild_id, :string, primary_key: true) + field(:name, :string) + end +end + +defmodule Migrator.NoPrimaryKey do + use Yacto.Schema, dbname: :default + + @primary_key false + + schema @auto_source do + field(:player_id, :string) + field(:guild_id, :string) + field(:name, :string) + end +end + defmodule Migrator.CoinType do @behaviour Ecto.Type @@ -118,3 +142,57 @@ end defmodule Migrator.Repo1 do use Ecto.Repo, otp_app: :migrator, adapter: Ecto.Adapters.MySQL end + +defmodule Migrator.AutogeneratePrimaryKey do + use Yacto.Schema + + @impl Yacto.Schema + def dbname(), do: :default + + @primary_key false + + schema @auto_source do + field(:player_id, :integer, primary_key: true, autogenerated: true) + end +end + +defmodule Migrator.DeleteAutogeneratePrimaryKey do + use Yacto.Schema + + @impl Yacto.Schema + def dbname(), do: :default + + @primary_key false + + schema @auto_source do + field(:player_id, :integer) + end +end + +defmodule Migrator.AutogeneratePrimaryKey2 do + use Yacto.Schema + + @impl Yacto.Schema + def dbname(), do: :default + + @primary_key false + + schema @auto_source do + field(:player_id, :id, primary_key: true, autogenerate: true) + field(:unit_id, :id, primary_key: true) + end +end + +defmodule Migrator.DeleteAutogeneratePrimaryKey2 do + use Yacto.Schema + + @impl Yacto.Schema + def dbname(), do: :default + + @primary_key false + + schema @auto_source do + field(:player_id, :id, primary_key: true) + field(:unit_id, :id, primary_key: true) + end +end diff --git a/test/apps/migrator/test/migrator_test.exs b/test/apps/migrator/test/migrator_test.exs index c34a652..32dc378 100644 --- a/test/apps/migrator/test/migrator_test.exs +++ b/test/apps/migrator/test/migrator_test.exs @@ -203,4 +203,155 @@ defmodule MigratorTest do record = Migrator.Repo1.insert!(record) assert record.type == :common_coin end + + test "Migrator.CompositePrimaryKey" do + Mix.Task.rerun("ecto.drop") + Mix.Task.rerun("ecto.create") + + _ = File.rm_rf(Yacto.Migration.Util.get_migration_dir(:migrator)) + _ = File.rm_rf(Yacto.Migration.Util.get_migration_dir_for_gen()) + + Mix.Task.rerun("yacto.gen.migration", []) + Mix.Task.rerun("yacto.migrate", ["--repo", "Migrator.Repo1", "--app", "migrator"]) + + record1 = %Migrator.CompositePrimaryKey{player_id: "tanaka", guild_id: "gumi", name: "1234"} + record1 = Migrator.Repo1.insert!(record1) + assert "tanaka" == record1.player_id + + record2 = %Migrator.CompositePrimaryKey{player_id: "saito", guild_id: "gumi", name: "1235"} + record2 = Migrator.Repo1.insert!(record2) + assert "saito" == record2.player_id + + # 複合主キーが同じデータを追加しようとすると Ecto.ConstraintError が発生する。 + record3 = %Migrator.CompositePrimaryKey{player_id: "saito", guild_id: "gumi", name: "12345"} + assert_raise Ecto.ConstraintError, fn -> Migrator.Repo1.insert(record3) end + end + + test "Migrator.NoPrimaryKey" do + Mix.Task.rerun("ecto.drop") + Mix.Task.rerun("ecto.create") + + {:ok, _} = Migrator.Repo1.start_link() + + v1 = [ + {Migrator.NoPrimaryKey, %Yacto.Migration.Structure{}, + Yacto.Migration.Structure.from_schema(Migrator.NoPrimaryKey)} + ] + + v2 = [ + {Migrator.NoPrimaryKey, Yacto.Migration.Structure.from_schema(Migrator.NoPrimaryKey), + Yacto.Migration.Structure.from_schema(Migrator.CompositePrimaryKey)} + ] + + source = Yacto.Migration.GenMigration.generate_source(Migrator, v1, 20_190_424_162_530, nil) + File.write!("migration_test_1.exs", source) + + source = + Yacto.Migration.GenMigration.generate_source( + Migrator, + v2, + 20_190_424_162_533, + 20_190_424_162_530 + ) + + File.write!("migration_test_2.exs", source) + + try do + migrations = + Yacto.Migration.Util.load_migrations(["migration_test_1.exs", "migration_test_2.exs"]) + + schemas = Yacto.Migration.Util.get_all_schema(:migrator) + :ok = Yacto.Migration.Migrator.up(:migrator, Migrator.Repo1, schemas, migrations) + after + File.rm!("migration_test_1.exs") + File.rm!("migration_test_2.exs") + Code.unload_files(["migration_test_1.exs", "migration_test_2.exs"]) + end + + record1 = %Migrator.CompositePrimaryKey{player_id: "tanaka", guild_id: "gumi", name: "1234"} + record1 = Migrator.Repo1.insert!(record1) + assert "tanaka" == record1.player_id + + record2 = %Migrator.CompositePrimaryKey{player_id: "saito", guild_id: "gumi", name: "1235"} + record2 = Migrator.Repo1.insert!(record2) + assert "saito" == record2.player_id + + # 複合主キーが同じデータを追加しようとすると Ecto.ConstraintError が発生する。 + record3 = %Migrator.CompositePrimaryKey{player_id: "saito", guild_id: "gumi", name: "12345"} + assert_raise Ecto.ConstraintError, fn -> IO.inspect(Migrator.Repo1.insert!(record3)) end + end + + test "プライマリキーを削除することができる" do + Mix.Task.rerun("ecto.drop") + Mix.Task.rerun("ecto.create") + + {:ok, _} = Migrator.Repo1.start_link() + v1 = [ + {Migrator.AutogeneratePrimaryKey, %Yacto.Migration.Structure{}, + Yacto.Migration.Structure.from_schema(Migrator.AutogeneratePrimaryKey)} + ] + + v2 = [ + {Migrator.DeleteAutogeneratePrimaryKey, Yacto.Migration.Structure.from_schema(Migrator.AutogeneratePrimaryKey), + Yacto.Migration.Structure.from_schema(Migrator.DeleteAutogeneratePrimaryKey)} + ] + + source = Yacto.Migration.GenMigration.generate_source(Migrator, v1, 20_190_515_162_530, nil) + File.write!("migration_test_1.exs", source) + + source = Yacto.Migration.GenMigration.generate_source( Migrator, + v2, + 20_190_515_162_533, + 20_190_515_162_530 + ) + + File.write!("migration_test_2.exs", source) + + try do + migrations = + Yacto.Migration.Util.load_migrations(["migration_test_1.exs", "migration_test_2.exs"]) + + schemas = Yacto.Migration.Util.get_all_schema(:migrator) + :ok = Yacto.Migration.Migrator.up(:migrator, Migrator.Repo1, schemas, migrations) + after + File.rm!("migration_test_1.exs") + File.rm!("migration_test_2.exs") + Code.unload_files(["migration_test_1.exs", "migration_test_2.exs"]) + end + end + + test "Autogenerated な複合プライマリキーを変更することはできない" do + ExUnit.Callbacks.on_exit(fn -> + File.rm!("migration_test_1.exs") + File.rm!("migration_test_2.exs") + Code.unload_files(["migration_test_1.exs", "migration_test_2.exs"]) + end) + + Mix.Task.rerun("ecto.drop") + Mix.Task.rerun("ecto.create") + + {:ok, _} = Migrator.Repo1.start_link() + + v1 = [ + {Migrator.AutogeneratePrimaryKey2, %Yacto.Migration.Structure{}, + Yacto.Migration.Structure.from_schema(Migrator.AutogeneratePrimaryKey2)} + ] + + v2 = [ + {Migrator.DeleteAutogeneratePrimaryKey2, Yacto.Migration.Structure.from_schema(Migrator.AutogeneratePrimaryKey2), + Yacto.Migration.Structure.from_schema(Migrator.DeleteAutogeneratePrimaryKey2)} + ] + + source = Yacto.Migration.GenMigration.generate_source(Migrator, v1, 20_190_515_162_530, nil) + File.write!("migration_test_1.exs", source) + + assert_raise(Yacto.Migration.GenMigration.GenMigrationError, + ~r/AutogenerateID Primary key can not be changed/, fn -> + Yacto.Migration.GenMigration.generate_source( Migrator, + v2, + 20_190_515_162_533, + 20_190_515_162_530 + ) + end) + end end diff --git a/test/support/query.ex b/test/support/query.ex index b1201d5..5bbb27e 100644 --- a/test/support/query.ex +++ b/test/support/query.ex @@ -22,6 +22,15 @@ defmodule Yacto.QueryTest.Item do end end +defmodule Yacto.QueryTest.UniqueItem do + use Yacto.Schema.Single, dbname: :default + + schema "item" do + field(:name, :string, unique: true) + field(:quantity, :integer) + end +end + defmodule Yacto.QueryTest.Default.Migration do use Ecto.Migration diff --git a/test/yacto/composite_primary_key_test.exs b/test/yacto/composite_primary_key_test.exs new file mode 100644 index 0000000..42ea03e --- /dev/null +++ b/test/yacto/composite_primary_key_test.exs @@ -0,0 +1,44 @@ +defmodule Yacto.CompositePrimaryKeyTest do + use PowerAssert + + defmodule Parent1 do + use Ecto.Schema + + schema "parents" do + field(:name, :string) + field(:player_id, :string) + field(:foo, :string) + end + end + + defmodule Parent2 do + use Ecto.Schema + @primary_key false + + schema "parents" do + field(:name, :string) + field(:player_id, :string, primary_key: true) + field(:foo, :string, primary_key: true) + end + end + + test "composite primary key" do + # p = %Parent{name: "foo", player_id: "bar", foo: "baz"} + # IO.inspect Parent.__schema__(:primary_key) + + s1 = Yacto.Migration.Structure.from_schema(Parent1) + s2 = Yacto.Migration.Structure.from_schema(Parent2) + + Yacto.Migration.Structure.diff(s1, s2) + |> IO.inspect() + + # %{ + # autogenerate_id: {:delete, {:id, :id, :id}}, + # fields: [del: [:id], eq: [:name, :player_id, :foo]], + # meta: %{attrs: %{del: %{}, ins: %{}}, indices: %{del: %{}, ins: %{}}}, + # primary_key: [del: [:id], ins: [:player_id, :foo]], + # source: :not_changed, + # types: %{del: %{id: :id}, ins: %{}} + # } + end +end diff --git a/test/yacto/migration/structure_test.exs b/test/yacto/migration/structure_test.exs index 887abc1..2d49a10 100644 --- a/test/yacto/migration/structure_test.exs +++ b/test/yacto/migration/structure_test.exs @@ -19,10 +19,10 @@ defmodule Yacto.Migration.StructureTest do test "inspect" do structure = Yacto.Migration.Structure.from_schema(Schema) - assert "%Yacto.Migration.Structure{source: \"yacto_migration_structuretest_schema\"}" == + assert "%Yacto.Migration.Structure{autogenerate_id: {:id, :id, :id}, primary_key: [:id], source: \"yacto_migration_structuretest_schema\"}" == inspect(structure) - assert "%Yacto.Migration.Structure{source: \"yacto_migration_structuretest_schema\"}" == + assert "%Yacto.Migration.Structure{autogenerate_id: {:id, :id, :id}, primary_key: [:id], source: \"yacto_migration_structuretest_schema\"}" == Yacto.Migration.Structure.to_string(structure) end end diff --git a/test/yacto/query_test.exs b/test/yacto/query_test.exs index bc176bc..f236651 100644 --- a/test/yacto/query_test.exs +++ b/test/yacto/query_test.exs @@ -145,6 +145,20 @@ defmodule Yacto.QueryTest do test_get_by_or_new(true) end + test "Yacto.Repo.get_by_or_new with lock2" do + repo = Yacto.QueryTest.UniqueItem.repo() + repo.get_by_or_insert_for_update( + Yacto.QueryTest.Item, + [name: "foo"], + Ecto.Changeset.change(%Yacto.QueryTest.Item{name: "foo", quantity: 1000}) + ) + repo.get_by_or_insert_for_update( + Yacto.QueryTest.Item, + [name: "foo"], + Ecto.Changeset.change(%Yacto.QueryTest.Item{name: "foo", quantity: 2000}) + ) + end + test "Yacto.Repo.find" do mod = Yacto.QueryTest.Item assert length(mod.repo().find(mod, name: "foo")) == 1