Skip to content

Commit b8f5e08

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

File tree

2 files changed

+82
-4
lines changed

2 files changed

+82
-4
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ jobs:
8383
mysql:
8484
- "5.7"
8585
- "8.0"
86+
- "9.4"
8687
steps:
8788
- uses: earthly/actions-setup@v1
8889
- uses: actions/checkout@v3

lib/ecto/adapters/myxql.ex

Lines changed: 81 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -419,13 +419,13 @@ 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+
"--init-command",
423+
"SET FOREIGN_KEY_CHECKS = 0;",
424424
"--database",
425425
config[:database]
426426
]
427427

428-
case run_with_cmd("mysql", config, args) do
428+
case run_with_port("mysql", config, args, contents) do
429429
{_output, 0} -> {:ok, path}
430430
{output, _} -> {:error, output}
431431
end
@@ -497,6 +497,12 @@ defmodule Ecto.Adapters.MyXQL do
497497
"please guarantee it is available before running ecto commands"
498498
end
499499

500+
{args, cmd_opts} = args_cmd_opts(opts, opt_args, cmd_opts)
501+
502+
System.cmd(cmd, args, cmd_opts)
503+
end
504+
505+
defp args_cmd_opts(opts, opt_args, cmd_opts) do
500506
env =
501507
if password = opts[:password] do
502508
[{"MYSQL_PWD", password}]
@@ -530,6 +536,77 @@ defmodule Ecto.Adapters.MyXQL do
530536
|> Keyword.put_new(:stderr_to_stdout, true)
531537
|> Keyword.update(:env, env, &Enum.concat(env, &1))
532538

533-
System.cmd(cmd, args, cmd_opts)
539+
{args, cmd_opts}
540+
end
541+
542+
// Largely ported from Elixir System.cmd implementation
543+
// with the intent of using file redirection for passing
544+
// dump files into the mysql client so that users don't
545+
// run into shell limits when dump files are large
546+
defp run_with_port(cmd, opts, opt_args, contents, cmd_opts \\ []) do
547+
unless System.find_executable(cmd) do
548+
raise "could not find executable `#{cmd}` in path, " <>
549+
"please guarantee it is available before running ecto commands"
550+
end
551+
552+
{args, cmd_opts} = args_cmd_opts(opts, opt_args, cmd_opts)
553+
554+
cmd =
555+
if Path.type(cmd) == :absolute do
556+
cmd
557+
else
558+
:os.find_executable(cmd) || :erlang.error(:enoent, [command, args, cmd_opts])
559+
end
560+
561+
port_opts = port_opts(cmd_opts, [args: args])
562+
port = Port.open({:spawn_executable, cmd}, port_opts)
563+
Port.command(port, content)
564+
565+
try do
566+
{initial, fun} = Collectable.into("")
567+
do_port_byte(port, initial, fun)
568+
catch
569+
kind, reason ->
570+
fun.(initial, :halt)
571+
:erlang.raise(kind, reason, __STACKTRACE__)
572+
else
573+
{acc, status} -> {fun.(acc, :done), status}
574+
end
575+
end
576+
577+
defp port_opts([{:stderr_to_stdout, true} | t], acc),
578+
do: port_opts(t, [:stderr_to_stdout | acc])
579+
580+
defp port_opts([{:stderr_to_stdout, _} | t], acc),
581+
do: port_opts(t, acc)
582+
583+
defp port_opts([{:env, enum} | t], acc),
584+
do: port_opts(t, [{:env, validate_env(enum)} | acc])
585+
586+
defp port_opts([], acc) do
587+
[:use_stdio, :exit_status, :binary, :hide] ++ acc
588+
end
589+
590+
defp validate_env(enum) do
591+
Enum.map(enum, fn
592+
{k, nil} ->
593+
{String.to_charlist(k), false}
594+
595+
{k, v} ->
596+
{String.to_charlist(k), String.to_charlist(v)}
597+
598+
other ->
599+
raise ArgumentError, "invalid environment key-value #{inspect(other)}"
600+
end)
601+
end
602+
603+
defp do_port_byte(port, acc, fun) do
604+
receive do
605+
{^port, {:data, data}} ->
606+
do_port_byte(port, fun.(acc, {:cont, data}), fun)
607+
608+
{^port, {:exit_status, status}} ->
609+
{acc, status}
610+
end
534611
end
535612
end

0 commit comments

Comments
 (0)