Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't let mysqldump specify prefix #540

Closed
Closed
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
48 changes: 1 addition & 47 deletions integration_test/myxql/storage_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -150,53 +150,7 @@ defmodule Ecto.Integration.StorageTest do
:ok = Ecto.Migrator.up(PoolRepo, num, Migration, log: false)
{:ok, path} = Ecto.Adapters.MyXQL.structure_dump(tmp_path(), TestRepo.config())
contents = File.read!(path)
assert contents =~ "INSERT INTO `ecto_test`.`schema_migrations` (version) VALUES (#{num})"
end

test "dumps structure and schema_migration records from multiple prefixes" do
# Create the test_schema schema
create_database()
prefix = params()[:database]

# Run migrations
version = @base_migration + System.unique_integer([:positive])
:ok = Ecto.Migrator.up(PoolRepo, version, Migration, log: false)
:ok = Ecto.Migrator.up(PoolRepo, version, Migration, log: false, prefix: prefix)

config = Keyword.put(TestRepo.config(), :dump_prefixes, ["ecto_test", prefix])
{:ok, path} = Ecto.Adapters.MyXQL.structure_dump(tmp_path(), config)
contents = File.read!(path)

assert contents =~ "Current Database: `#{prefix}`"
assert contents =~ "Current Database: `ecto_test`"
assert contents =~ "CREATE TABLE `schema_migrations`"
assert contents =~ ~s[INSERT INTO `#{prefix}`.`schema_migrations` (version) VALUES (#{version})]
assert contents =~ ~s[INSERT INTO `ecto_test`.`schema_migrations` (version) VALUES (#{version})]
after
drop_database()
end

test "dumps structure and schema_migration records only from queried prefix" do
# Create the test_schema schema
create_database()
prefix = params()[:database]

# Run migrations
version = @base_migration + System.unique_integer([:positive])
:ok = Ecto.Migrator.up(PoolRepo, version, Migration, log: false)
:ok = Ecto.Migrator.up(PoolRepo, version, Migration, log: false, prefix: prefix)

config = Keyword.put(TestRepo.config(), :dump_prefixes, ["ecto_test"])
{:ok, path} = Ecto.Adapters.MyXQL.structure_dump(tmp_path(), config)
contents = File.read!(path)

refute contents =~ "Current Database: `#{prefix}`"
assert contents =~ "Current Database: `ecto_test`"
assert contents =~ "CREATE TABLE `schema_migrations`"
refute contents =~ ~s[INSERT INTO `#{prefix}`.`schema_migrations` (version) VALUES (#{version})]
assert contents =~ ~s[INSERT INTO `ecto_test`.`schema_migrations` (version) VALUES (#{version})]
after
drop_database()
assert contents =~ "INSERT INTO `schema_migrations` (version) VALUES (#{num})"
end

defp strip_timestamp(dump) do
Expand Down
40 changes: 12 additions & 28 deletions lib/ecto/adapters/myxql.ex
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,6 @@ defmodule Ecto.Adapters.MyXQL do
* `:charset` - the database encoding (default: "utf8mb4")
* `:collation` - the collation order
* `:dump_path` - where to place dumped structures
* `:dump_prefixes` - list of prefixes that will be included in the
structure dump. When specified, the prefixes will have their definitions
dumped along with the data in their migration table. When it is not
specified, only the configured database and its migration table are dumped.

### After connect callback

Expand Down Expand Up @@ -316,36 +312,27 @@ defmodule Ecto.Adapters.MyXQL do
def structure_dump(default, config) do
table = config[:migration_source] || "schema_migrations"
path = config[:dump_path] || Path.join(default, "structure.sql")
prefixes = config[:dump_prefixes] || [config[:database]]

with {:ok, versions} <- select_versions(prefixes, table, config),
{:ok, contents} <- mysql_dump(prefixes, config),
with {:ok, versions} <- select_versions(table, config),
{:ok, contents} <- mysql_dump(config),
{:ok, contents} <- append_versions(table, versions, contents) do
File.mkdir_p!(Path.dirname(path))
File.write!(path, contents)
{:ok, path}
end
end

defp select_versions(prefixes, table, config) do
result =
Enum.reduce_while(prefixes, [], fn prefix, versions ->
case run_query(~s[SELECT version FROM `#{prefix}`.`#{table}` ORDER BY version], config) do
{:ok, %{rows: rows}} -> {:cont, Enum.map(rows, &{prefix, hd(&1)}) ++ versions}
{:error, %{mysql: %{name: :ER_NO_SUCH_TABLE}}} -> {:cont, versions}
{:error, _} = error -> {:halt, error}
{:exit, exit} -> {:halt, {:error, exit_to_exception(exit)}}
end
end)

case result do
defp select_versions(table, config) do
case run_query(~s[SELECT version FROM `#{table}` ORDER BY version], config) do
{:ok, %{rows: rows}} -> {:ok, Enum.map(rows, &hd/1)}
{:error, %{mysql: %{name: :ER_NO_SUCH_TABLE}}} -> {:ok, []}
{:error, _} = error -> error
versions -> {:ok, versions}
{:exit, exit} -> {:error, exit_to_exception(exit)}
end
end

defp mysql_dump(prefixes, config) do
args = ["--no-data", "--routines", "--databases" | prefixes]
defp mysql_dump(config) do
args = ["--no-data", "--routines", config[:database]]

case run_with_cmd("mysqldump", config, args) do
{output, 0} -> {:ok, output}
Expand All @@ -358,12 +345,9 @@ defmodule Ecto.Adapters.MyXQL do
end

defp append_versions(table, versions, contents) do
sql_statements =
Enum.map_join(versions, fn {prefix, version} ->
~s[INSERT INTO `#{prefix}`.`#{table}` (version) VALUES (#{version});\n]
end)

{:ok, contents <> sql_statements}
{:ok,
contents <>
Enum.map_join(versions, &~s[INSERT INTO `#{table}` (version) VALUES (#{&1});\n])}
end

@impl true
Expand Down
16 changes: 7 additions & 9 deletions lib/mix/tasks/ecto.dump.ex
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,13 @@ defmodule Mix.Tasks.Ecto.Dump do
* `-q`, `--quiet` - run the command quietly
* `--no-compile` - does not compile applications before dumping
* `--no-deps-check` - does not check dependencies before dumping
* `--prefix` - prefix that will be included in the structure dump.
Can include multiple prefixes (ex. `--prefix foo --prefix bar`).
* `--prefix` - (PostgreSQL only) the prefix that will be included in
the structure dump. Can include multiple prefixes (ex. `--prefix foo --prefix bar`).
When specified, the prefixes will have their definitions dumped along
with the data in their migration table. The default behavior is
dependent on the adapter for backwards compatibility reasons.
For PostgreSQL, the configured database has the definitions dumped
from all of its schemas but only the data from the migration table
from the `public` schema is included. For MySQL, only the configured
database and its migration table are dumped.
with the data in their migration table. For backwards compatibility reasons,
the default behaviour is for the configured database to have its definitions
dumped from all of its schemas but only the data from the migration table in
the `public` schema is included.
"""

@impl true
Expand Down Expand Up @@ -97,7 +95,7 @@ defmodule Mix.Tasks.Ecto.Dump do
end
end
end

defp format_time(microsec) when microsec < 1_000, do: "#{microsec} μs"
defp format_time(microsec) when microsec < 1_000_000, do: "#{div(microsec, 1_000)} ms"
defp format_time(microsec), do: "#{Float.round(microsec / 1_000_000.0)} s"
Expand Down