Arangox
An implementation of db_connection
for ArangoDB, which is silly because ArangoDB is not a transactional database (i.e.
no prepare, commit, rollback, etc.), but whatever, it's a solid connection pooler.
Tested on:
- ArangoDB 3.3.9 - 3.5
- Elixir 1.6 - 1.9
- OTP 20 - 22
Supports active failover.
Peer Dependencies
Arangox requires a json library and http client to work, the defaults are :jason
and
:gun
:
def deps do
[
...
{:arangox, "~> 0.1.0"},
{:jason, "~> 1.1"},
{:gun, "~> 1.3"}
]
end
You might need to add :gun
as an extra application in mix.exs
:
def application() do
[
extra_applications: [:logger, :gun])
]
end
To use a different json library, set the :json_library
config to the module of your
choice:
config :arangox, :json_library, Poison
Arangox already has a Mint
client. To use it, add :mint
to your deps instead of
:gun
and set the :client
start option to Arangox.Client.Mint
:
Arangox.start_link(client: Arangox.Client.Mint)
NOTE: Mint
does not accept paths to unix sockets at the moment.
To use something else, you'd have to implement the Arangox.Client
behaviour in a
module somewhere and set that instead. The Arangox.Endpoint
module has utilities
for parsing ArangoDB endpoints.
Examples
iex> {:ok, conn} = Arangox.start_link(pool_size: 10)
iex> Arangox.request(conn, :options, "/")
{:ok,
%Arangox.Request{
body: "",
headers: [{"authorization", "..."}],
method: :options,
path: "/"
},
%Arangox.Response{
body: nil,
headers: [
{"x-content-type-options", "nosniff"},
{"allow", "DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT"},
{"server", "ArangoDB"},
{"connection", "Keep-Alive"},
{"content-type", "text/plain; charset=utf-8"},
{"content-length", "0"}
],
status: 200
}}
iex> Arangox.options!(conn)
%Arangox.Response{
body: nil,
headers: [
{"x-content-type-options", "nosniff"},
{"allow", "DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT"},
{"server", "ArangoDB"},
{"connection", "Keep-Alive"},
{"content-type", "text/plain; charset=utf-8"},
{"content-length", "0"}
],
status: 200
}
Options
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 = [
pool_size: 1,
endpoints: ["http://localhost:8529"],
username: "root",
password: ""
]
Arangox.start_link(options)
Endpoints
See the arangosh or arangojs documentation for examples of supported endpoint formats.
As is common amongst ArangoDB drivers, arangox takes a list of endpoints as binaries:
endpoints = [
"http://localhost:8529",
"http://localhost:8530",
"http://localhost:8531"
]
Arangox.start_link(endpoints: endpoints)
Arangox will try to establish a connection with the first endpoint it can and check it's availability (via the ArangoDB api). If an endpoint is in maintenance mode or is a follower in an active failover setup, it will be 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, exception} = Arangox.post(conn, "/_api/database", %{name: "newDatabase"})
iex> exception.message
"forbidden"
Authentication
Arangox will generate an authorization header with the :username
and :password
options and add it to every request. To prevent this behavior, set the :auth?
option to false
.
iex> {:ok, conn} = Arangox.start_link(auth?: false)
iex> {:error, exception} = Arangox.get(conn, "/_admin/server/mode")
iex> exception.message
"not authorized to execute this request"
The header value is obfuscated in transfomed requests returned by arangox, for obvious reasons:
iex> {:ok, conn} = Arangox.start_link()
iex> {:ok, request, _response} = Arangox.options(conn)
iex> request.headers
[{"authorization", "..."}]
Databases
If a value is given to the :database
option, arangox will prepend /_db/:value
to the path of every request that isn't already prepended. If a value is not given,
nothing is prepended (ArangoDB will assume the _system
database).
iex> {:ok, conn} = Arangox.start_link()
iex> {:ok, request, _response} = Arangox.get(conn, "/_admin/time")
iex> request.path
"/_admin/time"
iex> {:ok, conn} = Arangox.start_link(database: "myDatabase")
iex> {:ok, request, _response} = Arangox.get(conn, "/_admin/time")
iex> request.path
"/_db/myDatabase/_admin/time"
iex> {:ok, request, _response} = Arangox.get(conn, "/_db/anotherDatabase/_admin/time")
iex> request.path
"/_db/anotherDatabase/_admin/time"
Headers
Headers are given as lists of two-element tuples:
[{"header", "value"}, {"another-header", "another-value"}]
When given to the start option they are merged with every request.
iex> {:ok, conn} = Arangox.start_link(headers: [{"header", "value"}])
iex> {:ok, request, _response} = Arangox.options(conn)
iex> request.headers
[{"authorization", "..."}, {"header", "value"}]
Headers can also be passed as an argument to any request:
iex> {:ok, conn} = Arangox.start_link()
iex> {:ok, request, _response} = Arangox.get(conn, "/_admin/time", [{"header", "value"}])
iex> request.headers
[{"header", "value"}, {"authorization", "..."}]
Headers given to the start option will not override any of the headers set by Arangox, but headers passed to requests will.
Transport
Transport options can be specified via :tcp_opts
and :ssl_opts
, for non-encrypted and
encrypted connections respectively. These options are passed directly to the :transport_opts
option of :gun
or Mint
.
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.
Contributing
mix do format, credo --strict
docker-compose up -d
mix test
Roadmap
- A VelocyStream client
- An Ecto adapter
- More descriptive logs
If anyone would like to collaborate, find me on the elixir-lang
or arangodb-community
slack.