Skip to content
Open
Show file tree
Hide file tree
Changes from 8 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
3 changes: 3 additions & 0 deletions lib/myxql.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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:

Expand Down
42 changes: 39 additions & 3 deletions lib/myxql/client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions lib/myxql/protocol.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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)

if config.ssl_opts && !has_capability_flag?(server_capability_flags, :client_ssl) do
{:error, :server_does_not_support_ssl}
Expand Down Expand Up @@ -331,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, ""} = 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
Expand Down Expand Up @@ -513,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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Base on the changes to take_string_nul, it seems you are not expecting a nul here? So maybe we should not call/change said function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, we shouldn't change the function!

{:local_infile, filename}
end

defp decode_resultset(payload, _next_data, :initial, _row_decoder) do
{:cont, {:column_defs, decode_int_lenenc(payload), []}}
end
Expand Down
15 changes: 11 additions & 4 deletions lib/myxql/protocol/types.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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(<<int::uint1(), rest::binary>>) 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
Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this change still necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I tested it without the changes to the method, an exception occurred when using the client. So its necessary

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But why? We said the filename is always followed by a nul byte, right? That will always return a two element list:

iex(2)> :binary.split(<<"/", 0>>, [<<0>>])
["/", ""]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I change it I got this error:

stopped: ** (MatchError) no match of right hand side value: ["/Users/iagocavalcante/Workspaces/i9Amazon/i9_processador/25332012000225-pdv_caixa_lancamento-20250618_210733-T-110.txt"]
    (myxql 0.8.0-dev) lib/myxql/protocol/types.ex:74: MyXQL.Protocol.Types.take_string_nul/1

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What should I do to have this merged?

case :binary.split(binary, <<0>>) do
[string] ->
{string, ""}

[string, rest] ->
{string, rest}
end
end
end