Packages

Neo4j driver for Elixir, using the fast Bolt protocol

Current section

Files

Jump to
boltx lib bolt_sips protocol.ex
Raw

lib/bolt_sips/protocol.ex

defmodule Bolt.Sips.Protocol do
@moduledoc false
# Implements callbacks required by DBConnection.
# Each callback receives an open connection as a state.
defmodule ConnData do
@moduledoc false
# Defines the state used by DbConnection implementation
defstruct [:sock, :bolt_version, :configuration]
@type t :: %__MODULE__{
sock: port(),
bolt_version: integer(),
configuration: Keyword.t()
}
end
use DBConnection
require Logger
alias Bolt.Sips.QueryStatement
alias Bolt.Sips.Internals.Error, as: BoltError
alias Bolt.Sips.Internals.BoltProtocol
@doc "Callback for DBConnection.connect/1"
def connect(opts \\ [])
def connect([]), do: connect(Bolt.Sips.Utils.default_config())
def connect(opts) do
conf = opts |> Bolt.Sips.Utils.default_config()
host = _to_hostname(conf[:hostname])
port = conf[:port]
auth = extract_auth(conf[:basic_auth])
timeout = conf[:timeout]
socket = conf[:socket]
default_socket_options = [packet: :raw, mode: :binary, active: false]
socket_opts =
case conf[:ssl] do
list when is_list(list) -> Keyword.merge(default_socket_options, conf[:ssl])
_ -> default_socket_options
end
with {:ok, sock} <- socket.connect(host, port, socket_opts, timeout),
{:ok, bolt_version} <- BoltProtocol.handshake(socket, sock),
{:ok, server_version} <- do_init(socket, sock, bolt_version, auth),
:ok <- socket.setopts(sock, active: :once) do
{:ok,
%ConnData{
sock: sock,
bolt_version: bolt_version,
configuration: Keyword.merge(conf, server_version: server_version)
}}
else
{:error, %BoltError{}} = error ->
error
{:error, reason} ->
{:error, BoltError.exception(reason, nil, :connect)}
end
end
defp do_init(transport, port, 3, auth) do
BoltProtocol.hello(transport, port, 3, auth)
end
defp do_init(transport, port, bolt_version, auth) do
BoltProtocol.init(transport, port, bolt_version, auth)
end
@doc "Callback for DBConnection.checkout/1"
def checkout(%ConnData{sock: sock, configuration: conf} = conn_data) do
case conf[:socket].setopts(sock, active: false) do
:ok -> {:ok, conn_data}
other -> other
end
end
@doc "Callback for DBConnection.checkin/1"
def checkin(%ConnData{sock: sock, configuration: conf} = conn_data) do
case conf[:socket].setopts(sock, active: :once) do
:ok -> {:ok, conn_data}
other -> other
end
end
def disconnect(_err, %ConnData{sock: sock, bolt_version: 3, configuration: conf} = conn_data) do
socket = conf[:socket]
:ok = BoltProtocol.goodbye(socket, sock, conn_data.bolt_version)
socket.close(sock)
:ok
end
@doc "Callback for DBConnection.disconnect/1"
def disconnect(_err, %ConnData{sock: sock, configuration: conf}) do
conf[:socket].close(sock)
:ok
end
@doc "Callback for DBConnection.handle_begin/1"
def handle_begin(_, %ConnData{sock: sock, bolt_version: 3, configuration: conf} = conn_data) do
{:ok, _} = BoltProtocol.begin(conf[:socket], sock, conn_data.bolt_version)
{:ok, :began, conn_data}
end
def handle_begin(_opts, conn_data) do
%QueryStatement{statement: "BEGIN"}
|> handle_execute(%{}, [], conn_data)
{:ok, :began, conn_data}
end
@doc "Callback for DBConnection.handle_rollback/1"
def handle_rollback(_, %ConnData{sock: sock, bolt_version: 3, configuration: conf} = conn_data) do
:ok = BoltProtocol.rollback(conf[:socket], sock, conn_data.bolt_version)
{:ok, :rolledback, conn_data}
end
def handle_rollback(_opts, conn_data) do
%QueryStatement{statement: "ROLLBACK"}
|> handle_execute(%{}, [], conn_data)
{:ok, :rolledback, conn_data}
end
@doc "Callback for DBConnection.handle_commit/1"
def handle_commit(_, %ConnData{sock: sock, bolt_version: 3, configuration: conf} = conn_data) do
{:ok, _} = BoltProtocol.commit(conf[:socket], sock, conn_data.bolt_version)
{:ok, :committed, conn_data}
end
def handle_commit(_opts, conn_data) do
%QueryStatement{statement: "COMMIT"}
|> handle_execute(%{}, [], conn_data)
{:ok, :committed, conn_data}
end
@doc "Callback for DBConnection.handle_execute/1"
def handle_execute(query, params, opts, conn_data) do
execute(query, params, opts, conn_data)
end
def handle_info(msg, state) do
Logger.error(fn ->
[inspect(__MODULE__), ?\s, inspect(self()), " received unexpected message: " | inspect(msg)]
end)
{:ok, state}
end
### Calming the warnings
# Callbacks for ...
def ping(state), do: {:ok, state}
def handle_prepare(query, _opts, state), do: {:ok, query, state}
def handle_close(query, _opts, state), do: {:ok, query, state}
def handle_deallocate(query, _cursor, _opts, state), do: {:ok, query, state}
def handle_declare(query, _params, _opts, state), do: {:ok, query, state, nil}
def handle_fetch(query, _cursor, _opts, state), do: {:cont, query, state}
def handle_status(_opts, state), do: {:idle, state}
defp extract_auth(nil), do: {}
defp extract_auth(basic_auth), do: {basic_auth[:username], basic_auth[:password]}
defp execute(q, params, _, conn_data) do
%QueryStatement{statement: statement} = q
%ConnData{sock: sock, bolt_version: bolt_version, configuration: conf} = conn_data
socket = conf |> Keyword.get(:socket)
case BoltProtocol.run_statement(socket, sock, bolt_version, statement, params) do
[{:success, _} | _] = data ->
{:ok, q, data, conn_data}
%BoltError{type: :cypher_error} = error ->
BoltProtocol.reset(socket, sock, bolt_version)
{:error, error, conn_data}
%BoltError{type: :connection_error} = error ->
{:disconnect, error, conn_data}
%BoltError{} = error ->
{:error, error, conn_data}
end
rescue
e ->
msg =
case e do
%Bolt.Sips.Internals.PackStreamError{} ->
"unable to encode value: #{inspect(e.data)}"
%BoltError{} ->
"#{e.message}, type: #{e.type}"
_ ->
e.message
end
{:error, %{code: :failure, message: msg}, conn_data}
end
defp _to_hostname(hostname) when is_binary(hostname), do: String.to_charlist(hostname)
defp _to_hostname(hostname) when is_list(hostname), do: hostname
defp _to_hostname(hostname), do: hostname
end