Usage
The basic user API for edgedb-elixir
is provided by the EdgeDB
module and in most cases you will use only it. The exception is when you
want to define custom codecs. Checkout the guide on hex.pm or on edgedb.com for more information.
EdgeDB
provides several functions for querying data from the database, which are named in EdgeDB.query*/4
format. Transactions are
supported with EdgeDB.transaction/3
function.
Establishing a connection
edgedb-elixir
, like other EdgeDB clients, allows a very flexible way to define how to connect to an instance. For more information, see
EdgeDB.connect_option/0
.
The examples on this page will involve connecting to an instance using edgedb projects. Run edgedb project init
to initialize the project:
$
edgedb project init
Database schema
Ensure that your database has the following schema:
module default {
type User {
required name: str {
constraint exclusive;
};
}
type Post {
required body: str;
required author: User;
multi comments: Post;
}
};
Let’s fill the database with some data, which will be used in further examples:
iex(1)>
{:ok, client} = EdgeDB.start_link()
iex(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)> ...(2)>
EdgeDB.query!(client, """
WITH
p1 := (
insert Post {
body := 'Yes!',
author := (
insert User {
name := 'commentator1'
}
)
}
),
p2 := (
insert Post {
body := 'Absolutely amazing',
author := (
insert User {
name := 'commentator2'
}
)
}
),
p3 := (
insert Post {
body := 'FYI here is a link to the Elixir client: https://hex.pm/packages/edgedb',
author := (
insert User {
name := 'commentator3'
}
)
}
)
insert Post {
body := 'EdgeDB is awesome! Try the Elixir client for it',
author := (
insert User {
name := 'author1'
}
),
comments := {p1, p2, p3}
}
""")
Querying data from EdgeDB
Depending on the expected results of the query, you can use different functions to retrieve data from the database. This is called the cardinality of the result and is better explained in the relevant documentation.
Querying a set of elements
If you want to receive an EdgeDB.Set
from your query, just use the EdgeDB.query/4
or EdgeDB.query!/4
functions. The difference
between the two functions is that EdgeDB.query/4
will return an :ok
tuple with result if successful or a :error
tuple with
EdgeDB.Error
if an error occurred during the query execution. EdgeDB.query!/4
will return a plain result if successful or raise
EdgeDB.Error
if error.
Let’s query all existing posts with their bodies:
iex(1)>
{:ok, client} = EdgeDB.start_link()
iex(2)>
{:ok, posts} = EdgeDB.query(client, "select Post { body }")
{:ok, #EdgeDB.Set<{#EdgeDB.Object<body := "EdgeDB is awesome! Try the Elixir client for it">, #EdgeDB.Object<body := "Yes!">, #EdgeDB.Object<body := "Absolutely amazing">, #EdgeDB.Object<body := "FYI here is a link to the Elixir client: https://hex.pm/packages/edgedb">}>}
We can iterate over EdgeDB.Set
and inspect each object separately:
iex(3)> ...(3)> ...(3)>
Enum.each(posts, fn post ->
IO.inspect(post[:body], label: "post (#{inspect(post.id)})")
end)
post ("3c5c8cf2-860f-11ec-a22a-2b0ab4e21d4b"): "EdgeDB is awesome! Try the Elixir client for it" post ("3c5c904e-860f-11ec-a22a-f7cdb9bcb510"): "Yes!" post ("3c5c9256-860f-11ec-a22a-0343fa0961f3"): "Absolutely amazing" post ("3c5c9378-860f-11ec-a22a-0713dfca8baa"): "FYI here is a link to the Elixir client: https://hex.pm/packages/edgedb" :ok
Querying a single element
If you know that the query will return only one element or none, you can use EdgeDB.query_single/4
and EdgeDB.query_single!/4
functions.
This function will automatically unpack the underlying EdgeDB.Set
and return the requested item (or nil
if the set is empty).
Let’s query a post with a link to the Elixir client for EdgeDB:
iex(1)>
{:ok, client} = EdgeDB.start_link()
iex(2)>
post = EdgeDB.query_single!(client, "select Post filter contains(.body, 'https://hex.pm/packages/edgedb') limit 1")
iex(3)>
post[:id]
"3c5c9378-860f-11ec-a22a-0713dfca8baa"
If we try to select a Post
that does not exist, nil
will be returned:
iex(4)>
EdgeDB.query_single!(client, "select Post filter .body = 'lol' limit 1")
nil
Querying a required single element
In case we want to ensure that the requested element must exist, we can use the functions EdgeDB.query_required_single/4
and
EdgeDB.query_required_single!/4
. Instead of returning nil
they will return EdgeDB.Error
in case of a missing element:
iex(5)>
EdgeDB.query_required_single!(client, "select Post filter .body = 'lol' limit 1")
** (EdgeDB.Error) NoDataError: expected result, but query did not return any data
Transactions
Note that EdgeDB.transaction/3
calls can not be nested.
The API for transactions is provided by the EdgeDB.transaction/3
function:
iex(1)>
{:ok, client} = EdgeDB.start_link()
iex(2)> ...(2)> ...(2)> ...(2)>
{:ok, user} =
EdgeDB.transaction(client, fn conn ->
EdgeDB.query_required_single!(conn, "insert User { name := <str>$username }", username: "user1")
end)
Transactions can be rollbacked using the EdgeDB.rollback/2
function or automatically if an error has occurred inside a transaction block:
iex(3)> ...(3)> ...(3)> ...(3)> ...(3)>
{:error, :rollback} =
EdgeDB.transaction(client, fn conn ->
EdgeDB.query_required_single!(conn, "insert User { name := <str>$username }", username: "wrong_username")
EdgeDB.rollback(conn)
end)
iex(4)>
EdgeDB.query_single!(client, "select User { name } filter .name = <str>$username", username: "wrong_username")
nil
Transactions are retriable. This means that if certain types of errors occur when querying data from the database, the transaction block can be automatically retried.
The following types of errors can be retried retried:
-
TransactionConflictError
and its inheritors. -
Network errors (e.g. a socket was closed).
As an example, let’s create a transaction conflict to show how this works. In the first example, we will disable retries:
iex(5)> ...(5)> ...(5)> ...(5)> ...(5)>
callback = fn conn, body ->
Process.sleep(500)
EdgeDB.query!(conn, "update Post filter .author.id = <uuid>$user_id set { body := <str>$new_body }", user_id: user.id, new_body: body)
Process.sleep(500)
end
iex(6)> ...(6)> ...(6)> ...(6)>
spawn(fn ->
{:ok, client} = EdgeDB.start_link()
EdgeDB.transaction(client, &callback.(&1, "new_body_1"))
end)
iex(7)>
EdgeDB.transaction(client, &callback.(&1, "new_body_2"), retry: [transaction_conflict: [attempts: 0]])
** (EdgeDB.Error) TransactionSerializationError: could not serialize access due to concurrent update
Now let’s execute the same thing but with enabled retries:
iex(8)> ...(8)> ...(8)> ...(8)>
spawn(fn ->
{:ok, client} = EdgeDB.start_link()
EdgeDB.transaction(client, &callback.(&1, "new_body_1"))
end)
iex(9)>
EdgeDB.transaction(client, &callback.(&1, "new_body_2"))
{:ok, :ok}
All failed transactions will be retried until they succeed or until the number of retries exceeds the limit (the default is 3).
Example
You can also check out an example application using this client to see how to work with it: