Search
ctrl/
Ask AI
Light
Dark
System

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.

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:

Copy
$ 
edgedb project init

Ensure that your database has the following schema:

Copy
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:

Copy
iex(1)> 
{:ok, client} = EdgeDB.start_link()
Copy
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}
}
""")

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.

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:

Copy
iex(1)> 
{:ok, client} = EdgeDB.start_link()
Copy
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:

Copy
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

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:

Copy
iex(1)> 
{:ok, client} = EdgeDB.start_link()
Copy
iex(2)> 
post = EdgeDB.query_single!(client, "select Post filter contains(.body, 'https://hex.pm/packages/edgedb') limit 1")
Copy
iex(3)> 
post[:id]
"3c5c9378-860f-11ec-a22a-0713dfca8baa"

If we try to select a Post that does not exist, nil will be returned:

Copy
iex(4)> 
EdgeDB.query_single!(client, "select Post filter .body = 'lol' limit 1")
nil

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:

Copy
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

Note that EdgeDB.transaction/3 calls can not be nested.

The API for transactions is provided by the EdgeDB.transaction/3 function:

Copy
iex(1)> 
{:ok, client} = EdgeDB.start_link()
Copy
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:

Copy
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)
Copy
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:

Copy
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
Copy
iex(6)> 
...(6)> 
...(6)> 
...(6)> 
spawn(fn ->
 {:ok, client} = EdgeDB.start_link()
 EdgeDB.transaction(client, &callback.(&1, "new_body_1"))
end)
Copy
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:

Copy
iex(8)> 
...(8)> 
...(8)> 
...(8)> 
spawn(fn ->
 {:ok, client} = EdgeDB.start_link()
 EdgeDB.transaction(client, &callback.(&1, "new_body_1"))
end)
Copy
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).

You can also check out an example application using this client to see how to work with it:

https://github.com/nsidnev/edgebeats