Packages

An implementation of DBConnection for ArangoDB. Velocy and JSON over HTTP/2, the new AQL plan cache, resource APIs covering ArangoDB's HTTP surface, and pools, transactions and cursors via DBConnection. VelocyStream and Active Failover remain for 3.11.

Current section

Files

Jump to
arangox README.md
Raw

README.md

# Arangox
[![Build Status](https://travis-ci.com/suazithustra/arangox.svg?branch=master)](https://travis-ci.org/suazithustra/arangox)
An implementation of [`db_connection`](https://hex.pm/packages/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](https://www.arangodb.com/docs/stable/architecture-deployment-modes-active-failover-architecture.html).
### Peer Dependencies
Arangox requires a json library and http client to work, the defaults are `:jason` and
`:gun`:
```elixir
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`:
```elixir
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:
```elixir
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`:
```elixir
Arangox.start_link(client: Arangox.Client.Mint)
```
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
```elixir
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`](https://hex.pm/packages/db_connection) assumes a default
`:pool_size` of `1` so the following:
```elixir
Arangox.start_link()
```
Is equivalent to:
```elixir
options = [
pool_size: 1,
endpoints: ["http://localhost:8529"],
username: "root",
password: ""
]
Arangox.start_link(options)
```
### Endpoints
See the
[arangosh](https://www.arangodb.com/docs/stable/programs-arangosh-examples.html) or
[arangojs](https://www.arangodb.com/docs/stable/drivers/js-reference-database.html)
documentation for examples of supported endpoint formats.
As is common amongst _ArangoDB_ drivers, arangox takes a list of endpoints as binaries:
```elixir
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:
```elixir
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`.
```elixir
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:
```elixir
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).
```elixir
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:
```elixir
[{"header", "value"}, {"another-header", "another-value"}]
```
When given to the start option they are merged with every request.
```elixir
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:
```elixir
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()`](http://erlang.org/doc/man/gen_tcp.html#type-connect_option)
for more information on `:tcp_opts`, or [`:ssl.tls_client_option()`](http://erlang.org/doc/man/ssl.html#type-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](https://ninenines.eu/docs/en/gun/1.3/manual/gun/)
or [`connect/4`](https://hexdocs.pm/mint/Mint.HTTP.html#connect/4) in the mint docs for more
information.
## Contributing
```
mix do format, credo
docker-compose up -d
mix test
```
## Roadmap
- A VelocyStream client
- An Ecto adapter
- More descriptive logs