From 335c16221c5d37dce082b353991f0a4c37205cdb Mon Sep 17 00:00:00 2001 From: Iago Cavalcante Date: Mon, 23 Jun 2025 17:40:41 -0300 Subject: [PATCH 01/10] adding new option to connection string --- lib/myxql.ex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/myxql.ex b/lib/myxql.ex index 0b790d8..f171c13 100644 --- a/lib/myxql.ex +++ b/lib/myxql.ex @@ -23,6 +23,7 @@ defmodule MyXQL do | {:prepare, :force_named | :named | :unnamed} | {:disconnect_on_error_codes, [atom()]} | {:enable_cleartext_plugin, boolean()} + | {:local_infile, boolean()} | DBConnection.start_option() @type option() :: DBConnection.option() @@ -103,6 +104,8 @@ defmodule MyXQL do * `:enable_cleartext_plugin` - Set to `true` to send password as cleartext (default: `false`) + * `:local_infile` - Set to `true` to enable LOCAL INFILE capability (default: `false`) + The given options are passed down to DBConnection, some of the most commonly used ones are documented below: From c903ff54cc83edb6d8f8975911d974d4e23d5116 Mon Sep 17 00:00:00 2001 From: Iago Cavalcante Date: Mon, 23 Jun 2025 19:14:23 -0300 Subject: [PATCH 02/10] adding new option to connection string --- lib/myxql/protocol.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/myxql/protocol.ex b/lib/myxql/protocol.ex index 74dd083..dd3d32b 100644 --- a/lib/myxql/protocol.ex +++ b/lib/myxql/protocol.ex @@ -180,6 +180,7 @@ defmodule MyXQL.Protocol do ]) |> maybe_put_capability_flag(:client_connect_with_db, !is_nil(config.database)) |> maybe_put_capability_flag(:client_ssl, is_list(config.ssl_opts)) + |> maybe_put_capability_flag(:client_local_files, config.local_infile == true) if config.ssl_opts && !has_capability_flag?(server_capability_flags, :client_ssl) do {:error, :server_does_not_support_ssl} From e1112587f08e66f915e6e26446fd438530a0762f Mon Sep 17 00:00:00 2001 From: Iago Cavalcante Date: Mon, 23 Jun 2025 19:17:48 -0300 Subject: [PATCH 03/10] adding new option to connection string --- lib/myxql/protocol.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/myxql/protocol.ex b/lib/myxql/protocol.ex index dd3d32b..1c933ea 100644 --- a/lib/myxql/protocol.ex +++ b/lib/myxql/protocol.ex @@ -180,7 +180,7 @@ defmodule MyXQL.Protocol do ]) |> maybe_put_capability_flag(:client_connect_with_db, !is_nil(config.database)) |> maybe_put_capability_flag(:client_ssl, is_list(config.ssl_opts)) - |> maybe_put_capability_flag(:client_local_files, config.local_infile == true) + |> maybe_put_capability_flag(:client_local_files, Map.get(config, :local_infile, false) == true) if config.ssl_opts && !has_capability_flag?(server_capability_flags, :client_ssl) do {:error, :server_does_not_support_ssl} From e3fa07b47d13de6ba9a3284feb21069939b51006 Mon Sep 17 00:00:00 2001 From: Frank Ferreira Date: Wed, 25 Jun 2025 15:10:49 -0300 Subject: [PATCH 04/10] fix (#1) --- lib/myxql/client.ex | 42 ++++++++++++++++++++++++++++++++++--- lib/myxql/protocol.ex | 13 +++++++++++- lib/myxql/protocol/types.ex | 15 +++++++++---- 3 files changed, 62 insertions(+), 8 deletions(-) diff --git a/lib/myxql/client.ex b/lib/myxql/client.ex index c2e057a..2e396c9 100644 --- a/lib/myxql/client.ex +++ b/lib/myxql/client.ex @@ -27,7 +27,8 @@ defmodule MyXQL.Client do :max_packet_size, :charset, :collation, - :enable_cleartext_plugin + :enable_cleartext_plugin, + :local_infile ] @sock_opts [mode: :binary, packet: :raw, active: false] @@ -67,7 +68,8 @@ defmodule MyXQL.Client do socket_options: (opts[:socket_options] || []) ++ @sock_opts, charset: Keyword.get(opts, :charset), collation: Keyword.get(opts, :collation), - enable_cleartext_plugin: Keyword.get(opts, :enable_cleartext_plugin, false) + enable_cleartext_plugin: Keyword.get(opts, :enable_cleartext_plugin, false), + local_infile: Keyword.get(opts, :local_infile, false) } end @@ -135,7 +137,32 @@ defmodule MyXQL.Client do def com_query(client, statement, result_state \\ :single) do with :ok <- send_com(client, {:com_query, statement}) do - recv_packets(client, &decode_com_query_response/3, :initial, result_state) + case recv_packets(client, &decode_com_query_response/3, :initial, result_state) do + {:ok, {:local_infile, filename}} -> + case handle_local_infile(client, filename) do + :ok -> + recv_packets(client, &decode_com_query_response/3, :initial, result_state) + + error -> + error + end + + other -> + other + end + end + end + + def handle_local_infile(client, filename) do + case File.read(filename) do + {:ok, content} -> + with :ok <- send_packet(client, content, 2), + :ok <- send_packet(client, <<>>, 3) do + :ok + end + + {:error, reason} -> + send_packet(client, <<>>, 2) end end @@ -265,6 +292,15 @@ defmodule MyXQL.Client do {:many, results} -> {:ok, [result | results]} end + {:local_infile, filename} -> + case handle_local_infile(client, filename) do + :ok -> + recv_packets(rest, decoder, decoder_state, result_state, timeout, client) + + {:error, reason} -> + {:error, reason} + end + {:error, _} = error -> error end diff --git a/lib/myxql/protocol.ex b/lib/myxql/protocol.ex index 1c933ea..baf609e 100644 --- a/lib/myxql/protocol.ex +++ b/lib/myxql/protocol.ex @@ -180,7 +180,7 @@ defmodule MyXQL.Protocol do ]) |> maybe_put_capability_flag(:client_connect_with_db, !is_nil(config.database)) |> maybe_put_capability_flag(:client_ssl, is_list(config.ssl_opts)) - |> maybe_put_capability_flag(:client_local_files, Map.get(config, :local_infile, false) == true) + |> maybe_put_capability_flag(:client_local_files, config.local_infile) if config.ssl_opts && !has_capability_flag?(server_capability_flags, :client_ssl) do {:error, :server_does_not_support_ssl} @@ -332,6 +332,12 @@ defmodule MyXQL.Protocol do {:halt, decode_ok_packet_body(rest)} end + def decode_com_query_response(<<0xFB, rest::binary>>, "", :initial) do + {filename, _remaining} = take_string_nul(rest) + + {:local_infile, filename} + end + def decode_com_query_response(<<0xFF, rest::binary>>, "", :initial) do {:halt, decode_err_packet_body(rest)} end @@ -514,6 +520,11 @@ defmodule MyXQL.Protocol do {:cont, :initial, {:many, [resultset | results]}} end + defp decode_resultset(<<0xFB, rest::binary>>, _next_data, :initial, _row_decoder) do + {filename, ""} = take_string_nul(rest) + {:local_infile, filename} + end + defp decode_resultset(payload, _next_data, :initial, _row_decoder) do {:cont, {:column_defs, decode_int_lenenc(payload), []}} end diff --git a/lib/myxql/protocol/types.ex b/lib/myxql/protocol/types.ex index 572331d..71867c9 100644 --- a/lib/myxql/protocol/types.ex +++ b/lib/myxql/protocol/types.ex @@ -27,14 +27,16 @@ defmodule MyXQL.Protocol.Types do def encode_int_lenenc(int) when int < 0xFFFFFFFFFFFFFFFF, do: <<0xFE, int::uint8()>> def decode_int_lenenc(binary) do - {integer, ""} = take_int_lenenc(binary) + {integer, _rest} = take_int_lenenc(binary) integer end def take_int_lenenc(<>) when int < 251, do: {int, rest} + def take_int_lenenc(<<0xFB, rest::binary>>), do: {nil, rest} def take_int_lenenc(<<0xFC, int::uint2(), rest::binary>>), do: {int, rest} def take_int_lenenc(<<0xFD, int::uint3(), rest::binary>>), do: {int, rest} def take_int_lenenc(<<0xFE, int::uint8(), rest::binary>>), do: {int, rest} + def take_int_lenenc(<<0xFF, rest::binary>>), do: {:error, rest} # https://dev.mysql.com/doc/internals/en/string.html#packet-Protocol::FixedLengthString defmacro string(size) do @@ -68,8 +70,13 @@ defmodule MyXQL.Protocol.Types do def take_string_nul(""), do: {nil, ""} - def take_string_nul(binary) do - [string, rest] = :binary.split(binary, <<0>>) - {string, rest} + def take_string_nul(binary) when is_binary(binary) do + case :binary.split(binary, <<0>>) do + [string] -> + {string, ""} + + [string, rest] -> + {string, rest} + end end end From c5be00e4d69c682b47b29c24c7ed3832b0e84f2b Mon Sep 17 00:00:00 2001 From: Iago Cavalcante Date: Wed, 25 Jun 2025 16:25:28 -0300 Subject: [PATCH 05/10] revert changes into --- lib/myxql/client.ex | 2 +- lib/myxql/protocol/types.ex | 9 ++------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/lib/myxql/client.ex b/lib/myxql/client.ex index 2e396c9..0aa569c 100644 --- a/lib/myxql/client.ex +++ b/lib/myxql/client.ex @@ -161,7 +161,7 @@ defmodule MyXQL.Client do :ok end - {:error, reason} -> + {:error, _reason} -> send_packet(client, <<>>, 2) end end diff --git a/lib/myxql/protocol/types.ex b/lib/myxql/protocol/types.ex index 71867c9..a743f50 100644 --- a/lib/myxql/protocol/types.ex +++ b/lib/myxql/protocol/types.ex @@ -71,12 +71,7 @@ defmodule MyXQL.Protocol.Types do def take_string_nul(""), do: {nil, ""} def take_string_nul(binary) when is_binary(binary) do - case :binary.split(binary, <<0>>) do - [string] -> - {string, ""} - - [string, rest] -> - {string, rest} - end + [string, rest] = :binary.split(binary, <<0>>) + {string, rest} end end From c2d5ea5e08327bdd434e24cc6218414538a4414a Mon Sep 17 00:00:00 2001 From: Iago Cavalcante Date: Wed, 25 Jun 2025 16:26:12 -0300 Subject: [PATCH 06/10] revert changes into --- lib/myxql/protocol/types.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/myxql/protocol/types.ex b/lib/myxql/protocol/types.ex index a743f50..f40583e 100644 --- a/lib/myxql/protocol/types.ex +++ b/lib/myxql/protocol/types.ex @@ -70,7 +70,7 @@ defmodule MyXQL.Protocol.Types do def take_string_nul(""), do: {nil, ""} - def take_string_nul(binary) when is_binary(binary) do + def take_string_nul(binary) do [string, rest] = :binary.split(binary, <<0>>) {string, rest} end From 66e99ed43c82e90b34ae569e62d8a0c46eb90344 Mon Sep 17 00:00:00 2001 From: Iago Cavalcante Date: Wed, 25 Jun 2025 16:29:08 -0300 Subject: [PATCH 07/10] error while change types --- lib/myxql/protocol/types.ex | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/myxql/protocol/types.ex b/lib/myxql/protocol/types.ex index f40583e..71867c9 100644 --- a/lib/myxql/protocol/types.ex +++ b/lib/myxql/protocol/types.ex @@ -70,8 +70,13 @@ defmodule MyXQL.Protocol.Types do def take_string_nul(""), do: {nil, ""} - def take_string_nul(binary) do - [string, rest] = :binary.split(binary, <<0>>) - {string, rest} + def take_string_nul(binary) when is_binary(binary) do + case :binary.split(binary, <<0>>) do + [string] -> + {string, ""} + + [string, rest] -> + {string, rest} + end end end From 31207c57c5cf4e5268a85612d894e2b7b4b387e2 Mon Sep 17 00:00:00 2001 From: Iago Cavalcante Date: Wed, 25 Jun 2025 18:20:57 -0300 Subject: [PATCH 08/10] match on empty string as well --- lib/myxql/protocol.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/myxql/protocol.ex b/lib/myxql/protocol.ex index baf609e..270c220 100644 --- a/lib/myxql/protocol.ex +++ b/lib/myxql/protocol.ex @@ -333,7 +333,7 @@ defmodule MyXQL.Protocol do end def decode_com_query_response(<<0xFB, rest::binary>>, "", :initial) do - {filename, _remaining} = take_string_nul(rest) + {filename, ""} = take_string_nul(rest) {:local_infile, filename} end From 7405353ad0f7c738a7fe2a554802bc13a7505849 Mon Sep 17 00:00:00 2001 From: Iago Cavalcante Date: Thu, 26 Jun 2025 11:28:40 -0300 Subject: [PATCH 09/10] trying revert take string nuk --- lib/myxql/protocol/types.ex | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/lib/myxql/protocol/types.ex b/lib/myxql/protocol/types.ex index 71867c9..f40583e 100644 --- a/lib/myxql/protocol/types.ex +++ b/lib/myxql/protocol/types.ex @@ -70,13 +70,8 @@ defmodule MyXQL.Protocol.Types do def take_string_nul(""), do: {nil, ""} - def take_string_nul(binary) when is_binary(binary) do - case :binary.split(binary, <<0>>) do - [string] -> - {string, ""} - - [string, rest] -> - {string, rest} - end + def take_string_nul(binary) do + [string, rest] = :binary.split(binary, <<0>>) + {string, rest} end end From e65d1021ea871d8c374f7b50b22e8485f5d4652c Mon Sep 17 00:00:00 2001 From: Iago Cavalcante Date: Thu, 26 Jun 2025 11:48:35 -0300 Subject: [PATCH 10/10] change to process correctly --- lib/myxql/protocol/types.ex | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/myxql/protocol/types.ex b/lib/myxql/protocol/types.ex index f40583e..71867c9 100644 --- a/lib/myxql/protocol/types.ex +++ b/lib/myxql/protocol/types.ex @@ -70,8 +70,13 @@ defmodule MyXQL.Protocol.Types do def take_string_nul(""), do: {nil, ""} - def take_string_nul(binary) do - [string, rest] = :binary.split(binary, <<0>>) - {string, rest} + def take_string_nul(binary) when is_binary(binary) do + case :binary.split(binary, <<0>>) do + [string] -> + {string, ""} + + [string, rest] -> + {string, rest} + end end end