Arangox v0.3.0 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.
Supports VelocyStream and Active Failover.
Tested on:
- ArangoDB 3.3.9 - 3.5
- Elixir 1.6 - 1.9
- OTP 20 - 22
Peer Dependencies
By default, Arangox communicates with ArangoDB via the VelocyStream protocol, which requires the :velocy
library:
def deps do
[
...
{:arangox, "~> 0.1.0"},
{:velocy, "~> 1.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
Examples
iex> {:ok, conn} = Arangox.start_link(pool_size: 10)
iex> Arangox.request(conn, :get, "/_admin/server/availability")
{:ok,
%Arangox.Request{
body: "",
headers: %{},
method: :get,
path: "/_admin/server/availability"
},
%Arangox.Response{
body: %{"code" => 200, "error" => false, "mode" => "default"},
headers: %{},
status: 200
}}
iex> Arangox.get!(conn, "/_admin/server/availability")
%Arangox.Response{
body: %{"code" => 200, "error" => false, "mode" => "default"},
headers: %{},
status: 200
}
HTTP
Arangox has two HTTP clients, Arangox.Client.Gun
and Arangox.Client.Mint
, they require a json library:
def deps do
[
...
{:arangox, "~> 0.1.0"},
{:jason, "~> 1.1"},
{:gun, "~> 1.3"} # or {:mint, "~> 0.4.0"}
]
end
Arangox.start_link(client: Arangox.Client.Gun) # or 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 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
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
Examples
iex> {:ok, conn} = Arangox.start_link(client: Arangox.Client.Gun, pool_size: 10)
iex> Arangox.request(conn, :options, "/")
{:ok,
%Arangox.Request{
body: "",
headers: %{"authorization" => "..."},
method: :options,
path: "/"
},
%Arangox.Response{
body: nil,
headers: %{
"allow" => "DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT",
"connection" => "Keep-Alive",
"content-length" => "0",
"content-type" => "text/plain; charset=utf-8",
"server" => "ArangoDB",
"x-content-type-options" => "nosniff"
},
status: 200
}}
iex> Arangox.options!(conn, "/")
%Arangox.Response{
body: nil,
headers: %{
"allow" => "DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT",
"connection" => "Keep-Alive",
"content-length" => "0",
"content-type" => "text/plain; charset=utf-8",
"server" => "ArangoDB",
"x-content-type-options" => "nosniff"
},
status: 200
}
Start 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 = [
endpoints: "http://localhost:8529",
username: "root",
password: "",
pool_size: 1
]
Arangox.start_link(options)
Endpoints
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, exception} = Arangox.post(conn, "/_api/database", %{name: "newDatabase"})
iex> exception.message
"forbidden"
Authentication
Velocy
When using the default client, authorization is resolved with the :username
and :password
options after a connection is established (authorization headers are not used).
This can be disabled by setting the :auth?
option to false
.
HTTP
When using an HTTP client, Arangox will generate a Basic 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, client: Arangox.Client.Gun)
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(client: Arangox.Client.Gun)
iex> {:ok, request, _response} = Arangox.options(conn, "/")
iex> request.headers
%{"authorization" => "..."}
Databases
Velocy
If the :database
option is set, it can be overridden by prepending the path of a
request with /_db/:value
. If nothing is set, ArangoDB will assume the _system
database.
HTTP
When using an HTTP client, arangox will prepend /_db/:value
to the path of every request
only if it isn't already prepended. If the start option is not set, nothing is prepended.
iex> {:ok, conn} = Arangox.start_link(client: Arangox.Client.Gun)
iex> {:ok, request, _response} = Arangox.get(conn, "/_admin/time")
iex> request.path
"/_admin/time"
iex> {:ok, conn} = Arangox.start_link(database: "_system", client: Arangox.Client.Gun)
iex> {:ok, request, _response} = Arangox.get(conn, "/_admin/time")
iex> request.path
"/_db/_system/_admin/time"
iex> {:ok, request, _response} = Arangox.get(conn, "/_db/_system/_admin/time")
iex> request.path
"/_db/_system/_admin/time"
Headers
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.get(conn, "/_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.get(conn, "/_api/version", %{"header" => "new_value"})
iex> request.headers
%{"header" => "new_value"}
Transport
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
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.get!(conn, "/_admin/server/availability", [], timeout: 15_000)
%Arangox.Response{
body: %{"code" => 200, "error" => false, "mode" => "default"},
headers: %{},
status: 200
}
Contributing
mix do format, credo --strict
docker-compose up -d
mix test
Roadmap
- An Ecto adapter
- More descriptive logs
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, headers()} | {: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(), headers(), [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(), headers(), [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(), headers(), [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(), headers(), [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(), headers(), [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(), headers(), [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 change the library, include the following in your config/config.exs
:
config :arangox, :json_library, Module
Defaults to Jason
.
options(conn, path, headers \\ %{}, opts \\ [])
View Sourceoptions(conn(), path(), headers(), [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, path, headers \\ %{}, opts \\ [])
View Sourceoptions!(conn(), path(), headers(), [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(), headers(), [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(), headers(), [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(), headers(), [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(), headers(), [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(), headers(), [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(), headers(), [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(), headers(), [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(), headers(), [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
- Either a single ArangoDB endpoint binary, or a list of 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 an available endpoint 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 map of headers to merge with every request.:auth?
- Configure whether or not to resolve authorization with the:username
and:password
options. 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 only called if a list of endpoints is given, 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)