Skip to content
Merged
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
10 changes: 8 additions & 2 deletions docs/decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,13 +157,19 @@ Provisional because none of it is built yet.

### 12. Rename `Mysqlex.Protocol` to `Mysqlex.Connection`

**2026-10-20 · open**
**2026-10-20 · settled 2026-10-29**

Today `Mysqlex.Protocol` holds the DBConnection callbacks, but every other driver
uses "protocol" for the codec layer. Keeping both meanings will be confusing once
`protocol/` exists as a directory of codecs.

Not done yet. Cheap now at ~130 lines, expensive later.
Done once `Mysqlex.Client` took over the connection phase, which is also when
the 2018 handshake parsing in that module became dead code and was deleted.
`Mysqlex.Connection` is now a thin `DBConnection` wrapper whose state is a
`%Mysqlex.Client{}`, which is what keeps decision 15's swap cheap.

`Mysqlex.Error` became an exception at the same time, because `DBConnection`
specifies its callbacks as returning `Exception.t()`.

### 13. Authentication order: `mysql_native_password` before `caching_sha2_password`

Expand Down
4 changes: 2 additions & 2 deletions lib/mysqlex.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ defmodule Mysqlex do
MySQL native driver for Elixir.
"""

alias Mysqlex.Protocol
alias Mysqlex.Connection

@doc """
Start the connection process and connect to the MySQL server.
Expand All @@ -25,6 +25,6 @@ defmodule Mysqlex do
port: port
]

DBConnection.start_link(Protocol, opts)
DBConnection.start_link(Connection, opts)
end
end
79 changes: 79 additions & 0 deletions lib/mysqlex/connection.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
defmodule Mysqlex.Connection do
@moduledoc """
`DBConnection` callbacks over `Mysqlex.Client`.

Thin on purpose. The protocol lives in `Mysqlex.Client` and knows nothing about
`DBConnection`, so this layer can be replaced — or dropped for a hand-written
pool — without touching anything below it. See decision 15 in
`docs/decisions.md`.

The command phase callbacks still return an error; `COM_QUERY` is next.
"""

use DBConnection

alias Mysqlex.{Client, Error}

@impl true
def connect(opts) do
case Client.connect(opts) do
{:ok, %Client{} = client} ->
{:ok, client}

{:error, %Error{} = error} ->
{:error, error}

# socket failures and unsupported auth methods are not server errors, but
# DBConnection's contract is an exception either way
{:error, reason} ->
{:error, Error.exception(message: "could not connect: #{inspect(reason)}")}
end
end

@impl true
def disconnect(_reason, %Client{transport: transport, socket: socket}) do
transport.close(socket)

:ok
end

@impl true
def checkout(state), do: {:ok, state}

@impl true
def ping(state), do: {:ok, state}

@impl true
def handle_begin(_opts, state), do: {:error, state}

@impl true
def handle_commit(_opts, state), do: {:error, state}

@impl true
def handle_rollback(_opts, state), do: {:error, state}

@impl true
def handle_status(_opts, state), do: {:idle, state}

@impl true
def handle_prepare(_query, _opts, state), do: {:error, not_implemented(), state}

@impl true
def handle_execute(_query, _params, _opts, state), do: {:error, not_implemented(), state}

@impl true
def handle_close(_query, _opts, state), do: {:error, not_implemented(), state}

@impl true
def handle_declare(_query, _params, _opts, state), do: {:error, not_implemented(), state}

@impl true
def handle_fetch(_query, _cursor, _opts, state), do: {:error, not_implemented(), state}

@impl true
def handle_deallocate(_query, _cursor, _opts, state), do: {:error, not_implemented(), state}

defp not_implemented do
DBConnection.ConnectionError.exception("Mysqlex does not implement the command phase yet")
end
end
5 changes: 4 additions & 1 deletion lib/mysqlex/error.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ defmodule Mysqlex.Error do
`code` is MySQL's own error number and is precise; `sql_state` is the ANSI SQL
five-character code and is portable across databases. `sql_state` is `nil` when
the connection did not negotiate `CLIENT_PROTOCOL_41`.

An exception because `DBConnection` specifies its callbacks as returning
`Exception.t()`, and because callers will want to raise it.
"""

defstruct [:code, :sql_state, :message]
defexception [:code, :sql_state, :message]
end
139 changes: 0 additions & 139 deletions lib/mysqlex/protocol.ex

This file was deleted.

12 changes: 12 additions & 0 deletions test/integration/login_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,18 @@ defmodule Mysqlex.LoginIntegrationTest do
assert error.message =~ "Access denied"
end

test "connects through DBConnection" do
assert {:ok, pid} = DBConnection.start_link(Mysqlex.Connection, @opts)

on_exit(fn ->
# a DBConnection pool shuts down rather than stopping normally
Process.unlink(pid)
Process.exit(pid, :shutdown)
end)

assert Process.alive?(pid)
end

test "a database the account cannot reach is refused after authentication" do
assert {:error, %Error{} = error} = Client.connect(Keyword.put(@opts, :database, "nope"))

Expand Down
Loading