Arangox v0.2.1 Arangox View Source
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.
Link to this section Summary
Functions
Returns a supervisor child specification for a DBConnection pool.
Runs a DELETE request against a connection pool.
Runs a DELETE request against a connection pool. Raises in the case of an error.
Runs a GET request against a connection pool.
Runs a GET request against a connection pool. Raises in the case of an error.
Runs a HEAD request against a connection pool.
Runs a HEAD request against a connection pool. Raises in the case of an error.
Returns the configured JSON library.
Runs a OPTIONS request against a connection pool.
Runs a OPTIONS request against a connection pool. Raises in the case of an error.
Runs a PATCH request against a connection pool.
Runs a PATCH request against a connection pool. Raises in the case of an error.
Runs a POST request against a connection pool.
Runs a POST request against a connection pool. Raises in the case of an error.
Runs a PUT request against a connection pool.
Runs a PUT request against a connection pool. Raises in the case of an error.
Runs a request against a connection pool.
Runs a request against a connection pool. Raises in the case of an error.
Starts a connection pool.
Acquire a connection from a pool and run a series of requests with it. If the connection disconnects, all future calls using that connection reference will fail.
Link to this section Types
method()
View Sourcemethod() :: :get | :head | :delete | :post | :put | :patch | :options
start_option()
View Sourcestart_option() :: {:client, module()} | {:endpoints, [endpoint()]} | {:auth?, boolean()} | {:database, binary()} | {:username, binary()} | {:password, binary()} | {:headers, [header()]} | {:read_only?, boolean()} | {:connect_timeout, timeout()} | {:failover_callback, (Arangox.Error.t() -> any()) | {module(), atom(), [any()]}} | {:tcp_opts, [:gen_tcp.connect_option()]} | {:ssl_opts, [:ssl.tls_client_option()]} | {:client_opts, :gun.opts() | keyword()} | DBConnection.start_option()
Link to this section Functions
child_spec(opts \\ [])
View Sourcechild_spec([start_option()]) :: Supervisor.child_spec()
Returns a supervisor child specification for a DBConnection pool.
delete(conn, path, headers \\ [], opts \\ [])
View Sourcedelete(conn(), path(), [header()], [DBConnection.option()]) :: {:ok, Arangox.Request.t(), Arangox.Response.t()} | {:error, any()}
Runs a DELETE request against a connection pool.
Accepts any of the options accepted by DBConnection.execute/4
.
delete!(conn, path, headers \\ [], opts \\ [])
View Sourcedelete!(conn(), path(), [header()], [DBConnection.option()]) :: Arangox.Response.t()
Runs a DELETE request against a connection pool. Raises in the case of an error.
Accepts any of the options accepted by DBConnection.execute!/4
.
get(conn, path, headers \\ [], opts \\ [])
View Sourceget(conn(), path(), [header()], [DBConnection.option()]) :: {:ok, Arangox.Request.t(), Arangox.Response.t()} | {:error, any()}
Runs a GET request against a connection pool.
Accepts any of the options accepted by DBConnection.execute/4
.
get!(conn, path, headers \\ [], opts \\ [])
View Sourceget!(conn(), path(), [header()], [DBConnection.option()]) :: Arangox.Response.t()
Runs a GET request against a connection pool. Raises in the case of an error.
Accepts any of the options accepted by DBConnection.execute!/4
.
head(conn, path, headers \\ [], opts \\ [])
View Sourcehead(conn(), path(), [header()], [DBConnection.option()]) :: {:ok, Arangox.Request.t(), Arangox.Response.t()} | {:error, any()}
Runs a HEAD request against a connection pool.
Accepts any of the options accepted by DBConnection.execute/4
.
head!(conn, path, headers \\ [], opts \\ [])
View Sourcehead!(conn(), path(), [header()], [DBConnection.option()]) :: Arangox.Response.t()
Runs a HEAD request against a connection pool. Raises in the case of an error.
Accepts any of the options accepted by DBConnection.execute!/4
.
Returns the configured JSON library.
To customize the JSON library, include the following in your config/config.exs
:
config :arangox, :json_library, Module
Defaults to Jason
.
options(conn, opts \\ [])
View Sourceoptions(conn(), [DBConnection.option()]) :: {:ok, Arangox.Request.t(), Arangox.Response.t()} | {:error, any()}
Runs a OPTIONS request against a connection pool.
Accepts any of the options accepted by DBConnection.execute/4
.
options!(conn, opts \\ [])
View Sourceoptions!(conn(), [DBConnection.option()]) :: Arangox.Response.t()
Runs a OPTIONS request against a connection pool. Raises in the case of an error.
Accepts any of the options accepted by DBConnection.execute!/4
.
patch(conn, path, body \\ "", headers \\ [], opts \\ [])
View Sourcepatch(conn(), path(), body(), [header()], [DBConnection.option()]) :: {:ok, Arangox.Request.t(), Arangox.Response.t()} | {:error, any()}
Runs a PATCH request against a connection pool.
Accepts any of the options accepted by DBConnection.execute/4
.
patch!(conn, path, body \\ "", headers \\ [], opts \\ [])
View Sourcepatch!(conn(), path(), body(), [header()], [DBConnection.option()]) :: Arangox.Response.t()
Runs a PATCH request against a connection pool. Raises in the case of an error.
Accepts any of the options accepted by DBConnection.execute!/4
.
post(conn, path, body \\ "", headers \\ [], opts \\ [])
View Sourcepost(conn(), path(), body(), [header()], [DBConnection.option()]) :: {:ok, Arangox.Request.t(), Arangox.Response.t()} | {:error, any()}
Runs a POST request against a connection pool.
Accepts any of the options accepted by DBConnection.execute/4
.
post!(conn, path, body \\ "", headers \\ [], opts \\ [])
View Sourcepost!(conn(), path(), body(), [header()], [DBConnection.option()]) :: Arangox.Response.t()
Runs a POST request against a connection pool. Raises in the case of an error.
Accepts any of the options accepted by DBConnection.execute!/4
.
put(conn, path, body \\ "", headers \\ [], opts \\ [])
View Sourceput(conn(), path(), body(), [header()], [DBConnection.option()]) :: {:ok, Arangox.Request.t(), Arangox.Response.t()} | {:error, any()}
Runs a PUT request against a connection pool.
Accepts any of the options accepted by DBConnection.execute/4
.
put!(conn, path, body \\ "", headers \\ [], opts \\ [])
View Sourceput!(conn(), path(), body(), [header()], [DBConnection.option()]) :: Arangox.Response.t()
Runs a PUT request against a connection pool. Raises in the case of an error.
Accepts any of the options accepted by DBConnection.execute!/4
.
request(conn, method, path, body \\ "", headers \\ [], opts \\ [])
View Sourcerequest(conn(), method(), path(), body(), [header()], [DBConnection.option()]) :: {:ok, Arangox.Request.t(), Arangox.Response.t()} | {:error, any()}
Runs a request against a connection pool.
Accepts any of the options accepted by DBConnection.execute/4
.
request!(conn, method, path, body \\ "", headers \\ [], opts \\ [])
View Sourcerequest!(conn(), method(), path(), body(), [header()], [DBConnection.option()]) :: Arangox.Response.t()
Runs a request against a connection pool. Raises in the case of an error.
Accepts any of the options accepted by DBConnection.execute!/4
.
start_link(opts \\ [])
View Sourcestart_link([start_option()]) :: GenServer.on_start()
Starts a connection pool.
Options
Accepts any of the options accepted by DBConnection.start_link/2
, as well as any of the
following:
:endpoints
- A list of ArangoDB endpoints in order of presedence. Each process in a pool will individually attempt to establish a connection with and check the availablility of each endpoint in the order given until one is found. Defaults to["http://localhost:8529"]
.:database
- 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).:headers
- A list of headers to merge with every request.:auth?
- Configure whether or not to add an authorization header to every request with the provided username and password. Defaults totrue
.:username
- Defaults to"root"
.:password
- Defaults to""
.:read_only?
- Read-only pools will only connect to followers in an active failover setup and add an x-arango-allow-dirty-read header to every request. Defaults tofalse
.:connect_timeout
- Sets the timeout for establishing a connection with a server.:transport_opts
- Transport options for the socket interface (which is:gen_tcp
or:ssl
for both gun and mint, depending on whether or not they are connecting via ssl).:tcp_opts
- Transport options for the tcp socket interface (:gen_tcp
in the case of gun or mint).:ssl_opts
- Transport options for the ssl socket interface (:ssl
in the case of gun or mint).:client
- A module that implements theArangox.Client
behaviour. Defaults toArangox.Client.Gun
.:client_opts
- Options for the client library being used. WARNING: If:transport_opts
is set here it will override the options given to:tcp_opts
and:ssl_opts
.:failover_callback
- A function to call every time arangox fails to establish a connection. This is called regardless of whether or not it's connecting to an endpoint in an active failover setup. Can be either an anonymous function that takes one argument (which is an%Arangox.Error{}
struct), or a three-element tuple containing arguments to pass toapply/3
(in which case an%Arangox.Error{}
struct is always prepended to the arguments).
transaction(conn, fun, opts \\ [])
View Sourcetransaction(conn(), (DBConnection.t() -> result), [DBConnection.option()]) :: {:ok, result} | {:error, any()} when result: var
Acquire a connection from a pool and run a series of requests with it. If the connection disconnects, all future calls using that connection reference will fail.
Requests can be nested multiple times if the connection reference is used to start a nested transaction (i.e. calling another function that calls this one). The top level transaction function will represent the actual transaction.
Delegates to DBConnection.transaction/3
.
Example
{:ok, result} =
Arangox.transaction(conn, fn c ->
Arangox.request!(c, ...)
end)