ArangoDB 3.11 driver for Elixir with connection pooling, support for VelocyStream, active failover, transactions and streamed cursors.
An implementation of DBConnection
for
ArangoDB.
Supports VelocyStream, active failover, transactions and streamed cursors.
Tested on:
iex> {:ok, conn} = Arangox.start_link(pool_size: 10)
iex> {:ok, %Arangox.Response{status: 200, body: %{"code" => 200, "error" => false, "mode" => "default"}}} = Arangox.get(conn, "/_admin/server/availability")
iex> {:error, %Arangox.Error{status: 404}} = Arangox.get(conn, "/invalid")
iex> %Arangox.Response{status: 200, body: %{"code" => 200, "error" => false, "mode" => "default"}} = Arangox.get!(conn, "/_admin/server/availability")
iex> {:ok,
iex> %Arangox.Request{
iex> body: "",
iex> headers: %{},
iex> method: :get,
iex> path: "/_admin/server/availability"
iex> },
iex> %Arangox.Response{
iex> status: 200,
iex> body: %{"code" => 200, "error" => false, "mode" => "default"}
iex> }
iex> } = Arangox.request(conn, :get, "/_admin/server/availability")
iex> Arangox.transaction(conn, fn c ->
iex> stream =
iex> Arangox.cursor(
iex> c,
iex> "FOR i IN [1, 2, 3] FILTER i == 1 || i == @num RETURN i",
iex> %{num: 2},
iex> properties: [batchSize: 1]
iex> )
iex>
iex> Enum.reduce(stream, [], fn resp, acc ->
iex> acc ++ resp.body["result"]
iex> end)
iex> end)
{:ok, [1, 2]}
By default, Arangox communicates with ArangoDB via VelocyStream, which requires the :velocy
library:
def deps do
[
...
{:arangox, "~> 0.4.0"},
{:velocy, "~> 0.1"}
]
end
The default vst chunk size is 30_720
. To change it, you can include the following in your config/config.exs
:
config :arangox, :vst_maxsize, 12_345
Arangox has two HTTP clients, Arangox.GunClient
and Arangox.MintClient
, they require a json library:
def deps do
[
...
{:arangox, "~> 0.4.0"},
{:jason, "~> 1.1"},
{:gun, "~> 1.3.0"} # or {:mint, "~> 0.4.0"}
]
end
Arangox.start_link(client: Arangox.GunClient) # or Arangox.MintClient
iex> {:ok, conn} = Arangox.start_link(client: Arangox.GunClient)
iex> {:ok, %Arangox.Response{status: 200, body: nil}} = Arangox.options(conn, "/")
NOTE: :mint
doesn't support unix sockets.
NOTE: Since :gun
is an Erlang library, you might need to add it as an extra application in mix.exs
:
def application() do
[
extra_applications: [:logger, :gun]
]
end
To use something else, you'd have to implement the Arangox.Client
behaviour in a
module somewhere and set that instead.
The default json library is Jason
. To use a different library, set the :json_library
config to the module of your choice, i.e:
config :arangox, :json_library, Poison
pool size 10
parallel processes 1000
system virtual machine, 1 cpu (not shared), 2GB RAM
Name | Latency |
---|---|
Velocy: GET | 179.74 ms |
Velocy: POST | 201.23 ms |
Mint: GET | 207.00 ms |
Mint: POST | 216.53 ms |
Gun: GET | 222.61 ms |
Gun: POST | 243.65 ms |
Results generated with Benchee
.
Arangox assumes defaults for the :endpoints
, :username
and :password
options,
and db_connection
assumes a default
:pool_size
of 1
, so the following:
Arangox.start_link()
Is equivalent to:
options = [
endpoints: "http://localhost:8529",
pool_size: 1
]
Arangox.start_link(options)
Unencrypted endpoints can be specified with either http://
or
tcp://
, whereas encrypted endpoints can be specified with https://
,
ssl://
or tls://
:
"tcp://localhost:8529" == "http://localhost:8529"
"https://localhost:8529" == "ssl://localhost:8529" == "tls://localhost:8529"
"tcp+unix:///tmp/arangodb.sock" == "http+unix:///tmp/arangodb.sock"
"https+unix:///tmp/arangodb.sock" == "ssl+unix:///tmp/arangodb.sock" == "tls+unix:///tmp/arangodb.sock"
"tcp://unix:/tmp/arangodb.sock" == "http://unix:/tmp/arangodb.sock"
"https://unix:/tmp/arangodb.sock" == "ssl://unix:/tmp/arangodb.sock" == "tls://unix:/tmp/arangodb.sock"
The :endpoints
option accepts either a binary, or a list of binaries. In the case of a list,
Arangox will try to establish a connection with the first endpoint it can.
If a connection is established, the availability of the server will be checked (via the ArangoDB api), and if an endpoint is in maintenance mode or is a Follower in an Active Failover setup, the connection will be dropped, or in the case of a list, the endpoint skipped.
With the :read_only?
option set to true
, arangox will try to find a server in
readonly mode instead and add the x-arango-allow-dirty-read header to every request:
iex> endpoints = ["http://localhost:8003", "http://localhost:8004", "http://localhost:8005"]
iex> {:ok, conn} = Arangox.start_link(endpoints: endpoints, read_only?: true)
iex> %Arangox.Response{body: body} = Arangox.get!(conn, "/_admin/server/mode")
iex> body["mode"]
"readonly"
iex> {:error, %Arangox.Error{status: 403}} = Arangox.post(conn, "/_api/database", %{name: "newDatabase"})
ArangoDB's VelocyStream endpoints do not read authorization headers, authentication configuration must be
provided as options to Arangox.start_link/1
.
As a consequence, if you're using bearer auth, there are a couple of caveats to bear in mind:
When using an HTTP client, Arangox will generate a Basic or Bearer authorization header if the :auth
option is set to {:basic, username, password}
or to {:bearer, token}
respectively, and append it to every request. If the :auth
option is not explicitly set, no authorization header will be appended.
iex> {:ok, conn} = Arangox.start_link(client: Arangox.GunClient, endpoints: "http://localhost:8001")
iex> {:error, %Arangox.Error{status: 401}} = Arangox.get(conn, "/_admin/server/mode")
The header value is obfuscated in transfomed requests returned by arangox, for obvious reasons:
iex> {:ok, conn} = Arangox.start_link(client: Arangox.GunClient, auth: {:basic, "root", ""})
iex> {:ok, request, _response} = Arangox.request(conn, :options, "/")
iex> request.headers
%{"authorization" => "..."}
If the :database
option is set, it can be overridden by prepending the path of a
request with /_db/:value
. If nothing is set, the request will be sent as-is and
ArangoDB will assume the _system
database.
When using an HTTP client, arangox will prepend /_db/:value
to the path of every request
only if one isn't already prepended. If a :database
option is not set, nothing is prepended.
iex> {:ok, conn} = Arangox.start_link(client: Arangox.GunClient)
iex> {:ok, request, _response} = Arangox.request(conn, :get, "/_admin/time")
iex> request.path
"/_admin/time"
iex> {:ok, conn} = Arangox.start_link(database: "_system", client: Arangox.GunClient)
iex> {:ok, request, _response} = Arangox.request(conn, :get, "/_admin/time")
iex> request.path
"/_db/_system/_admin/time"
iex> {:ok, request, _response} = Arangox.request(conn, :get, "/_db/_system/_admin/time")
iex> request.path
"/_db/_system/_admin/time"
Headers can be given as maps:
%{"header" => "value"}
Or lists of two binary element tuples:
[{"header", "value"}]
Headers given to the start option are merged with every request, but will not override any of the headers set by Arangox:
iex> {:ok, conn} = Arangox.start_link(headers: %{"header" => "value"})
iex> {:ok, request, _response} = Arangox.request(conn, :get, "/_api/version")
iex> request.headers
%{"header" => "value"}
Headers passed to requests will override any of the headers given to the start option or set by Arangox:
iex> {:ok, conn} = Arangox.start_link(headers: %{"header" => "value"})
iex> {:ok, request, _response} = Arangox.request(conn, :get, "/_api/version", "", %{"header" => "new_value"})
iex> request.headers
%{"header" => "new_value"}
The :connect_timeout
start option defaults to 5_000
.
Transport options can be specified via :tcp_opts
and :ssl_opts
, for unencrypted and
encrypted connections respectively. When using :gun
or :mint
, these options are passed
directly to the :transport_opts
connect option.
See :gen_tcp.connect_option()
for more information on :tcp_opts
,
or :ssl.tls_client_option()
for :ssl_opts
.
The :client_opts
option can be used to pass client-specific options to :gun
or :mint
.
These options are merged with and may override values set by arangox. Some options cannot be
overridden (i.e. :mint
's :mode
option). If :transport_opts
is set here it will override
everything given to :tcp_opts
or :ssl_opts
, regardless of whether or not a connection is
encrypted.
See the gun:opts()
type in the gun docs
or connect/4
in the mint docs for more
information.
Request options are handled by and passed directly to :db_connection
.
See execute/4 in the :db_connection
docs for supported
options.
Request timeouts default to 15_000
.
iex> {:ok, conn} = Arangox.start_link()
iex> %Arangox.Response{status: 200, body: %{"code" => 200, "error" => false, "mode" => "default"}} = Arangox.get!(conn, "/_admin/server/availability", [], timeout: 15_000)
mix format
mix do format, credo --strict
docker-compose up -d
mix test
:get_endpoints
and :port_mappings
options