Skip to content

Commit 64ce332

Browse files
use input redirection for mysql structure_load
1 parent 059a059 commit 64ce332

File tree

2 files changed

+88
-5
lines changed

2 files changed

+88
-5
lines changed

Earthfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ integration-test-mysql:
9292
# the default authentication plugin for MySQL 8 is sha 256 but it doesn't come with the docker image. falling back to the 5.7 way
9393
--default-authentication-plugin=mysql_native_password; \
9494
# wait for mysql to start
95-
while ! mysqladmin ping --host=127.0.0.1 --port=3306 --protocol=TCP --silent; do \
95+
while ! mysqladmin ping --host=127.0.0.1 --port=3306 --protocol=TCP; do \
96+
docker logs mysql; \
9697
test "$(date +%s)" -le "$timeout" || (echo "timed out waiting for mysql"; exit 1); \
9798
echo "waiting for mysql"; \
9899
sleep 1; \

lib/ecto/adapters/myxql.ex

Lines changed: 86 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -419,13 +419,15 @@ defmodule Ecto.Adapters.MyXQL do
419419
case File.read(path) do
420420
{:ok, contents} ->
421421
args = [
422-
"--execute",
423-
"SET FOREIGN_KEY_CHECKS = 0; " <> contents <> "; SET FOREIGN_KEY_CHECKS = 1",
422+
"--silent",
423+
"--batch",
424+
"--unbuffered",
425+
"--init-command=SET FOREIGN_KEY_CHECKS = 0;",
424426
"--database",
425427
config[:database]
426428
]
427429

428-
case run_with_cmd("mysql", config, args) do
430+
case run_with_port("mysql", config, args, contents) do
429431
{_output, 0} -> {:ok, path}
430432
{output, _} -> {:error, output}
431433
end
@@ -497,6 +499,12 @@ defmodule Ecto.Adapters.MyXQL do
497499
"please guarantee it is available before running ecto commands"
498500
end
499501

502+
{args, cmd_opts} = args_cmd_opts(opts, opt_args, cmd_opts)
503+
504+
System.cmd(cmd, args, cmd_opts)
505+
end
506+
507+
defp args_cmd_opts(opts, opt_args, cmd_opts) do
500508
env =
501509
if password = opts[:password] do
502510
[{"MYSQL_PWD", password}]
@@ -530,6 +538,80 @@ defmodule Ecto.Adapters.MyXQL do
530538
|> Keyword.put_new(:stderr_to_stdout, true)
531539
|> Keyword.update(:env, env, &Enum.concat(env, &1))
532540

533-
System.cmd(cmd, args, cmd_opts)
541+
{args, cmd_opts}
542+
end
543+
544+
# Ported from Elixir System.cmd implementation with the
545+
# intent of using file redirection for passing dumps
546+
# into the mysql client so that users don't run into
547+
# shell limits when files are too large
548+
defp run_with_port(cmd, opts, opt_args, contents, cmd_opts \\ []) do
549+
abs_cmd = System.find_executable(cmd)
550+
551+
unless abs_cmd do
552+
raise "could not find executable `#{cmd}` in path, " <>
553+
"please guarantee it is available before running ecto commands"
554+
end
555+
556+
abs_cmd = String.to_charlist(abs_cmd)
557+
{args, cmd_opts} = args_cmd_opts(opts, opt_args, cmd_opts)
558+
559+
port_opts = port_opts(cmd_opts, args: args)
560+
port = Port.open({:spawn_executable, abs_cmd}, port_opts)
561+
Port.command(port, contents)
562+
# Use this as a signal to close the port since we cannot
563+
# send an exit command to mysql in batch mode
564+
Port.command(port, ";SELECT '__ECTO_EOF__';\n")
565+
566+
{initial, fun} = Collectable.into("")
567+
568+
try do
569+
collect_output(port, initial, fun)
570+
catch
571+
kind, reason ->
572+
fun.(initial, :halt)
573+
:erlang.raise(kind, reason, __STACKTRACE__)
574+
else
575+
{acc, status} -> {fun.(acc, :done), status}
576+
end
577+
end
578+
579+
defp port_opts([{:stderr_to_stdout, true} | t], acc),
580+
do: port_opts(t, [:stderr_to_stdout | acc])
581+
582+
defp port_opts([{:stderr_to_stdout, _} | t], acc),
583+
do: port_opts(t, acc)
584+
585+
defp port_opts([{:env, enum} | t], acc),
586+
do: port_opts(t, [{:env, validate_env(enum)} | acc])
587+
588+
defp port_opts([], acc) do
589+
[:use_stdio, :exit_status, :binary, :hide] ++ acc
590+
end
591+
592+
defp validate_env(enum) do
593+
Enum.map(enum, fn
594+
{k, nil} ->
595+
{String.to_charlist(k), false}
596+
597+
{k, v} ->
598+
{String.to_charlist(k), String.to_charlist(v)}
599+
600+
other ->
601+
raise ArgumentError, "invalid environment key-value #{inspect(other)}"
602+
end)
603+
end
604+
605+
defp collect_output(port, acc, fun) do
606+
receive do
607+
{^port, {:data, "__ECTO_EOF__" <> _rest}} ->
608+
{acc, 0}
609+
610+
{^port, {:data, data}} ->
611+
collect_output(port, fun.(acc, {:cont, data}), fun)
612+
613+
{^port, {:exit_status, status}} ->
614+
{acc, status}
615+
end
534616
end
535617
end

0 commit comments

Comments
 (0)