Gel Database MCP Server
by christian561
.. File: access_policies.rst
.. _ref_datamodel_access_policies:
===============
Access Policies
===============
.. index:: access policy, object-level security, row-level security, RLS,
allow, deny, using
Object types in |Gel| can contain security policies that restrict the set of
objects that can be selected, inserted, updated, or deleted by a particular
query. This is known as *object-level security* and is similar in function
to SQL's row-level security.
When no access policies are defined, object-level security is not activated:
any properly authenticated client can carry out any operation on any object
in the database. Access policies allow you to ensure that the database itself
handles access control logic rather than having to implement it in every
application or service that connects to your database.
Access policies can greatly simplify your backend code, centralizing access
control logic in a single place. They can also be extremely useful for
implementing AI agentic flows, where you want to have guardrails around
your data that agents can't break.
We'll illustrate access policies in this document with this simple schema:
.. code-block:: sdl
type User {
required email: str { constraint exclusive; }
}
type BlogPost {
required title: str;
required author: User;
}
.. warning::
Once a policy is added to a particular object type, **all operations**
(``select``, ``insert``, ``delete``, ``update``, etc.) on any object of
that type are now *disallowed by default* unless specifically allowed by
an access policy! See :ref:`resolution order <ref_datamodel_access_policies>`
below for details.
Global variables
================
Global variables are a convenient way to set up the context for your access
policies. Gel's global variables are tightly integrated with the Gel's
data model, client APIs, EdgeQL and SQL, and the tooling around them.
Global variables in Gel are not pre-defined. Users are free to define
as many globals in their schema as they want to represent the business
logic of their application.
A common scenario is storing a ``current_user`` global representing
the user executing queries. We'd like to have a slightly more complex example
showing that you can use more than one global variable. Let's do that:
* We'll use one *global* ``uuid`` to represent the identity of the user
executing the query.
* We'll have the ``Country`` *enum* to represent the type of country
that the user is currently in. The enum represents three types of
countries: those where the service has not been rolled out, those with
read-only access, and those with full access.
* We'll use the ``current_country`` *global* to represent the user's
current country. In our *example schema*, we want *country* to be
context-specific: the same user who can access certain content in one
country might not be able to in another country (let's imagine that's
due to different country-specific legal frameworks).
Here is an illustration:
.. code-block:: sdl-diff
+ scalar type Country extending enum<Full, ReadOnly, None>;
+ global current_user: uuid;
+ required global current_country: Country {
+ default := Country.None
+ }
type User {
required email: str { constraint exclusive; }
}
type BlogPost {
required title: str;
required author: User;
}
You can set and reset these globals in Gel client libraries, for example:
.. tabs::
.. code-tab:: typescript
import createClient from 'gel';
const client = createClient();
// 'authedClient' will share the network connection with 'client',
// but will have the 'current_user' global set.
const authedClient = client.withGlobals({
current_user: '2141a5b4-5634-4ccc-b835-437863534c51',
});
const result = await authedClient.query(
`select global current_user;`);
console.log(result);
.. code-tab:: python
from gel import create_client
client = create_client().with_globals({
'current_user': '580cc652-8ab8-4a20-8db9-4c79a4b1fd81'
})
result = client.query("""
select global current_user;
""")
print(result)
.. code-tab:: go
package main
import (
"context"
"fmt"
"log"
"github.com/geldata/gel-go"
)
func main() {
ctx := context.Background()
client, err := gel.CreateClient(ctx, gel.Options{})
if err != nil {
log.Fatal(err)
}
defer client.Close()
id, err := gel.ParseUUID("2141a5b4-5634-4ccc-b835-437863534c51")
if err != nil {
log.Fatal(err)
}
var result gel.UUID
err = client.
WithGlobals(map[string]interface{}{"current_user": id}).
QuerySingle(ctx, "SELECT global current_user;", &result)
if err != nil {
log.Fatal(err)
}
fmt.Println(result)
}
.. code-tab:: rust
use gel_protocol::{
model::Uuid,
value::EnumValue
};
let client = gel_tokio::create_client()
.await
.expect("Client should init")
.with_globals_fn(|c| {
c.set(
"current_user",
Value::Uuid(
Uuid::parse_str("2141a5b4-5634-4ccc-b835-437863534c51")
.expect("Uuid should have parsed"),
),
);
c.set(
"current_country",
Value::Enum(EnumValue::from("Full"))
);
});
client
.query_required_single::<Uuid, _>("select global current_user;", &())
.await
.expect("Returning value");
Defining policies
=================
A policy example for our simple blog schema might look like:
.. code-block:: sdl-diff
global current_user: uuid;
required global current_country: Country {
default := Country.None
}
scalar type Country extending enum<Full, ReadOnly, None>;
type User {
required email: str { constraint exclusive; }
}
type BlogPost {
required title: str;
required author: User;
+ access policy author_has_full_access
+ allow all
+ using (global current_user ?= .author.id
+ and global current_country ?= Country.Full) {
+ errmessage := "User does not have full access";
+ }
+ access policy author_has_read_access
+ allow select
+ using (global current_user ?= .author.id
+ and global current_country ?= Country.ReadOnly);
}
Explanation:
- ``access policy <name>`` introduces a new policy in an object type.
- ``allow all`` grants ``select``, ``insert``, ``update``, and ``delete``
access if the condition passes. We also used a separate policy to allow
only ``select`` in some cases.
- ``using (<expr>)`` is a boolean filter restricting the set of objects to
which the policy applies. (We used the coalescing operator ``?=`` to
handle empty sets gracefully.)
- ``errmessage`` is an optional custom message to display in case of a write
violation.
Let's run some experiments in the REPL:
.. code-block:: edgeql-repl
db> insert User { email := "test@example.com" };
{default::User {id: be44b326-03db-11ed-b346-7f1594474966}}
db> set global current_user :=
... <uuid>"be44b326-03db-11ed-b346-7f1594474966";
OK: SET GLOBAL
db> set global current_country := Country.Full;
OK: SET GLOBAL
db> insert BlogPost {
... title := "My post",
... author := (select User filter .id = global current_user)
... };
{default::BlogPost {id: e76afeae-03db-11ed-b346-fbb81f537ca6}}
Because the user is in a "full access" country and the current user ID
matches the author, the new blog post is permitted. When the same user sets
``global current_country := Country.ReadOnly;``:
.. code-block:: edgeql-repl
db> set global current_country := Country.ReadOnly;
OK: SET GLOBAL
db> select BlogPost;
{default::BlogPost {id: e76afeae-03db-11ed-b346-fbb81f537ca6}}
db> insert BlogPost {
... title := "My second post",
... author := (select User filter .id = global current_user)
... };
gel error: AccessPolicyError: access policy violation on
insert of default::BlogPost (User does not have full access)
Finally, let's unset ``current_user`` and see how many blog posts are returned
when we count them.
.. code-block:: edgeql-repl
db> set global current_user := {};
OK: SET GLOBAL
db> select BlogPost;
{}
db> select count(BlogPost);
{0}
``select BlogPost`` returns zero results in this case as well. We can only
``select`` the *posts* written by the *user* specified by ``current_user``.
When ``current_user`` has no value or has a different value from the
``.author.id`` of any existing ``BlogPost`` objects, we can't read any posts.
But thanks to ``Country`` being set to ``Country.Full``, this user will be
able to write a new blog post.
**The bottom line:** access policies use global variables to define a
"subgraph" of data that is visible to your queries.
Policy types
============
.. index:: access policy, select, insert, delete, update, update read,
update write, all
The types of policy rules map to the statement type in EdgeQL:
- ``select``: Controls which objects are visible to any query.
- ``insert``: Post-insert check. If the inserted object violates the policy,
the operation fails.
- ``delete``: Controls which objects can be deleted.
- ``update read``: Pre-update check on which objects can be updated at all.
- ``update write``: Post-update check for how objects can be updated.
- ``all``: Shorthand for granting or denying ``select, insert, update,
delete``.
Resolution order
================
If multiple policies apply (some are ``allow`` and some are ``deny``), the
logic is:
1. If there are no policies, access is allowed.
2. All ``allow`` policies collectively form a *union* / *or* of allowed sets.
3. All ``deny`` policies *subtract* from that union, overriding allows!
4. The final set of objects is the intersection of the above logic for each
operation: ``select, insert, update read, update write, delete``.
By default, once you define any policy on an object type, you must explicitly
allow the operations you need. This is a common **pitfall** when you are
starting out with access policies (but you will develop an intuition for this
quickly). Let's look at an example:
.. code-block:: sdl
global current_user_id: uuid;
global current_user := (
select User filter .id = global current_user_id
);
type User {
required email: str { constraint exclusive; }
required is_admin: bool { default := false };
access policy admin_only
allow all
using (global current_user.is_admin ?? false);
}
type BlogPost {
required title: str;
author: User;
access policy author_has_full_access
allow all
using (global current_user ?= .author.id);
}
In the above schema only admins will see a non-empty ``author`` link when
running ``select BlogPost { author }``. Why? Because only admins can see
``User`` objects at all: ``admin_only`` policy is the only one defined on
the ``User`` type!
This means that instead of making ``BlogPost`` visible to its author, all
non-admin authors won't be able to see their own posts. The above issue can be
remedied by making the current user able to see their own ``User`` record.
Interaction between policies
============================
Policy expressions themselves do not take other policies into account
(since |EdgeDB| 3). This makes it easier to reason about policies.
Custom error messages
=====================
.. index:: access policy, errmessage, using
When an ``insert`` or ``update write`` violates an access policy, Gel will
raise a generic ``AccessPolicyError``:
.. code-block::
gel error: AccessPolicyError: access policy violation
on insert of <type>
.. note::
Restricted access is represented either as an error message or an empty
set, depending on the filtering order of the operation. The operations
``select``, ``delete``, or ``update read`` filter up front, and thus you
simply won't get the data that is being restricted. Other operations
(``insert`` and ``update write``) will return an error message.
If multiple policies are in effect, it can be helpful to define a distinct
``errmessage`` in your policy:
.. code-block:: sdl-diff
global current_user_id: uuid;
global current_user := (
select User filter .id = global current_user_id
);
type User {
required email: str { constraint exclusive; };
required is_admin: bool { default := false };
access policy admin_only
allow all
+ using (global current_user.is_admin ?? false) {
+ errmessage := 'Only admins may query Users'
+ };
}
type BlogPost {
required title: str;
author: User;
access policy author_has_full_access
allow all
+ using (global current_user ?= .author) {
+ errmessage := 'BlogPosts may only be queried by their authors'
+ };
}
Now if you attempt, for example, a ``User`` insert as a non-admin user, you
will receive this error:
.. code-block::
gel error: AccessPolicyError: access policy violation on insert of
default::User (Only admins may query Users)
Disabling policies
==================
.. index:: apply_access_policies
You may disable all access policies by setting the ``apply_access_policies``
:ref:`configuration parameter <ref_std_cfg>` to ``false``.
You may also temporarily disable access policies using the Gel UI configuration
checkbox (or via :gelcmd:`ui`), which only applies to your UI session.
More examples
=============
Here are some additional patterns:
1. Publicly visible blog posts, only writable by the author:
.. code-block:: sdl-diff
global current_user: uuid;
type User {
required email: str { constraint exclusive; }
}
type BlogPost {
required title: str;
required author: User;
+ required published: bool { default := false };
access policy author_has_full_access
allow all
using (global current_user ?= .author.id);
+ access policy visible_if_published
+ allow select
+ using (.published);
}
2. Visible to friends, only modifiable by the author:
.. code-block:: sdl-diff
global current_user: uuid;
type User {
required email: str { constraint exclusive; }
+ multi friends: User;
}
type BlogPost {
required title: str;
required author: User;
access policy author_has_full_access
allow all
using (global current_user ?= .author.id);
+ access policy friends_can_read
+ allow select
+ using ((global current_user in .author.friends.id) ?? false);
}
3. Publicly visible except to those blocked by the author:
.. code-block:: sdl-diff
type User {
required email: str { constraint exclusive; }
+ multi blocked: User;
}
type BlogPost {
required title: str;
required author: User;
access policy author_has_full_access
allow all
using (global current_user ?= .author.id);
+ access policy anyone_can_read
+ allow select;
+ access policy exclude_blocked
+ deny select
+ using ((global current_user in .author.blocked.id) ?? false);
}
4. "Disappearing" posts that become invisible after 24 hours:
.. code-block:: sdl-diff
type User {
required email: str { constraint exclusive; }
}
type BlogPost {
required title: str;
required author: User;
+ required created_at: datetime {
+ default := datetime_of_statement() # non-volatile
+ }
access policy author_has_full_access
allow all
using (global current_user ?= .author.id);
+ access policy hide_after_24hrs
+ allow select
+ using (
+ datetime_of_statement() - .created_at < <duration>'24 hours'
+ );
}
Super constraints
=================
Access policies can act like "super constraints." For instance, a policy on
``insert`` or ``update write`` can do a post-write validity check, rejecting
the operation if a certain condition is not met.
E.g. here's a policy that limits the number of blog posts a
``User`` can post:
.. code-block:: sdl-diff
type User {
required email: str { constraint exclusive; }
+ multi posts := .<author[is BlogPost]
}
type BlogPost {
required title: str;
required author: User;
access policy author_has_full_access
allow all
using (global current_user ?= .author.id);
+ access policy max_posts_limit
+ deny insert
+ using (count(.author.posts) > 500);
}
.. _ref_eql_sdl_access_policies:
.. _ref_eql_sdl_access_policies_syntax:
Declaring access policies
=========================
This section describes the syntax to declare access policies in your schema.
Syntax
------
.. sdl:synopsis::
access policy <name>
[ when (<condition>) ]
{ allow | deny } <action> [, <action> ... ]
[ using (<expr>) ]
[ "{"
[ errmessage := value ; ]
[ <annotation-declarations> ]
"}" ] ;
# where <action> is one of
all
select
insert
delete
update [{ read | write }]
Where:
:eql:synopsis:`<name>`
The name of the access policy.
:eql:synopsis:`when (<condition>)`
Specifies which objects this policy applies to. The
:eql:synopsis:`<condition>` has to be a :eql:type:`bool` expression.
When omitted, it is assumed that this policy applies to all objects of a
given type.
:eql:synopsis:`allow`
Indicates that qualifying objects should allow access under this policy.
:eql:synopsis:`deny`
Indicates that qualifying objects should *not* allow access under this
policy. This flavor supersedes any :eql:synopsis:`allow` policy and can
be used to selectively deny access to a subset of objects that otherwise
explicitly allows accessing them.
:eql:synopsis:`all`
Apply the policy to all actions. It is exactly equivalent to listing
:eql:synopsis:`select`, :eql:synopsis:`insert`, :eql:synopsis:`delete`,
:eql:synopsis:`update` actions explicitly.
:eql:synopsis:`select`
Apply the policy to all selection queries. Note that any object that
cannot be selected, cannot be modified either. This makes
:eql:synopsis:`select` the most basic "visibility" policy.
:eql:synopsis:`insert`
Apply the policy to all inserted objects. If a newly inserted object would
violate this policy, an error is produced instead.
:eql:synopsis:`delete`
Apply the policy to all objects about to be deleted. If an object does not
allow access under this kind of policy, it is not going to be considered
by any :eql:stmt:`delete` command.
Note that any object that cannot be selected, cannot be modified either.
:eql:synopsis:`update read`
Apply the policy to all objects selected for an update. If an object does
not allow access under this kind of policy, it is not visible cannot be
updated.
Note that any object that cannot be selected, cannot be modified either.
:eql:synopsis:`update write`
Apply the policy to all objects at the end of an update. If an updated
object violates this policy, an error is produced instead.
Note that any object that cannot be selected, cannot be modified either.
:eql:synopsis:`update`
This is just a shorthand for :eql:synopsis:`update read` and
:eql:synopsis:`update write`.
Note that any object that cannot be selected, cannot be modified either.
:eql:synopsis:`using <expr>`
Specifies what the policy is with respect to a given eligible (based on
:eql:synopsis:`when` clause) object. The :eql:synopsis:`<expr>` has to be
a :eql:type:`bool` expression. The specific meaning of this value also
depends on whether this policy flavor is :eql:synopsis:`allow` or
:eql:synopsis:`deny`.
The expression must be :ref:`Stable <ref_reference_volatility>`.
When omitted, it is assumed that this policy applies to all eligible
objects of a given type.
:eql:synopsis:`set errmessage := <value>`
Set a custom error message of :eql:synopsis:`<value>` that is displayed
when this access policy prevents a write action.
:sdl:synopsis:`<annotation-declarations>`
Set access policy :ref:`annotation <ref_eql_sdl_annotations>`
to a given *value*.
Any sub-type extending a type inherits all of its access policies.
You can define additional access policies on sub-types.
.. _ref_eql_ddl_access_policies:
DDL commands
============
This section describes the low-level DDL commands for creating, altering, and
dropping access policies. You typically don't need to use these commands
directly, but knowing about them is useful for reviewing migrations.
Create access policy
--------------------
:eql-statement:
Define a new object access policy on a type:
.. eql:synopsis::
[ with <with-item> [, ...] ]
{ create | alter } type <TypeName> "{"
[ ... ]
create access policy <name>
[ when (<condition>) ; ]
{ allow | deny } action [, action ... ; ]
[ using (<expr>) ; ]
[ "{"
[ set errmessage := value ; ]
[ create annotation <annotation-name> := value ; ]
"}" ]
"}"
# where <action> is one of
all
select
insert
delete
update [{ read | write }]
See the meaning of each parameter in the `Declaring access policies`_ section.
The following subcommands are allowed in the ``create access policy`` block:
:eql:synopsis:`set errmessage := <value>`
Set a custom error message of :eql:synopsis:`<value>` that is displayed
when this access policy prevents a write action.
:eql:synopsis:`create annotation <annotation-name> := <value>`
Set access policy annotation :eql:synopsis:`<annotation-name>` to
:eql:synopsis:`<value>`.
See :eql:stmt:`create annotation` for details.
Alter access policy
-------------------
:eql-statement:
Modify an existing access policy:
.. eql:synopsis::
[ with <with-item> [, ...] ]
alter type <TypeName> "{"
[ ... ]
alter access policy <name> "{"
[ when (<condition>) ; ]
[ reset when ; ]
{ allow | deny } <action> [, <action> ... ; ]
[ using (<expr>) ; ]
[ set errmessage := value ; ]
[ reset expression ; ]
[ create annotation <annotation-name> := <value> ; ]
[ alter annotation <annotation-name> := <value> ; ]
[ drop annotation <annotation-name>; ]
"}"
"}"
You can change the policy's condition, actions, or error message, or add/drop
annotations.
The parameters describing the action policy are identical to the parameters
used by ``create action policy``. There are a handful of additional
subcommands that are allowed in the ``alter access policy`` block:
:eql:synopsis:`reset when`
Clear the :eql:synopsis:`when (<condition>)` so that the policy applies to
all objects of a given type. This is equivalent to ``when (true)``.
:eql:synopsis:`reset expression`
Clear the :eql:synopsis:`using (<condition>)` so that the policy always
passes. This is equivalent to ``using (true)``.
:eql:synopsis:`alter annotation <annotation-name>;`
Alter access policy annotation :eql:synopsis:`<annotation-name>`.
See :eql:stmt:`alter annotation` for details.
:eql:synopsis:`drop annotation <annotation-name>;`
Remove access policy annotation :eql:synopsis:`<annotation-name>`.
See :eql:stmt:`drop annotation` for details.
All the subcommands allowed in the ``create access policy`` block are also
valid subcommands for ``alter access policy`` block.
Drop access policy
------------------
:eql-statement:
Remove an existing policy:
.. eql:synopsis::
[ with <with-item> [, ...] ]
alter type <TypeName> "{"
[ ... ]
drop access policy <name> ;
"}"
================================================================================
.. File: aliases.rst
.. _ref_datamodel_aliases:
=======
Aliases
=======
.. index:: alias, virtual type
You can think of *aliases* as a way to give schema names to arbitrary EdgeQL
expressions. You can later refer to aliases in queries and in other aliases.
Aliases are functionally equivalent to expression aliases defined in EdgeQL
statements in :ref:`with block <ref_eql_statements_with>`, but are available
to all queries using the schema and can be introspected.
Like computed properties, the aliased expression is evaluated on the fly
whenever the alias is referenced.
Scalar alias
============
.. code-block:: sdl
# in your schema:
alias digits := {0,1,2,3,4,5,6,7,8,9};
Later, in some query:
.. code-block:: edgeql
select count(digits);
Object type alias
=================
The name of a given object type (e.g. ``User``) is itself a pointer to the *set
of all User objects*. After declaring the alias below, you can use ``User`` and
``UserAlias`` interchangeably:
.. code-block:: sdl
alias UserAlias := User;
Object type alias with computeds
================================
Object type aliases can include a *shape* that declares additional computed
properties or links:
.. code-block:: sdl
type Post {
required title: str;
}
alias PostWithTrimmedTitle := Post {
trimmed_title := str_trim(.title)
}
Later, in some query:
.. code-block:: edgeql
select PostWithTrimmedTitle {
trimmed_title
};
Arbitrary expressions
=====================
Aliases can correspond to any arbitrary EdgeQL expression, including entire
queries.
.. code-block:: sdl
# Tuple alias
alias Color := ("Purple", 128, 0, 128);
# Named tuple alias
alias GameInfo := (
name := "Li Europan Lingues",
country := "Iceland",
date_published := 2023,
creators := (
(name := "Bob Bobson", age := 20),
(name := "Trina Trinadóttir", age := 25),
),
);
type BlogPost {
required title: str;
required is_published: bool;
}
# Query alias
alias PublishedPosts := (
select BlogPost
filter .is_published = true
);
.. note::
All aliases are reflected in the database's built-in :ref:`GraphQL schema
<ref_graphql_index>`.
.. _ref_eql_sdl_aliases:
.. _ref_eql_sdl_aliases_syntax:
Defining aliases
================
Syntax
------
Define a new alias corresponding to the :ref:`more explicit DDL
commands <ref_eql_ddl_aliases>`.
.. sdl:synopsis::
alias <alias-name> := <alias-expr> ;
alias <alias-name> "{"
using <alias-expr>;
[ <annotation-declarations> ]
"}" ;
Where:
:eql:synopsis:`<alias-name>`
The name (optionally module-qualified) of an alias to be created.
:eql:synopsis:`<alias-expr>`
The aliased expression. Must be a :ref:`Stable <ref_reference_volatility>`
EdgeQL expression.
The valid SDL sub-declarations are listed below:
:sdl:synopsis:`<annotation-declarations>`
Set alias :ref:`annotation <ref_eql_sdl_annotations>`
to a given *value*.
.. _ref_eql_ddl_aliases:
DDL commands
============
This section describes the low-level DDL commands for creating and
dropping aliases. You typically don't need to use these commands
directly, but knowing about them is useful for reviewing migrations.
Create alias
------------
:eql-statement:
:eql-haswith:
Define a new alias in the schema.
.. eql:synopsis::
[ with <with-item> [, ...] ]
create alias <alias-name> := <alias-expr> ;
[ with <with-item> [, ...] ]
create alias <alias-name> "{"
using <alias-expr>;
[ create annotation <attr-name> := <attr-value>; ... ]
"}" ;
# where <with-item> is:
[ <module-alias> := ] module <module-name>
Parameters
^^^^^^^^^^
Most sub-commands and options of this command are identical to the
:ref:`SDL alias declaration <ref_eql_sdl_aliases_syntax>`, with some
additional features listed below:
:eql:synopsis:`[ <module-alias> := ] module <module-name>`
An optional list of module alias declarations to be used in the
alias definition.
:eql:synopsis:`create annotation <annotation-name> := <value>;`
An optional list of annotation values for the alias.
See :eql:stmt:`create annotation` for details.
Example
^^^^^^^
Create a new alias:
.. code-block:: edgeql
create alias Superusers := (
select User filter User.groups.name = 'Superusers'
);
Drop alias
----------
:eql-statement:
:eql-haswith:
Remove an alias from the schema.
.. eql:synopsis::
[ with <with-item> [, ...] ]
drop alias <alias-name> ;
Parameters
^^^^^^^^^^
*alias-name*
The name (optionally qualified with a module name) of an existing
expression alias.
Example
^^^^^^^
Remove an alias:
.. code-block:: edgeql
drop alias SuperUsers;
================================================================================
.. File: annotations.rst
.. _ref_datamodel_annotations:
.. _ref_eql_sdl_annotations:
===========
Annotations
===========
.. index:: annotation
*Annotations* are named values associated with schema items and are
designed to hold arbitrary schema-level metadata represented as a
:eql:type:`str` (unstructured text).
Users can store JSON-encoded data in annotations if they need to store
more complex metadata.
Standard annotations
====================
.. index:: title, description, deprecated
There are a number of annotations defined in the standard library. The
following are the annotations which can be set on any schema item:
- ``std::title``
- ``std::description``
- ``std::deprecated``
For example, consider the following declaration:
.. code-block:: sdl
type Status {
annotation title := 'Activity status';
annotation description := 'All possible user activities';
required name: str {
constraint exclusive
}
}
And the ``std::deprecated`` annotation can be used to mark deprecated items
(e.g., :eql:func:`str_rpad`) and to provide some information such as what
should be used instead.
User-defined annotations
========================
.. index:: abstract annotation
To declare a custom annotation type beyond the three built-ins, add an abstract
annotation type to your schema. A custom annotation could be used to attach
arbitrary JSON-encoded data to your schema—potentially useful for introspection
and code generation.
.. code-block:: sdl
abstract annotation admin_note;
type Status {
annotation admin_note := 'system-critical';
}
.. _ref_eql_sdl_annotations_syntax:
Declaring annotations
=====================
This section describes the syntax to use annotations in your schema.
Syntax
------
.. sdl:synopsis::
# Abstract annotation form:
abstract [ inheritable ] annotation <name>
[ "{" <annotation-declarations>; [...] "}" ] ;
# Concrete annotation (same as <annotation-declarations>) form:
annotation <name> := <value> ;
Description
^^^^^^^^^^^
There are two forms of annotation declarations: abstract and concrete.
The *abstract annotation* form is used for declaring new kinds of
annotation in a module. The *concrete annotation* declarations are
used as sub-declarations for all other declarations in order to
actually annotate them.
The annotation declaration options are as follows:
:eql:synopsis:`abstract`
If specified, the annotation will be *abstract*.
:eql:synopsis:`inheritable`
If specified, the annotation will be *inheritable*. The
annotations are non-inheritable by default. That is, if a schema
item has an annotation defined on it, the descendants of that
schema item will not automatically inherit the annotation. Normal
inheritance behavior can be turned on by declaring the annotation
with the ``inheritable`` qualifier. This is only valid for *abstract
annotation*.
:eql:synopsis:`<name>`
The name (optionally module-qualified) of the annotation.
:eql:synopsis:`<value>`
Any string value that the specified annotation is intended to have
for the given context.
The only valid SDL sub-declarations are *concrete annotations*:
:sdl:synopsis:`<annotation-declarations>`
Annotations can also have annotations. Set the *annotation* of the
enclosing annotation to a specific value.
.. _ref_eql_ddl_annotations:
DDL commands
============
This section describes the low-level DDL commands for creating, altering,
and dropping annotations and abstract annotations. You typically don't need to
use these commands directly, but knowing about them is useful for reviewing
migrations.
Create abstract annotation
--------------------------
:eql-statement:
Define a new annotation.
.. eql:synopsis::
[ with <with-item> [, ...] ]
create abstract [ inheritable ] annotation <name>
[
"{"
create annotation <annotation-name> := <value> ;
[...]
"}"
] ;
Description
^^^^^^^^^^^
The command ``create abstract annotation`` defines a new annotation
for use in the current Gel database.
If *name* is qualified with a module name, then the annotation is created
in that module, otherwise it is created in the current module.
The annotation name must be distinct from that of any existing schema item
in the module.
The annotations are non-inheritable by default. That is, if a schema item
has an annotation defined on it, the descendants of that schema item will
not automatically inherit the annotation. Normal inheritance behavior can
be turned on by declaring the annotation with the ``inheritable`` qualifier.
Most sub-commands and options of this command are identical to the
:ref:`SDL annotation declaration <ref_eql_sdl_annotations_syntax>`.
There's only one subcommand that is allowed in the ``create
annotation`` block:
:eql:synopsis:`create annotation <annotation-name> := <value>`
Annotations can also have annotations. Set the
:eql:synopsis:`<annotation-name>` of the
enclosing annotation to a specific :eql:synopsis:`<value>`.
See :eql:stmt:`create annotation` for details.
Example
^^^^^^^
Declare an annotation ``extrainfo``:
.. code-block:: edgeql
create abstract annotation extrainfo;
Alter abstract annotation
-------------------------
:eql-statement:
Change the definition of an annotation.
.. eql:synopsis::
alter abstract annotation <name>
[ "{" ] <subcommand>; [...] [ "}" ];
# where <subcommand> is one of
rename to <newname>
create annotation <annotation-name> := <value>
alter annotation <annotation-name> := <value>
drop annotation <annotation-name>
Description
^^^^^^^^^^^
:eql:synopsis:`alter abstract annotation` changes the definition of an
abstract annotation.
Parameters
^^^^^^^^^^
:eql:synopsis:`<name>`
The name (optionally module-qualified) of the annotation to alter.
The following subcommands are allowed in the ``alter abstract annotation``
block:
:eql:synopsis:`rename to <newname>`
Change the name of the annotation to :eql:synopsis:`<newname>`.
:eql:synopsis:`alter annotation <annotation-name> := <value>`
Annotations can also have annotations. Change
:eql:synopsis:`<annotation-name>` to a specific
:eql:synopsis:`<value>`. See :eql:stmt:`alter annotation` for
details.
:eql:synopsis:`drop annotation <annotation-name>`
Annotations can also have annotations. Remove annotation
:eql:synopsis:`<annotation-name>`.
See :eql:stmt:`drop annotation` for details.
All the subcommands allowed in the ``create abstract annotation``
block are also valid subcommands for ``alter annotation`` block.
Example
^^^^^^^
Rename an annotation:
.. code-block:: edgeql
alter abstract annotation extrainfo
rename to extra_info;
Drop abstract annotation
------------------------
:eql-statement:
Drop a schema annotation.
.. eql:synopsis::
[ with <with-item> [, ...] ]
drop abstract annotation <name> ;
Description
^^^^^^^^^^^
The command ``drop abstract annotation`` removes an existing schema
annotation from the database schema. Note that the ``inheritable``
qualifier is not necessary in this statement.
Example
^^^^^^^
Drop the annotation ``extra_info``:
.. code-block:: edgeql
drop abstract annotation extra_info;
Create annotation
-----------------
:eql-statement:
Define an annotation value for a given schema item.
.. eql:synopsis::
create annotation <annotation-name> := <value>
Description
^^^^^^^^^^^
The command ``create annotation`` defines an annotation for a schema item.
:eql:synopsis:`<annotation-name>` refers to the name of a defined annotation,
and :eql:synopsis:`<value>` must be a constant EdgeQL expression
evaluating into a string.
This statement can only be used as a subcommand in another
DDL statement.
Example
^^^^^^^
Create an object type ``User`` and set its ``title`` annotation to
``"User type"``.
.. code-block:: edgeql
create type User {
create annotation title := "User type";
};
Alter annotation
----------------
:eql-statement:
Alter an annotation value for a given schema item.
.. eql:synopsis::
alter annotation <annotation-name> := <value>
Description
^^^^^^^^^^^
The command ``alter annotation`` alters an annotation value on a schema item.
:eql:synopsis:`<annotation-name>` refers to the name of a defined annotation,
and :eql:synopsis:`<value>` must be a constant EdgeQL expression
evaluating into a string.
This statement can only be used as a subcommand in another
DDL statement.
Example
^^^^^^^
Alter an object type ``User`` and alter the value of its previously set
``title`` annotation to ``"User type"``.
.. code-block:: edgeql
alter type User {
alter annotation title := "User type";
};
Drop annotation
---------------
:eql-statement:
Remove an annotation from a given schema item.
.. eql:synopsis::
drop annotation <annotation-name> ;
Description
^^^^^^^^^^^
The command ``drop annotation`` removes an annotation value from a schema item.
:eql:synopsis:`<annotaion_name>` refers to the name of a defined annotation.
The annotation value does not have to exist on a schema item.
This statement can only be used as a subcommand in another
DDL statement.
Example
^^^^^^^
Drop the ``title`` annotation from the ``User`` object type:
.. code-block:: edgeql
alter type User {
drop annotation title;
};
.. list-table::
:class: seealso
* - **See also**
* - :ref:`Cheatsheets > Annotations <ref_cheatsheet_annotations>`
* - :ref:`Introspection > Object types <ref_datamodel_introspection_object_types>`
================================================================================
.. File: branches.rst
.. _ref_datamodel_branches:
.. _ref_datamodel_databases:
.. versionadded:: 5.0
========
Branches
========
Gel's |branches| are equivalent to PostgreSQL's *databases* and map to
them directly. Gel comes with tooling to help manage branches and build
a development workflow around them. E.g. when developing locally you can
map your Gel branches to your Git branches, and when using Gel Cloud and
GitHub you can have a branch per PR.
CLI commands
============
Refer to the :ref:`gel branch <ref_cli_gel_branch>` command group for
details on the CLI commands for managing branches.
.. _ref_admin_branches:
DDL commands
============
These are low-level commands that are used to create, alter, and drop branches.
You can use them when experimenting in REPL, of if you want to create your own
tools to manage Gel branches.
Create empty branch
-------------------
:eql-statement:
Create a new branch without schema or data.
.. eql:synopsis::
create empty branch <name> ;
Description
^^^^^^^^^^^
The command ``create empty branch`` creates a new Gel branch without schema
or data, aside from standard schemas.
Example
^^^^^^^
Create a new empty branch:
.. code-block:: edgeql
create empty branch newbranch;
Create schema branch
--------------------
:eql-statement:
Create a new branch copying the schema (without data)of an existing branch.
.. eql:synopsis::
create schema branch <newbranch> from <oldbranch> ;
Description
^^^^^^^^^^^
The command ``create schema branch`` creates a new Gel branch with schema
copied from an already existing branch.
Example
^^^^^^^
Create a new schema branch:
.. code-block:: edgeql
create schema branch feature from main;
Create data branch
------------------
:eql-statement:
Create a new branch copying the schema and data of an existing branch.
.. eql:synopsis::
create data branch <newbranch> from <oldbranch> ;
Description
^^^^^^^^^^^
The command ``create data branch`` creates a new Gel branch with schema and
data copied from an already existing branch.
Example
^^^^^^^
Create a new data branch:
.. code-block:: edgeql
create data branch feature from main;
Drop branch
-----------
:eql-statement:
Remove a branch.
.. eql:synopsis::
drop branch <name> ;
Description
^^^^^^^^^^^
The command ``drop branch`` removes an existing branch. It cannot be executed
while there are existing connections to the target branch.
.. warning::
Executing ``drop branch`` removes data permanently and cannot be undone.
Example
^^^^^^^
Remove a branch:
.. code-block:: edgeql
drop branch appdb;
Alter branch
------------
:eql-statement:
Rename a branch.
.. eql:synopsis::
alter branch <oldname> rename to <newname> ;
Description
^^^^^^^^^^^
The command ``alter branch … rename`` changes the name of an existing branch.
It cannot be executed while there are existing connections to the target
branch.
Example
^^^^^^^
Rename a branch:
.. code-block:: edgeql
alter branch featuer rename to feature;
================================================================================
.. File: comparison.rst
.. _ref_datamodel_comparison:
===============
vs SQL and ORMs
===============
|Gel's| approach to schema modeling builds upon the foundation of SQL while
taking cues from modern tools like ORM libraries. Let's see how it stacks up.
.. _ref_datamodel_sql_comparison:
Comparison to SQL
-----------------
When using SQL databases, there's no convenient representation of the schema.
Instead, the schema only exists as a series of ``{CREATE|ALTER|DELETE} {TABLE|
COLUMN}`` commands, usually spread across several SQL migration scripts.
There's no simple way to see the current state of your schema at a glance.
Moreover, SQL stores data in a *relational* way. Connections between tables are
represented with foreign key constraints and ``JOIN`` operations are required
to query across tables.
.. code-block::
CREATE TABLE people (
id uuid PRIMARY KEY,
name text,
);
CREATE TABLE movies (
id uuid PRIMARY KEY,
title text,
director_id uuid REFERENCES people(id)
);
In |Gel|, connections between tables are represented with :ref:`Links
<ref_datamodel_links>`.
.. code-block:: sdl
type Movie {
required title: str;
required director: Person;
}
type Person {
required name: str;
}
This approach makes it simple to write queries that traverse this link, no
JOINs required.
.. code-block:: edgeql
select Movie {
title,
director: {
name
}
}
.. _ref_datamodel_orm_comparison:
Comparison to ORMs
------------------
Object-relational mapping libraries are popular for a reason. They provide a
way to model your schema and write queries in a way that feels natural in the
context of modern, object-oriented programming languages. But ORMs have
downsides too.
- **Lock-in**. Your schema is strongly coupled to the ORM library you are
using. More generally, this also locks you into using a particular
programming language.
- Most ORMs have more **limited querying capabilities** than the query
languages they abstract.
- Many ORMs produce **suboptimal queries** that can have serious performance
implications.
- **Migrations** can be difficult. Since most ORMs aim to be the single source
of truth for your schema, they necessarily must provide some sort of
migration tool. These migration tools are maintained by the contributors to
the ORM library, not the maintainers of the database itself. Quality control
and long-term maintenance is not always guaranteed.
From the beginning, Gel was designed to incorporate the best aspects of ORMs
— declarative modeling, object-oriented APIs, and intuitive querying —
without the drawbacks.
================================================================================
.. File: computeds.rst
.. _ref_datamodel_computed:
=========
Computeds
=========
:edb-alt-title: Computed properties and links
.. index:: computeds, :=, __source__, backlinks
.. important::
This section assumes a basic understanding of EdgeQL. If you aren't familiar
with it, feel free to skip this page for now.
Object types can contain *computed* properties and links. Computed properties
and links are not persisted in the database. Instead, they are evaluated *on
the fly* whenever that field is queried. Computed properties must be declared
with the ``property`` keyword and computed links must be declared with the
``link`` keyword in |EdgeDB| versions prior to 4.0.
.. code-block:: sdl
type Person {
name: str;
all_caps_name := str_upper(__source__.name);
}
Computed fields are associated with an EdgeQL expression. This expression
can be an *arbitrary* EdgeQL query. This expression is evaluated whenever the
field is referenced in a query.
.. note::
Computed fields don't need to be pre-defined in your schema; you can drop
them into individual queries as well. They behave in exactly the same way.
For more information, see the :ref:`EdgeQL > Select > Computeds
<ref_eql_select_computeds>`.
.. warning::
:ref:`Volatile and modifying <ref_reference_volatility>` expressions are not
allowed in computed properties defined in schema. This means that, for
example, your schema-defined computed property cannot call
:eql:func:`datetime_current`, but it *can* call
:eql:func:`datetime_of_transaction` or :eql:func:`datetime_of_statement`.
This does *not* apply to computed properties outside of schema.
.. _ref_dot_notation:
Leading dot notation
--------------------
The example above used the special keyword ``__source__`` to refer to the
current object; it's analogous to ``this`` or ``self`` in many object-oriented
languages.
However, explicitly using ``__source__`` is optional here; inside the scope of
an object type declaration, you can omit it entirely and use the ``.<name>``
shorthand.
.. code-block:: sdl
type Person {
first_name: str;
last_name: str;
full_name := .first_name ++ ' ' ++ .last_name;
}
Type and cardinality inference
------------------------------
The type and cardinality of a computed field is *inferred* from the expression.
There's no need for the modifier keywords you use for non-computed fields (like
``multi`` and ``required``). However, it's common to specify them anyway; it
makes the schema more readable and acts as a sanity check: if the provided
EdgeQL expression disagrees with the modifiers, an error will be thrown the
next time you try to :ref:`create a migration <ref_intro_migrations>`.
.. code-block:: sdl
type Person {
first_name: str;
# this is invalid, because first_name is not a required property
required first_name_upper := str_upper(.first_name);
}
Common use cases
----------------
Filtering
^^^^^^^^^
If you find yourself writing the same ``filter`` expression repeatedly in
queries, consider defining a computed field that encapsulates the filter.
.. code-block:: sdl
type Club {
multi members: Person;
multi active_members := (
select .members filter .is_active = true
)
}
type Person {
name: str;
is_active: bool;
}
.. _ref_datamodel_links_backlinks:
Backlinks
^^^^^^^^^
Backlinks are one of the most common use cases for computed links. In |Gel|
links are *directional*; they have a source and a target. Often it's convenient
to traverse a link in the *reverse* direction.
.. code-block:: sdl
type BlogPost {
title: str;
author: User;
}
type User {
name: str;
multi blog_posts := .<author[is BlogPost]
}
The ``User.blog_posts`` expression above uses the *backlink operator* ``.<`` in
conjunction with a *type filter* ``[is BlogPost]`` to fetch all the
``BlogPosts`` associated with a given ``User``. For details on this syntax, see
the EdgeQL docs for :ref:`Backlinks <ref_eql_paths_backlinks>`.
.. list-table::
:class: seealso
* - :ref:`SDL > Links <ref_eql_sdl_links>`
* - :ref:`DDL > Links <ref_eql_ddl_links>`
* - :ref:`SDL > Properties <ref_eql_sdl_links>`
* - :ref:`DDL > Properties <ref_eql_ddl_links>`
================================================================================
.. File: constraints.rst
.. _ref_datamodel_constraints:
.. _ref_eql_sdl_constraints:
===========
Constraints
===========
.. index:: constraint, validation, exclusive, expression on, one_of, max_value,
max_ex_value, min_value, min_ex_value, max_len_value, min_len_value,
regexp, __subject__
Constraints give users fine-grained control to ensure data consistency.
They can be defined on :ref:`properties <ref_datamodel_props>`,
:ref:`links<ref_datamodel_links>`,
:ref:`object types <ref_datamodel_object_types>`,
and :ref:`custom scalars <ref_datamodel_links>`.
.. _ref_datamodel_constraints_builtin:
Standard constraints
====================
|Gel| includes a number of standard ready-to-use constraints:
.. include:: ../stdlib/constraint_table.rst
Constraints on properties
=========================
Example: enforce all ``User`` objects to have a unique ``username``
no longer than 25 characters:
.. code-block:: sdl
type User {
required username: str {
# usernames must be unique
constraint exclusive;
# max length (built-in)
constraint max_len_value(25);
};
}
.. _ref_datamodel_constraints_objects:
Constraints on object types
===========================
.. index:: __subject__
Constraints can be defined on object types. This is useful when the
constraint logic must reference multiple links or properties.
Example: enforce that the magnitude of ``ConstrainedVector`` objects
is no more than 5
.. code-block:: sdl
type ConstrainedVector {
required x: float64;
required y: float64;
constraint expression on (
(.x ^ 2 + .y ^ 2) ^ 0.5 <= 5
# or, long form: `(__subject__.x + __subject__.y) ^ 0.5 <= 5`
);
}
The ``expression`` constraint is used here to define custom constraint logic.
Inside constraints, the keyword ``__subject__`` can be used to reference the
*value* being constrained.
.. note::
Note that inside an object type declaration, you can omit ``__subject__``
and simply refer to properties with the
:ref:`leading dot notation <ref_dot_notation>` (e.g. ``.property``).
.. note::
Also note that the constraint expression are fairly restricted. Due
to how constraints are implemented, you can only reference ``single``
(non-multi) properties and links defined on the object type:
.. code-block:: sdl
# Not valid!
type User {
required username: str;
multi friends: User;
# ❌ constraints cannot contain paths with more than one hop
constraint expression on ('bob' in .friends.username);
}
Abstract constraints
====================
You can re-use constraints across multiple object types by declaring them as
abstract constraints. Example:
.. code-block:: sdl
abstract constraint min_value(min: anytype) {
errmessage :=
'Minimum allowed value for {__subject__} is {min}.';
using (__subject__ >= min);
}
# use it like this:
scalar type posint64 extending int64 {
constraint min_value(0);
}
# or like this:
type User {
required age: int16 {
constraint min_value(12);
};
}
Computed constraints
====================
Constraints can be defined on computed properties:
.. code-block:: sdl
type User {
required username: str;
required clean_username := str_trim(str_lower(.username));
constraint exclusive on (.clean_username);
}
Composite constraints
=====================
.. index:: tuple
To define a composite constraint, create an ``exclusive`` constraint on a
tuple of properties or links.
.. code-block:: sdl
type User {
username: str;
}
type BlogPost {
title: str;
author: User;
constraint exclusive on ((.title, .author));
}
.. _ref_datamodel_constraints_partial:
Partial constraints
===================
.. index:: constraint exclusive on, except
Constraints on object types can be made partial, so that they are not enforced
when the specified ``except`` condition is met.
.. code-block:: sdl
type User {
required username: str;
deleted: bool;
# Usernames must be unique unless marked deleted
constraint exclusive on (.username) except (.deleted);
}
Constraints on links
====================
You can constrain links such that a given object can only be linked once by
using :eql:constraint:`exclusive`:
.. code-block:: sdl
type User {
required name: str;
# Make sure none of the "owned" items belong
# to any other user.
multi owns: Item {
constraint exclusive;
}
}
Link property constraints
=========================
You can also add constraints for :ref:`link properties
<ref_datamodel_link_properties>`:
.. code-block:: sdl
type User {
name: str;
multi friends: User {
strength: float64;
constraint expression on (
@strength >= 0
);
}
}
Link's "@source" and "@target"
==============================
.. index:: constraint exclusive on, @source, @target
You can create a composite exclusive constraint on the object linking/linked
*and* a link property by using ``@source`` or ``@target`` respectively. Here's
a schema for a library book management app that tracks books and who has
checked them out:
.. code-block:: sdl
type Book {
required title: str;
}
type User {
name: str;
multi checked_out: Book {
date: cal::local_date;
# Ensures a given Book can be checked out
# only once on a given day.
constraint exclusive on ((@target, @date));
}
}
Here, the constraint ensures that no book can be checked out to two ``User``\s
on the same ``@date``.
In this example demonstrating ``@source``, we've created a schema to track
player picks in a color-based memory game:
.. code-block:: sdl
type Player {
required name: str;
multi picks: Color {
order: int16;
constraint exclusive on ((@source, @order));
}
}
type Color {
required name: str;
}
This constraint ensures that a single ``Player`` cannot pick two ``Color``\s at
the same ``@order``.
Constraints on custom scalars
=============================
Custom scalar types can be constrained.
.. code-block:: sdl
scalar type username extending str {
constraint regexp(r'^[A-Za-z0-9_]{4,20}$');
}
Note: you can't use :eql:constraint:`exclusive` constraints on custom scalar
types, as the concept of exclusivity is only defined in the context of a given
object type.
Use :eql:constraint:`expression` constraints to declare custom constraints
using arbitrary EdgeQL expressions. The example below uses the built-in
:eql:func:`str_trim` function.
.. code-block:: sdl
scalar type title extending str {
constraint expression on (
__subject__ = str_trim(__subject__)
);
}
Constraints and inheritance
===========================
.. index:: delegated constraint
If you define a constraint on a type and then extend that type, the constraint
will *not* be applied individually to each extending type. Instead, it will
apply globally across all the types that inherited the constraint.
.. code-block:: sdl
type User {
required name: str {
constraint exclusive;
}
}
type Administrator extending User;
type Moderator extending User;
.. code-block:: edgeql-repl
gel> insert Administrator {
.... name := 'Jan'
.... };
{default::Administrator {id: 7aeaa146-f5a5-11ed-a598-53ddff476532}}
gel> insert Moderator {
.... name := 'Jan'
.... };
gel error: ConstraintViolationError: name violates exclusivity constraint
Detail: value of property 'name' of object type 'default::Moderator'
violates exclusivity constraint
gel> insert User {
.... name := 'Jan'
.... };
gel error: ConstraintViolationError: name violates exclusivity constraint
Detail: value of property 'name' of object type 'default::User'
violates exclusivity constraint
As this example demonstrates, if an object of one extending type has a value
for a property that is exclusive, an object of a *different* extending type
cannot have the same value.
If that's not what you want, you can instead delegate the constraint to the
inheriting types by prepending the ``delegated`` keyword to the constraint.
The constraint would then be applied just as if it were declared individually
on each of the inheriting types.
.. code-block:: sdl
type User {
required name: str {
delegated constraint exclusive;
}
}
type Administrator extending User;
type Moderator extending User;
.. code-block:: edgeql-repl
gel> insert Administrator {
.... name := 'Jan'
.... };
{default::Administrator {id: 7aeaa146-f5a5-11ed-a598-53ddff476532}}
gel> insert User {
.... name := 'Jan'
.... };
{default::User {id: a6e3fdaf-c44b-4080-b39f-6a07496de66b}}
gel> insert Moderator {
.... name := 'Jan'
.... };
{default::Moderator {id: d3012a3f-0f16-40a8-8884-7203f393b63d}}
gel> insert Moderator {
.... name := 'Jan'
.... };
gel error: ConstraintViolationError: name violates exclusivity constraint
Detail: value of property 'name' of object type 'default::Moderator'
violates exclusivity constraint
With the addition of ``delegated`` to the constraints, the inserts were
successful for each of the types. We did not hit a constraint violation
until we tried to insert a second ``Moderator`` object with the same
name as the existing one.
.. _ref_eql_sdl_constraints_syntax:
Declaring constraints
=====================
This section describes the syntax to declare constraints in your schema.
Syntax
------
.. sdl:synopsis::
[{abstract | delegated}] constraint <name> [ ( [<argspec>] [, ...] ) ]
[ on ( <subject-expr> ) ]
[ except ( <except-expr> ) ]
[ extending <base> [, ...] ]
"{"
[ using <constr-expression> ; ]
[ errmessage := <error-message> ; ]
[ <annotation-declarations> ]
[ ... ]
"}" ;
# where <argspec> is:
[ <argname>: ] {<argtype> | <argvalue>}
Description
^^^^^^^^^^^
This declaration defines a new constraint with the following options:
:eql:synopsis:`abstract`
If specified, the constraint will be *abstract*.
:eql:synopsis:`delegated`
If specified, the constraint is defined as *delegated*, which means
that it will not be enforced on the type it's declared on, and the
enforcement will be delegated to the subtypes of this type.
This is particularly useful for :eql:constraint:`exclusive`
constraints in abstract types. This is only valid for *concrete
constraints*.
:eql:synopsis:`<name>`
The name (optionally module-qualified) of the new constraint.
:eql:synopsis:`<argspec>`
An optional list of constraint arguments.
For an *abstract constraint* :eql:synopsis:`<argname>` optionally
specifies the argument name and :eql:synopsis:`<argtype>` specifies
the argument type.
For a *concrete constraint* :eql:synopsis:`<argname>` optionally
specifies the argument name and :eql:synopsis:`<argvalue>` specifies
the argument value. The argument value specification must match the
parameter declaration of the abstract constraint.
:eql:synopsis:`on ( <subject-expr> )`
An optional expression defining the *subject* of the constraint.
If not specified, the subject is the value of the schema item on which
the concrete constraint is defined.
The expression must refer to the original subject of the constraint as
``__subject__``. The expression must be
:ref:`Immutable <ref_reference_volatility>`, but may refer to
``__subject__`` and its properties and links.
Note also that ``<subject-expr>`` itself has to
be parenthesized.
:eql:synopsis:`except ( <exception-expr> )`
An optional expression defining a condition to create exceptions to
the constraint. If ``<exception-expr>`` evaluates to ``true``,
the constraint is ignored for the current subject. If it evaluates
to ``false`` or ``{}``, the constraint applies normally.
``except`` may only be declared on object constraints, and otherwise
follows the same rules as ``on``.
:eql:synopsis:`extending <base> [, ...]`
If specified, declares the *parent* constraints for this abstract
constraint.
The valid SDL sub-declarations are listed below:
:eql:synopsis:`using <constr_expression>`
A boolean expression that returns ``true`` for valid data and
``false`` for invalid data. The expression may refer to the
subject of the constraint as ``__subject__``. This declaration is
only valid for *abstract constraints*.
:eql:synopsis:`errmessage := <error_message>`
An optional string literal defining the error message template
that is raised when the constraint is violated. The template is a
formatted string that may refer to constraint context variables in
curly braces. The template may refer to the following:
- ``$argname`` -- the value of the specified constraint argument
- ``__subject__`` -- the value of the ``title`` annotation of the
scalar type, property or link on which the constraint is defined.
If the content of curly braces does not match any variables,
the curly braces are emitted as-is. They can also be escaped by
using double curly braces.
:sdl:synopsis:`<annotation-declarations>`
Set constraint :ref:`annotation <ref_eql_sdl_annotations>`
to a given *value*.
.. _ref_eql_ddl_constraints:
DDL commands
============
This section describes the low-level DDL commands for creating and dropping
constraints and abstract constraints. You typically don't need to use these
commands directly, but knowing about them is useful for reviewing migrations.
Create abstract constraint
--------------------------
:eql-statement:
:eql-haswith:
Define a new abstract constraint.
.. eql:synopsis::
[ with [ <module-alias> := ] module <module-name> ]
create abstract constraint <name> [ ( [<argspec>] [, ...] ) ]
[ on ( <subject-expr> ) ]
[ extending <base> [, ...] ]
"{" <subcommand>; [...] "}" ;
# where <argspec> is:
[ <argname>: ] <argtype>
# where <subcommand> is one of
using <constr-expression>
set errmessage := <error-message>
create annotation <annotation-name> := <value>
Description
^^^^^^^^^^^
The command ``create abstract constraint`` defines a new abstract constraint.
If *name* is qualified with a module name, then the constraint is created in
that module, otherwise it is created in the current module. The constraint
name must be distinct from that of any existing schema item in the module.
Parameters
^^^^^^^^^^
Most sub-commands and options of this command are identical to the
:ref:`SDL constraint declaration <ref_eql_sdl_constraints_syntax>`,
with some additional features listed below:
:eql:synopsis:`[ <module-alias> := ] module <module-name>`
An optional list of module alias declarations to be used in the
migration definition. When *module-alias* is not specified,
*module-name* becomes the effective current module and is used
to resolve all unqualified names.
:eql:synopsis:`set errmessage := <error_message>`
An optional string literal defining the error message template
that is raised when the constraint is violated. Other than a
slight syntactical difference this is the same as the
corresponding SDL declaration.
:eql:synopsis:`create annotation <annotation-name> := <value>;`
Set constraint annotation ``<annotation-name>`` to ``<value>``.
See :eql:stmt:`create annotation` for details.
Example
^^^^^^^
Create an abstract constraint "uppercase" which checks if the subject
is a string in upper case:
.. code-block:: edgeql
create abstract constraint uppercase {
create annotation title := "Upper case constraint";
using (str_upper(__subject__) = __subject__);
set errmessage := "{__subject__} is not in upper case";
};
Alter abstract constraint
-------------------------
:eql-statement:
:eql-haswith:
Alter the definition of an abstract constraint.
.. eql:synopsis::
[ with [ <module-alias> := ] module <module-name> ]
alter abstract constraint <name>
"{" <subcommand>; [...] "}" ;
# where <subcommand> is one of
rename to <newname>
using <constr-expression>
set errmessage := <error-message>
reset errmessage
create annotation <annotation-name> := <value>
alter annotation <annotation-name> := <value>
drop annotation <annotation-name>
Description
^^^^^^^^^^^
The command ``alter abstract constraint`` changes the definition of an
abstract constraint item. *name* must be a name of an existing
abstract constraint, optionally qualified with a module name.
Parameters
^^^^^^^^^^
:eql:synopsis:`[ <module-alias> := ] module <module-name>`
An optional list of module alias declarations to be used in the
migration definition. When *module-alias* is not specified,
*module-name* becomes the effective current module and is used
to resolve all unqualified names.
:eql:synopsis:`<name>`
The name (optionally module-qualified) of the constraint to alter.
Subcommands allowed in the ``alter abstract constraint`` block:
:eql:synopsis:`rename to <newname>`
Change the name of the constraint to *newname*. All concrete
constraints inheriting from this constraint are also renamed.
:eql:synopsis:`alter annotation <annotation-name> := <value>`
Alter constraint annotation ``<annotation-name>``.
See :eql:stmt:`alter annotation` for details.
:eql:synopsis:`drop annotation <annotation-name>`
Remove annotation ``<annotation-name>``.
See :eql:stmt:`drop annotation` for details.
:eql:synopsis:`reset errmessage`
Remove the error message from this abstract constraint. The error message
specified in the base abstract constraint will be used instead.
All subcommands allowed in a ``create abstract constraint`` block are also
valid here.
Example
^^^^^^^
Rename the abstract constraint "uppercase" to "upper_case":
.. code-block:: edgeql
alter abstract constraint uppercase rename to upper_case;
Drop abstract constraint
------------------------
:eql-statement:
:eql-haswith:
Remove an abstract constraint from the schema.
.. eql:synopsis::
[ with [ <module-alias> := ] module <module-name> ]
drop abstract constraint <name> ;
Description
^^^^^^^^^^^
The command ``drop abstract constraint`` removes an existing abstract
constraint item from the database schema. If any schema items depending
on this constraint exist, the operation is refused.
Parameters
^^^^^^^^^^
:eql:synopsis:`[ <module-alias> := ] module <module-name>`
An optional list of module alias declarations to be used in the
migration definition.
:eql:synopsis:`<name>`
The name (optionally module-qualified) of the constraint to remove.
Example
^^^^^^^
Drop abstract constraint ``upper_case``:
.. code-block:: edgeql
drop abstract constraint upper_case;
Create constraint
-----------------
:eql-statement:
Define a concrete constraint on the specified schema item.
.. eql:synopsis::
[ with [ <module-alias> := ] module <module-name> ]
create [ delegated ] constraint <name>
[ ( [<argspec>] [, ...] ) ]
[ on ( <subject-expr> ) ]
[ except ( <except-expr> ) ]
"{" <subcommand>; [...] "}" ;
# where <argspec> is:
[ <argname>: ] <argvalue>
# where <subcommand> is one of
set errmessage := <error-message>
create annotation <annotation-name> := <value>
Description
^^^^^^^^^^^
The command ``create constraint`` defines a new concrete constraint. It can
only be used in the context of :eql:stmt:`create scalar`,
:eql:stmt:`alter scalar`, :eql:stmt:`create property`,
:eql:stmt:`alter property`, :eql:stmt:`create link`, or :eql:stmt:`alter link`.
*name* must be a name (optionally module-qualified) of a previously defined
abstract constraint.
Parameters
^^^^^^^^^^
Most sub-commands and options of this command are identical to the
:ref:`SDL constraint declaration <ref_eql_sdl_constraints_syntax>`,
with some additional features listed below:
:eql:synopsis:`[ <module-alias> := ] module <module-name>`
An optional list of module alias declarations to be used in the
migration definition.
:eql:synopsis:`set errmessage := <error_message>`
An optional string literal defining the error message template
that is raised when the constraint is violated. Other than a
slight syntactical difference, this is the same as the corresponding
SDL declaration.
:eql:synopsis:`create annotation <annotation-name> := <value>;`
An optional list of annotations for the constraint. See
:eql:stmt:`create annotation` for details.
Example
^^^^^^^
Create a "score" property on the "User" type with a minimum value
constraint:
.. code-block:: edgeql
alter type User create property score: int64 {
create constraint min_value(0)
};
Create a Vector with a maximum magnitude:
.. code-block:: edgeql
create type Vector {
create required property x: float64;
create required property y: float64;
create constraint expression ON (
__subject__.x^2 + __subject__.y^2 < 25
);
}
Alter constraint
----------------
:eql-statement:
Alter the definition of a concrete constraint on the specified schema item.
.. eql:synopsis::
[ with [ <module-alias> := ] module <module-name> [, ...] ]
alter constraint <name>
[ ( [<argspec>] [, ...] ) ]
[ on ( <subject-expr> ) ]
[ except ( <except-expr> ) ]
"{" <subcommand>; [ ... ] "}" ;
# -- or --
[ with [ <module-alias> := ] module <module-name> [, ...] ]
alter constraint <name>
[ ( [<argspec>] [, ...] ) ]
[ on ( <subject-expr> ) ]
<subcommand> ;
# where <subcommand> is one of:
set delegated
set not delegated
set errmessage := <error-message>
reset errmessage
create annotation <annotation-name> := <value>
alter annotation <annotation-name>
drop annotation <annotation-name>
Description
^^^^^^^^^^^
The command ``alter constraint`` changes the definition of a concrete
constraint. Both single- and multi-command forms are supported.
Parameters
^^^^^^^^^^
:eql:synopsis:`[ <module-alias> := ] module <module-name>`
An optional list of module alias declarations for the migration.
:eql:synopsis:`<name>`
The name (optionally module-qualified) of the concrete constraint
that is being altered.
:eql:synopsis:`<argspec>`
A list of constraint arguments as specified at the time of
``create constraint``.
:eql:synopsis:`on ( <subject-expr> )`
An expression defining the *subject* of the constraint as specified
at the time of ``create constraint``.
The following subcommands are allowed in the ``alter constraint`` block:
:eql:synopsis:`set delegated`
Mark the constraint as *delegated*, which means it will
not be enforced on the type it's declared on, and enforcement is
delegated to subtypes. Useful for :eql:constraint:`exclusive` constraints.
:eql:synopsis:`set not delegated`
Mark the constraint as *not delegated*, so it is enforced globally across
the type and any extending types.
:eql:synopsis:`rename to <newname>`
Change the name of the constraint to ``<newname>``.
:eql:synopsis:`alter annotation <annotation-name>`
Alter a constraint annotation.
:eql:synopsis:`drop annotation <annotation-name>`
Remove a constraint annotation.
:eql:synopsis:`reset errmessage`
Remove the error message from this constraint, reverting to that of the
abstract constraint, if any.
All subcommands allowed in ``create constraint`` are also valid in
``alter constraint``.
Example
^^^^^^^
Change the error message on the minimum value constraint on the property
"score" of the "User" type:
.. code-block:: edgeql
alter type User alter property score
alter constraint min_value(0)
set errmessage := 'Score cannot be negative';
Drop constraint
---------------
:eql-statement:
:eql-haswith:
Remove a concrete constraint from the specified schema item.
.. eql:synopsis::
[ with [ <module-alias> := ] module <module-name> [, ...] ]
drop constraint <name>
[ ( [<argspec>] [, ...] ) ]
[ on ( <subject-expr> ) ]
[ except ( <except-expr> ) ] ;
Description
^^^^^^^^^^^
The command ``drop constraint`` removes the specified constraint from
its containing schema item.
Parameters
^^^^^^^^^^
:eql:synopsis:`[ <module-alias> := ] module <module-name>`
Optional module alias declarations for the migration definition.
:eql:synopsis:`<name>`
The name (optionally module-qualified) of the concrete constraint
to remove.
:eql:synopsis:`<argspec>`
A list of constraint arguments as specified at the time of
``create constraint``.
:eql:synopsis:`on ( <subject-expr> )`
Expression defining the *subject* of the constraint as specified
at the time of ``create constraint``.
Example
^^^^^^^
Remove constraint "min_value" from the property "score" of the "User" type:
.. code-block:: edgeql
alter type User alter property score
drop constraint min_value(0);
.. list-table::
:class: seealso
* - **See also**
* - :ref:`Introspection > Constraints <ref_datamodel_introspection_constraints>`
* - :ref:`Standard Library > Constraints <ref_std_constraints>`
================================================================================
.. File: extensions.rst
.. _ref_datamodel_extensions:
==========
Extensions
==========
.. index:: using extension
Extensions are the way |Gel| can be extended with more functionality.
They can add new types, scalars, functions, etc., but, more
importantly, they can add new ways of interacting with the database.
Built-in extensions
===================
.. index:: edgeql_http, graphql, auth, ai, pg_trgm, pg_unaccent, pgcrypto,
pgvector
There are a few built-in extensions available:
- ``edgeql_http``: enables :ref:`EdgeQL over HTTP <ref_edgeql_http>`,
- ``graphql``: enables :ref:`GraphQL <ref_graphql_index>`,
- ``auth``: enables :ref:`Gel Auth <ref_guide_auth>`,
- ``ai``: enables :ref:`ext::ai module <ref_ai_extai_reference>`,
- ``pg_trgm``: enables ``ext::pg_trgm``, which re-exports
`pgtrgm <https://www.postgresql.org/docs/current/pgtrgm.html>`__,
- ``pg_unaccent``: enables ``ext::pg_unaccent``, which re-exports
`unaccent <https://www.postgresql.org/docs/current/unaccent.html>`__,
- ``pgcrypto``: enables ``ext::pgcrypto``, which re-exports
`pgcrypto <https://www.postgresql.org/docs/current/pgcrypto.html>`__,
- ``pgvector``: enables ``ext::pgvector``, which re-exports
`pgvector <https://github.com/pgvector/pgvector/>`__,
.. _ref_datamodel_using_extension:
To enable these extensions, add a ``using`` statement at the top level of
your schema:
.. code-block:: sdl
using extension auth;
# or / and
using extension ai;
Standalone extensions
=====================
.. index:: postgis
Additionally, standalone extension packages can be installed via the CLI,
with ``postgis`` being a notable example.
List installed extensions:
.. code-block:: bash
$ gel extension list
┌─────────┬─────────┐
│ Name │ Version │
└─────────┴─────────┘
List available extensions:
.. code-block:: bash
$ gel extension list-available
┌─────────┬───────────────┐
│ Name │ Version │
│ postgis │ 3.4.3+6b82d77 │
└─────────┴───────────────┘
Install the ``postgis`` extension:
.. code-block:: bash
$ gel extension install -E postgis
Found extension package: postgis version 3.4.3+6b82d77
00:00:03 [====================] 22.49 MiB/22.49 MiB
Extension 'postgis' installed successfully.
Check that extension is installed:
.. code-block:: bash
$ gel extension list
┌─────────┬───────────────┐
│ Name │ Version │
│ postgis │ 3.4.3+6b82d77 │
└─────────┴───────────────┘
After installing extensions, make sure to restart your instance:
.. code-block:: bash
$ gel instance restart
Standalone extensions can now be declared in the schema, same as
built-in extensions:
.. code-block:: sdl
using extension postgis;
.. note::
To restore a dump that uses a standalone extension, that extension must
be installed before the restore process.
.. _ref_eql_sdl_extensions:
Using extensions
================
Syntax
------
.. sdl:synopsis::
using extension <ExtensionName> ";"
Extension declaration must be outside any :ref:`module block
<ref_eql_sdl_modules>` since extensions affect the entire database and
not a specific module.
.. _ref_eql_ddl_extensions:
DDL commands
============
This section describes the low-level DDL commands for creating and
dropping extensions. You typically don't need to use these commands directly,
but knowing about them is useful for reviewing migrations.
create extension
----------------
:eql-statement:
Enable a particular extension for the current schema.
.. eql:synopsis::
create extension <ExtensionName> ";"
Description
^^^^^^^^^^^
The command ``create extension`` enables the specified extension for
the current :versionreplace:`database;5.0:branch`.
Examples
^^^^^^^^
Enable :ref:`GraphQL <ref_graphql_index>` extension for the current
schema:
.. code-block:: edgeql
create extension graphql;
Enable :ref:`EdgeQL over HTTP <ref_edgeql_http>` extension for the
current :versionreplace:`database;5.0:branch`:
.. code-block:: edgeql
create extension edgeql_http;
drop extension
--------------
:eql-statement:
Disable an extension.
.. eql:synopsis::
drop extension <ExtensionName> ";"
The command ``drop extension`` disables a currently active extension for
the current |branch|.
Examples
^^^^^^^^
Disable :ref:`GraphQL <ref_graphql_index>` extension for the current
schema:
.. code-block:: edgeql
drop extension graphql;
Disable :ref:`EdgeQL over HTTP <ref_edgeql_http>` extension for the
current :versionreplace:`database;5.0:branch`:
.. code-block:: edgeql
drop extension edgeql_http;
================================================================================
.. File: functions.rst
.. _ref_datamodel_functions:
.. _ref_eql_sdl_functions:
=========
Functions
=========
.. index:: function, using
.. note::
This page documents how to define custom functions, however |Gel| provides a
large library of built-in functions and operators. These are documented in
:ref:`Standard Library <ref_std>`.
User-defined Functions
======================
Gel allows you to define custom functions. For example, consider
a function that adds an exclamation mark ``'!'`` at the end of the
string:
.. code-block:: sdl
function exclamation(word: str) -> str
using (word ++ '!');
This function accepts a :eql:type:`str` as an argument and produces a
:eql:type:`str` as output as well.
.. code-block:: edgeql-repl
test> select exclamation({'Hello', 'World'});
{'Hello!', 'World!'}
.. _ref_datamodel_functions_modifying:
Sets as arguments
=================
Calling a user-defined function on a set will always apply it as
:ref:`*element-wise* <ref_reference_cardinality_functions_operators>`.
.. code-block:: sdl
function magnitude(x: float64) -> float64
using (
math::sqrt(sum(x * x))
);
.. code-block:: edgeql-repl
db> select magnitude({3, 4});
{3, 4}
In order to pass in multiple arguments at once, arguments should be packed into
arrays:
.. code-block:: sdl
function magnitude(xs: array<float64>) -> float64
using (
with x := array_unpack(xs)
select math::sqrt(sum(x * x))
);
.. code-block:: edgeql-repl
db> select magnitude([3, 4]);
{5}
Multiple packed arrays can be passed into such a function, which will then be
applied element-wise.
.. code-block:: edgeql-repl
db> select magnitude({[3, 4], [5, 12]});
{5, 13}
Modifying Functions
===================
.. versionadded:: 6.0
User-defined functions can contain DML (i.e.,
:ref:`insert <ref_eql_insert>`, :ref:`update <ref_eql_update>`,
:ref:`delete <ref_eql_delete>`) to make changes to existing data. These
functions have a :ref:`modifying <ref_reference_volatility>` volatility.
.. code-block:: sdl
function add_user(name: str) -> User
using (
insert User {
name := name,
joined_at := std::datetime_current(),
}
);
.. code-block:: edgeql-repl
db> select add_user('Jan') {name, joined_at};
{default::User {name: 'Jan', joined_at: <datetime>'2024-12-11T11:49:47Z'}}
Unlike other functions, the arguments of modifying functions **must** have a
:ref:`cardinality <ref_reference_cardinality>` of ``One``.
.. code-block:: edgeql-repl
db> select add_user({'Feb','Mar'});
gel error: QueryError: possibly more than one element passed into
modifying function
db> select add_user(<str>{});
gel error: QueryError: possibly an empty set passed as non-optional
argument into modifying function
Optional arguments can still accept empty sets. For example, if ``add_user``
was defined as:
.. code-block:: sdl
function add_user(name: str, joined_at: optional datetime) -> User
using (
insert User {
name := name,
joined_at := joined_at ?? std::datetime_current(),
}
);
then the following queries are valid:
.. code-block:: edgeql-repl
db> select add_user('Apr', <datetime>{}) {name, joined_at};
{default::User {name: 'Apr', joined_at: <datetime>'2024-12-11T11:50:51Z'}}
db> select add_user('May', <datetime>'2024-12-11T12:00:00-07:00') {name, joined_at};
{default::User {name: 'May', joined_at: <datetime>'2024-12-11T12:00:00Z'}}
In order to insert or update a multi parameter, the desired arguments should be
aggregated into an array as described above:
.. code-block:: sdl
function add_user(name: str, nicknames: array<str>) -> User
using (
insert User {
name := name,
nicknames := array_unpack(nicknames),
}
);
.. _ref_eql_sdl_functions_syntax:
Declaring functions
===================
This section describes the syntax to declare a function in your schema.
Syntax
------
.. sdl:synopsis::
function <name> ([ <argspec> ] [, ... ]) -> <returnspec>
using ( <edgeql> );
function <name> ([ <argspec> ] [, ... ]) -> <returnspec>
using <language> <functionbody> ;
function <name> ([ <argspec> ] [, ... ]) -> <returnspec>
"{"
[ <annotation-declarations> ]
[ volatility := {'Immutable' | 'Stable' | 'Volatile' | 'Modifying'} ]
[ using ( <expr> ) ; ]
[ using <language> <functionbody> ; ]
[ ... ]
"}" ;
# where <argspec> is:
[ <argkind> ] <argname>: [ <typequal> ] <argtype> [ = <default> ]
# <argkind> is:
[ { variadic | named only } ]
# <typequal> is:
[ { set of | optional } ]
# and <returnspec> is:
[ <typequal> ] <rettype>
Description
^^^^^^^^^^^
This declaration defines a new **function** with the following options:
:eql:synopsis:`<name>`
The name (optionally module-qualified) of the function to create.
:eql:synopsis:`<argkind>`
The kind of an argument: ``variadic`` or ``named only``.
If not specified, the argument is called *positional*.
The ``variadic`` modifier indicates that the function takes an
arbitrary number of arguments of the specified type. The passed
arguments will be passed as an array of the argument type.
Positional arguments cannot follow a ``variadic`` argument.
``variadic`` parameters cannot have a default value.
The ``named only`` modifier indicates that the argument can only
be passed using that specific name. Positional arguments cannot
follow a ``named only`` argument.
:eql:synopsis:`<argname>`
The name of an argument. If ``named only`` modifier is used this
argument *must* be passed using this name only.
.. _ref_sdl_function_typequal:
:eql:synopsis:`<typequal>`
The type qualifier: ``set of`` or ``optional``.
The ``set of`` qualifier indicates that the function is taking the
argument as a *whole set*, as opposed to being called on the input
product element-by-element.
User defined functions can not use ``set of`` arguments.
The ``optional`` qualifier indicates that the function will be called
if the argument is an empty set. The default behavior is to return
an empty set if the argument is not marked as ``optional``.
:eql:synopsis:`<argtype>`
The data type of the function's arguments
(optionally module-qualified).
:eql:synopsis:`<default>`
An expression to be used as default value if the parameter is not
specified. The expression has to be of a type compatible with the
type of the argument.
.. _ref_sdl_function_rettype:
:eql:synopsis:`<rettype>`
The return data type (optionally module-qualified).
The ``set of`` modifier indicates that the function will return
a non-singleton set.
The ``optional`` qualifier indicates that the function may return
an empty set.
The valid SDL sub-declarations are listed below:
:eql:synopsis:`volatility := {'Immutable' | 'Stable' | 'Volatile' | 'Modifying'}`
Function volatility determines how aggressively the compiler can
optimize its invocations.
If not explicitly specified the function volatility is
:ref:`inferred <ref_reference_volatility>` from the function body.
* An ``Immutable`` function cannot modify the database and is
guaranteed to return the same results given the same arguments
*in all statements*.
* A ``Stable`` function cannot modify the database and is
guaranteed to return the same results given the same
arguments *within a single statement*.
* A ``Volatile`` function cannot modify the database and can return
different results on successive calls with the same arguments.
* A ``Modifying`` function can modify the database and can return
different results on successive calls with the same arguments.
:eql:synopsis:`using ( <expr> )`
Specifies the body of the function. :eql:synopsis:`<expr>` is an
arbitrary EdgeQL expression.
:eql:synopsis:`using <language> <functionbody>`
A verbose version of the :eql:synopsis:`using` clause that allows
specifying the language of the function body.
* :eql:synopsis:`<language>` is the name of the language that
the function is implemented in. Currently can only be ``edgeql``.
* :eql:synopsis:`<functionbody>` is a string constant defining
the function. It is often helpful to use
:ref:`dollar quoting <ref_eql_lexical_dollar_quoting>`
to write the function definition string.
:sdl:synopsis:`<annotation-declarations>`
Set function :ref:`annotation <ref_eql_sdl_annotations>`
to a given *value*.
The function name must be distinct from that of any existing function
with the same argument types in the same module. Functions of
different argument types can share a name, in which case the functions
are called *overloaded functions*.
.. _ref_eql_ddl_functions:
DDL commands
============
This section describes the low-level DDL commands for creating, altering, and
dropping functions. You typically don't need to use these commands directly, but
knowing about them is useful for reviewing migrations.
Create function
---------------
:eql-statement:
:eql-haswith:
Define a new function.
.. eql:synopsis::
[ with <with-item> [, ...] ]
create function <name> ([ <argspec> ] [, ... ]) -> <returnspec>
using ( <expr> );
[ with <with-item> [, ...] ]
create function <name> ([ <argspec> ] [, ... ]) -> <returnspec>
using <language> <functionbody> ;
[ with <with-item> [, ...] ]
create function <name> ([ <argspec> ] [, ... ]) -> <returnspec>
"{" <subcommand> [, ...] "}" ;
# where <argspec> is:
[ <argkind> ] <argname>: [ <typequal> ] <argtype> [ = <default> ]
# <argkind> is:
[ { variadic | named only } ]
# <typequal> is:
[ { set of | optional } ]
# and <returnspec> is:
[ <typequal> ] <rettype>
# and <subcommand> is one of
set volatility := {'Immutable' | 'Stable' | 'Volatile' | 'Modifying'} ;
create annotation <annotation-name> := <value> ;
using ( <expr> ) ;
using <language> <functionbody> ;
Description
^^^^^^^^^^^
The command ``create function`` defines a new function. If *name* is
qualified with a module name, then the function is created in that
module, otherwise it is created in the current module.
The function name must be distinct from that of any existing function
with the same argument types in the same module. Functions of
different argument types can share a name, in which case the functions
are called *overloaded functions*.
Parameters
^^^^^^^^^^
Most sub-commands and options of this command are identical to the
:ref:`SDL function declaration <ref_eql_sdl_functions_syntax>`, with
some additional features listed below:
:eql:synopsis:`set volatility := {'Immutable' | 'Stable' | 'Volatile' | 'Modifying'}`
Function volatility determines how aggressively the compiler can
optimize its invocations. Other than a slight syntactical
difference this is the same as the corresponding SDL declaration.
:eql:synopsis:`create annotation <annotation-name> := <value>`
Set the function's :eql:synopsis:`<annotation-name>` to
:eql:synopsis:`<value>`.
See :eql:stmt:`create annotation` for details.
Examples
^^^^^^^^
Define a function returning the sum of its arguments:
.. code-block:: edgeql
create function mysum(a: int64, b: int64) -> int64
using (
select a + b
);
The same, but using a variadic argument and an explicit language:
.. code-block:: edgeql
create function mysum(variadic argv: int64) -> int64
using edgeql $$
select sum(array_unpack(argv))
$$;
Define a function using the block syntax:
.. code-block:: edgeql
create function mysum(a: int64, b: int64) -> int64 {
using (
select a + b
);
create annotation title := "My sum function.";
};
Alter function
--------------
:eql-statement:
:eql-haswith:
Change the definition of a function.
.. eql:synopsis::
[ with <with-item> [, ...] ]
alter function <name> ([ <argspec> ] [, ... ]) "{"
<subcommand> [, ...]
"}"
# where <argspec> is:
[ <argkind> ] <argname>: [ <typequal> ] <argtype> [ = <default> ]
# and <subcommand> is one of
set volatility := {'Immutable' | 'Stable' | 'Volatile' | 'Modifying'} ;
reset volatility ;
rename to <newname> ;
create annotation <annotation-name> := <value> ;
alter annotation <annotation-name> := <value> ;
drop annotation <annotation-name> ;
using ( <expr> ) ;
using <language> <functionbody> ;
Description
^^^^^^^^^^^
The command ``alter function`` changes the definition of a function.
The command allows changing annotations, the volatility level, and
other attributes.
Subcommands
^^^^^^^^^^^
The following subcommands are allowed in the ``alter function`` block
in addition to the commands common to the ``create function``:
:eql:synopsis:`reset volatility`
Remove explicitly specified volatility in favor of the volatility
inferred from the function body.
:eql:synopsis:`rename to <newname>`
Change the name of the function to *newname*.
:eql:synopsis:`alter annotation <annotation-name>;`
Alter function :eql:synopsis:`<annotation-name>`.
See :eql:stmt:`alter annotation` for details.
:eql:synopsis:`drop annotation <annotation-name>;`
Remove function :eql:synopsis:`<annotation-name>`.
See :eql:stmt:`drop annotation` for details.
:eql:synopsis:`reset errmessage;`
Remove the error message from this abstract constraint.
The error message specified in the base abstract constraint
will be used instead.
Example
^^^^^^^
.. code-block:: edgeql
create function mysum(a: int64, b: int64) -> int64 {
using (
select a + b
);
create annotation title := "My sum function.";
};
alter function mysum(a: int64, b: int64) {
set volatility := 'Immutable';
drop annotation title;
};
alter function mysum(a: int64, b: int64) {
using (
select (a + b) * 100
)
};
Drop function
-------------
:eql-statement:
:eql-haswith:
Remove a function.
.. eql:synopsis::
[ with <with-item> [, ...] ]
drop function <name> ([ <argspec> ] [, ... ]);
# where <argspec> is:
[ <argkind> ] <argname>: [ <typequal> ] <argtype> [ = <default> ]
Description
^^^^^^^^^^^
The command ``drop function`` removes the definition of an existing function.
The argument types to the function must be specified, since there
can be different functions with the same name.
Parameters
^^^^^^^^^^
:eql:synopsis:`<name>`
The name (optionally module-qualified) of an existing function.
:eql:synopsis:`<argname>`
The name of an argument used in the function definition.
:eql:synopsis:`<argmode>`
The mode of an argument: ``set of`` or ``optional`` or ``variadic``.
:eql:synopsis:`<argtype>`
The data type(s) of the function's arguments
(optionally module-qualified), if any.
Example
^^^^^^^
Remove the ``mysum`` function:
.. code-block:: edgeql
drop function mysum(a: int64, b: int64);
.. list-table::
:class: seealso
* - **See also**
* - :ref:`Reference > Function calls <ref_reference_function_call>`
* - :ref:`Introspection > Functions <ref_datamodel_introspection_functions>`
* - :ref:`Cheatsheets > Functions <ref_cheatsheet_functions>`
================================================================================
.. File: future.rst
.. _ref_datamodel_future:
===============
Future behavior
===============
.. index:: future, nonrecursive_access_policies
This article explains what the ``using future ...;`` statement means in your
schema.
Our goal is to make |Gel| the best database system in the world, which requires
us to keep evolving. Usually, we can add new functionality while preserving
backward compatibility, but on rare occasions we must implement changes that
require elaborate transitions.
To handle these cases, we introduce *future* behavior, which lets you try out
upcoming features before a major release. Sometimes enabling a future is
necessary to fix current issues; other times it offers a safe and easy way to
ensure your codebase remains compatible. This approach provides more time to
adopt a new feature and identify any resulting bugs.
Any time a behavior is available as a ``future,`` all new :ref:`projects
<ref_intro_projects>` enable it by default for empty databases. You can remove
a ``future`` from your schema if absolutely necessary, but doing so is
discouraged. Existing projects are unaffected by default, so you must manually
add the ``future`` specification to gain early access.
Flags
=====
At the moment there are three ``future`` flags available:
- ``simple_scoping``
Introduced in |Gel| 6.0, this flag simplifies the scoping rules for
path expressions. Read more about it and in great detail in
:ref:`ref_eql_path_resolution`.
- ``warn_old_scoping``
Introduced in |Gel| 6.0, this flag will emit a warning when a query
is detected to depend on the old scoping rules. This is an intermediate
step towards enabling the ``simple_scoping`` flag in existing large
codebases.
Read more about this flag in :ref:`ref_warn_old_scoping`.
.. _ref_datamodel_access_policies_nonrecursive:
.. _nonrecursive:
- ``nonrecursive_access_policies``: makes access policies non-recursive.
This flag is no longer used becauae the behavior is enabled
by default since |EdgeDB| 4. The flag was helpful to ease transition
from EdgeDB 3.x to 4.x.
Since |EdgeDB| 3.0, access policy restrictions do **not** apply
to any access policy expression. This means that when reasoning about access
policies it is no longer necessary to take other policies into account.
Instead, all data is visible for the purpose of *defining* an access
policy.
This change was made to simplify reasoning about access policies and
to allow certain patterns to be expressed efficiently. Since those who have
access to modifying the schema can remove unwanted access policies, no
additional security is provided by applying access policies to each
other's expressions.
.. _ref_eql_sdl_future:
Declaring future flags
======================
Syntax
------
Declare that the current schema enables a particular future behavior.
.. sdl:synopsis::
using future <FutureBehavior> ";"
Description
^^^^^^^^^^^
Future behavior declaration must be outside any :ref:`module block
<ref_eql_sdl_modules>` since this behavior affects the entire database and not
a specific module.
Example
^^^^^^^
.. code-block:: sdl-invalid
using future simple_scoping;
.. _ref_eql_ddl_future:
DDL commands
============
This section describes the low-level DDL commands for creating and
dropping future flags. You typically don't need to use these commands directly,
but knowing about them is useful for reviewing migrations.
Create future
-------------
:eql-statement:
Enable a particular future behavior for the current schema.
.. eql:synopsis::
create future <FutureBehavior> ";"
The command ``create future`` enables the specified future behavior for
the current branch.
Example
^^^^^^^
.. code-block:: edgeql
create future simple_scoping;
Drop future
-----------
:eql-statement:
Disable a particular future behavior for the current schema.
.. eql:synopsis::
drop future <FutureBehavior> ";"
Description
^^^^^^^^^^^
The command ``drop future`` disables a currently active future behavior for the
current branch. However, this is only possible for versions of |Gel| when the
behavior in question is not officially introduced. Once a particular behavior is
introduced as the standard behavior in a |Gel| release, it cannot be disabled.
Example
^^^^^^^
.. code-block:: edgeql
drop future warn_old_scoping;
================================================================================
.. File: globals.rst
.. _ref_datamodel_globals:
=======
Globals
=======
.. index:: global, required global
Schemas in Gel can contain typed *global variables*. These create a mechanism
for specifying session-level context that can be referenced in queries,
access policies, triggers, and elsewhere with the ``global`` keyword.
Here's a very common example of a global variable representing the current
user ID:
.. code-block:: sdl
global current_user_id: uuid;
.. tabs::
.. code-tab:: edgeql
select User {
id,
posts: { title, content }
}
filter .id = global current_user_id;
.. code-tab:: python
# In a non-trivial example, `global current_user_id` would
# be used indirectly in an access policy or some other context.
await client.with_globals({'user_id': user_id}).qeury('''
select User {
id,
posts: { title, content }
}
filter .id = global current_user_id;
''')
.. code-tab:: typescript
// In a non-trivial example, `global current_user_id` would
// be used indirectly in an access policy or some other context.
await client.withGlobals({user_id}).qeury('''
select User {
id,
posts: { title, content }
}
filter .id = global current_user_id;
''')
Setting global variables
========================
Global variables are set at session level or when initializing a client.
The exact API depends on which client library you're using, but the general
behavior and principles are the same across all libraries.
.. tabs::
.. code-tab:: typescript
import createClient from 'gel';
const baseClient = createClient();
// returns a new Client instance, that shares the underlying
// network connection with `baseClient` , but sends the configured
// globals along with all queries run through it:
const clientWithGlobals = baseClient.withGlobals({
current_user_id: '2141a5b4-5634-4ccc-b835-437863534c51',
});
const result = await clientWithGlobals.query(
`select global current_user_id;`
);
.. code-tab:: python
from gel import create_client
base_client = create_client()
# returns a new Client instance, that shares the underlying
# network connection with `base_client` , but sends the configured
# globals along with all queries run through it:
client = base_client.with_globals({
'current_user_id': '580cc652-8ab8-4a20-8db9-4c79a4b1fd81'
})
result = client.query("""
select global current_user_id;
""")
.. code-tab:: go
package main
import (
"context"
"fmt"
"log"
"github.com/geldata/gel-go"
)
func main() {
ctx := context.Background()
client, err := gel.CreateClient(ctx, gel.Options{})
if err != nil {
log.Fatal(err)
}
defer client.Close()
id, err := gel.ParseUUID("2141a5b4-5634-4ccc-b835-437863534c51")
if err != nil {
log.Fatal(err)
}
var result gel.UUID
err = client.
WithGlobals(map[string]interface{}{"current_user": id}).
QuerySingle(ctx, "SELECT global current_user;", &result)
if err != nil {
log.Fatal(err)
}
fmt.Println(result)
}
.. code-tab:: rust
use uuid::Uuid;
let client = gel_tokio::create_client().await.expect("Client init");
let client_with_globals = client.with_globals_fn(|c| {
c.set(
"current_user_id",
Value::Uuid(
Uuid::parse_str("2141a5b4-5634-4ccc-b835-437863534c51")
.expect("Uuid should have parsed"),
),
)
});
let val: Uuid = client_with_globals
.query_required_single("select global current_user_id;", &())
.await
.expect("Returning value");
println!("Result: {val}");
.. code-tab:: edgeql
set global current_user_id :=
<uuid>'2141a5b4-5634-4ccc-b835-437863534c51';
Cardinality
===========
A global variable can be declared with one of two cardinalities:
- ``single`` (the default): At most one value.
- ``multi``: A set of values. Only valid for computed global variables.
In addition, a global can be marked ``required`` or ``optional`` (the default).
If marked ``required``, a default value must be provided.
Computed globals
================
.. index:: global, :=
Global variables can also be computed. The value of computed globals is
dynamically computed when they are referenced in queries.
.. code-block:: sdl
required global now := datetime_of_transaction();
The provided expression will be computed at the start of each query in which
the global is referenced. There's no need to provide an explicit type; the type
is inferred from the computed expression.
Computed globals can also be object-typed and have ``multi`` cardinality.
For example:
.. code-block:: sdl
global current_user_id: uuid;
# object-typed global
global current_user := (
select User filter .id = global current_user_id
);
# multi global
global current_user_friends := (global current_user).friends;
Referencing globals
===================
Unlike query parameters, globals can be referenced *inside your schema
declarations*:
.. code-block:: sdl
type User {
name: str;
is_self := (.id = global current_user_id)
};
This is particularly useful when declaring :ref:`access policies
<ref_datamodel_access_policies>`:
.. code-block:: sdl
type Person {
required name: str;
access policy my_policy allow all
using (.id = global current_user_id);
}
Refer to :ref:`Access Policies <ref_datamodel_access_policies>` for complete
documentation.
.. _ref_eql_sdl_globals:
.. _ref_eql_sdl_globals_syntax:
Declaring globals
=================
This section describes the syntax to declare a global variable in your schema.
Syntax
------
Define a new global variable in SDL, corresponding to the more explicit DDL
commands described later:
.. sdl:synopsis::
# Global variable declaration:
[{required | optional}] [single]
global <name>: <type>
[ "{"
[ default := <expression> ; ]
[ <annotation-declarations> ]
...
"}" ]
# Computed global variable declaration:
[{required | optional}] [{single | multi}]
global <name> := <expression>;
Description
^^^^^^^^^^^
There are two different forms of ``global`` declarations, as shown in the
syntax synopsis above:
1. A *settable* global (defined with ``: <type>``) which can be changed using
a session-level :ref:`set <ref_eql_statements_session_set_alias>` command.
2. A *computed* global (defined with ``:= <expression>``), which cannot be
directly set but instead derives its value from the provided expression.
The following options are available:
:eql:synopsis:`required`
If specified, the global variable is considered *required*. It is an
error for this variable to have an empty value. If a global variable is
declared *required*, it must also declare a *default* value.
:eql:synopsis:`optional`
The global variable is considered *optional*, i.e. it is possible for the
variable to have an empty value. (This is the default.)
:eql:synopsis:`multi`
Specifies that the global variable may have a set of values. Only
*computed* global variables can have this qualifier.
:eql:synopsis:`single`
Specifies that the global variable must have at most a *single* value. It
is assumed that a global variable is ``single`` if neither ``multi`` nor
``single`` is specified. All non-computed global variables must be *single*.
:eql:synopsis:`<name>`
The name of the global variable. It can be fully-qualified with the module
name, or it is assumed to belong to the module in which it appears.
:eql:synopsis:`<type>`
The type must be a valid :ref:`type expression <ref_eql_types>` denoting a
non-abstract scalar or a container type.
:eql:synopsis:`<name> := <expression>`
Defines a *computed* global variable. The provided expression must be a
:ref:`Stable <ref_reference_volatility>` EdgeQL expression. It can refer
to other global variables. The type of a *computed* global variable is
not limited to scalar and container types; it can also be an object type.
The valid SDL sub-declarations are:
:eql:synopsis:`default := <expression>`
Specifies the default value for the global variable as an EdgeQL
expression. The default value is used in a session if the value was not
explicitly specified by the client, or was reset with the :ref:`reset
<ref_eql_statements_session_reset_alias>` command.
:sdl:synopsis:`<annotation-declarations>`
Set global variable :ref:`annotation <ref_eql_sdl_annotations>`
to a given *value*.
Examples
--------
Declare a new global variable:
.. code-block:: sdl
global current_user_id: uuid;
global current_user := (
select User filter .id = global current_user_id
);
Set the global variable to a specific value using :ref:`session-level commands
<ref_eql_statements_session_set_alias>`:
.. code-block:: edgeql
set global current_user_id :=
<uuid>'00ea8eaa-02f9-11ed-a676-6bd11cc6c557';
Use the computed global variable that is based on the value that was just set:
.. code-block:: edgeql
select global current_user { name };
:ref:`Reset <ref_eql_statements_session_reset_alias>` the global variable to
its default value:
.. code-block:: edgeql
reset global user_id;
.. _ref_eql_ddl_globals:
DDL commands
============
This section describes the low-level DDL commands for creating, altering, and
dropping globals. You typically don't need to use these commands directly, but
knowing about them is useful for reviewing migrations.
Create global
-------------
:eql-statement:
:eql-haswith:
Declare a new global variable using DDL.
.. eql:synopsis::
[ with <with-item> [, ...] ]
create [{required | optional}] [single]
global <name>: <type>
[ "{" <subcommand>; [...] "}" ] ;
# Computed global variable form:
[ with <with-item> [, ...] ]
create [{required | optional}] [{single | multi}]
global <name> := <expression>;
# where <subcommand> is one of
set default := <expression>
create annotation <annotation-name> := <value>
Description
^^^^^^^^^^^
As with SDL, there are two different forms of ``global`` declaration:
- A global variable that can be :ref:`set <ref_eql_statements_session_set_alias>`
in a session.
- A *computed* global that is derived from an expression (and so cannot be
directly set in a session).
The subcommands mirror those in SDL:
:eql:synopsis:`set default := <expression>`
Specifies the default value for the global variable as an EdgeQL
expression. The default value is used by the session if the value was not
explicitly specified or was reset with the :ref:`reset
<ref_eql_statements_session_reset_alias>` command.
:eql:synopsis:`create annotation <annotation-name> := <value>`
Assign an annotation to the global variable. See :eql:stmt:`create annotation`
for details.
Examples
^^^^^^^^
Define a new global property ``current_user_id``:
.. code-block:: edgeql
create global current_user_id: uuid;
Define a new *computed* global property ``current_user`` based on the
previously defined ``current_user_id``:
.. code-block:: edgeql
create global current_user := (
select User filter .id = global current_user_id
);
Alter global
------------
:eql-statement:
:eql-haswith:
Change the definition of a global variable.
.. eql:synopsis::
[ with <with-item> [, ...] ]
alter global <name>
[ "{" <subcommand>; [...] "}" ] ;
# where <subcommand> is one of
set default := <expression>
reset default
rename to <newname>
set required
set optional
reset optionalily
set single
set multi
reset cardinality
set type <typename> reset to default
using (<computed-expr>)
create annotation <annotation-name> := <value>
alter annotation <annotation-name> := <value>
drop annotation <annotation-name>
Description
^^^^^^^^^^^
The command :eql:synopsis:`alter global` changes the definition of a global
variable. It can modify default values, rename the global, or change other
attributes like optionality, cardinality, computed expressions, etc.
Examples
^^^^^^^^
Set the ``description`` annotation of global variable ``current_user``:
.. code-block:: edgeql
alter global current_user
create annotation description :=
'Current User as specified by the global ID';
Make the ``current_user_id`` global variable ``required``:
.. code-block:: edgeql
alter global current_user_id {
set required;
# A required global variable MUST have a default value.
set default := <uuid>'00ea8eaa-02f9-11ed-a676-6bd11cc6c557';
}
Drop global
-----------
:eql-statement:
:eql-haswith:
Remove a global variable from the schema.
.. eql:synopsis::
[ with <with-item> [, ...] ]
drop global <name> ;
Description
^^^^^^^^^^^
The command :eql:synopsis:`drop global` removes the specified global variable
from the schema.
Example
^^^^^^^
Remove the ``current_user`` global variable:
.. code-block:: edgeql
drop global current_user;
================================================================================
.. File: index.rst
.. versioned-section::
.. _ref_datamodel_index:
======
Schema
======
.. toctree::
:maxdepth: 3
:hidden:
objects
properties
links
computeds
primitives
indexes
constraints
inheritance
aliases
globals
access_policies
functions
triggers
mutation_rewrites
linkprops
modules
migrations
branches
extensions
annotations
future
comparison
introspection/index
|Gel| schema is a high-level description of your application's data model.
In the schema, you define your types, links, access policies, functions,
triggers, constraints, indexes, and more.
Gel schema is strictly typed and is high-level enough to be mapped directly
to mainstream programming languages and back.
.. _ref_eql_sdl:
Schema Definition Language
==========================
Migrations are sequences of *data definition language* (DDL) commands.
DDL is a low-level language that tells the database exactly how to change
the schema. You typically won't need to write any DDL by hand; the Gel server
will generate it for you.
For a full guide on migrations, refer to the :ref:`Creating and applying
migrations <ref_intro_migrations>` guide or the
:ref:`migrations reference <ref_datamodel_migrations>` section.
Example:
.. code-block:: sdl
# dbschema/default.gel
type Movie {
required title: str;
required director: Person;
}
type Person {
required name: str;
}
.. important::
Syntax highlighter packages/extensions for |.gel| files are available for
`Visual Studio Code <https://marketplace.visualstudio.com/
itemdetails?itemName=magicstack.edgedb>`_,
`Sublime Text <https://packagecontrol.io/packages/EdgeDB>`_,
`Atom <https://atom.io/packages/edgedb>`_, and `Vim <https://github.com/
geldata/edgedb-vim>`_.
Migrations and DDL
==================
Gel's baked-in migration system lets you painlessly evolve your schema over
time. Just update the contents of your |.gel| file(s) and use the |Gel| CLI
to *create* and *apply* migrations.
.. code-block:: bash
$ gel migration create
Created dbschema/migrations/00001.edgeql
$ gel migrate
Applied dbschema/migrations/00001.edgeql
Migrations are sequences of *data definition language* (DDL) commands.
DDL is a low level language that tells the database how exactly to change
the schema. Don't worry, you won't need to write any DDL directly, the Gel
server will generate it for you.
For a full guide on migrations, refer to the :ref:`Creating and applying
migrations <ref_intro_migrations>` guide or the
:ref:`migrations reference <ref_datamodel_migrations>` section.
.. _ref_datamodel_terminology:
.. _ref_datamodel_instances:
Instances, branches, and modules
================================
Gel is like a stack of containers:
* The *instance* is the running Gel process. Every instance has one or
more |branches|. Instances can be created, started, stopped, and
destroyed locally with :ref:`gel project <ref_cli_gel_project>`
or low-level :ref:`gel instance <ref_cli_gel_instance>` commands.
* A *branch* is where your schema and data live. Branches map to PostgreSQL
databases. Like instances, branches can be conveniently created, removed,
and switched with the :ref:`gel branch <ref_cli_gel_branch>` commands.
Read more about branches in the
:ref:`branches reference <ref_datamodel_branches>`.
* A *module* is a collection of types, functions, and other definitions.
The default module is called ``default``. Modules are used to organize
your schema logically. Read more about modules in the
:ref:`modules reference <ref_datamodel_modules>`.
================================================================================
.. File: indexes.rst
.. _ref_datamodel_indexes:
=======
Indexes
=======
.. index::
index on, performance, postgres query planner
An index is a data structure used internally to speed up filtering, ordering,
and grouping operations in |Gel|. Indexes help accomplish this in two key ways:
- They are pre-sorted, which saves time on costly sort operations on rows.
- They can be used by the query planner to filter out irrelevant rows.
.. note::
The Postgres query planner decides when to use indexes for a query. In some
cases—e.g. when tables are small—it may be faster to scan the whole table
rather than use an index. In such scenarios, the index might be ignored.
For more information on how the planner decides this, see
`the Postgres query planner documentation
<https://www.postgresql.org/docs/current/planner-optimizer.html>`_.
Tradeoffs
=========
While improving query performance, indexes also increase disk and memory usage
and can slow down insertions and updates. Creating too many indexes may be
detrimental; only index properties you often filter, order, or group by.
.. important::
**Foreign and primary keys**
In SQL databases, indexes are commonly used to index *primary keys* and
*foreign keys*. Gel's analog to a SQL primary key is the ``id`` field
automatically created for each object, while a link in Gel is the analog
to a SQL foreign key. Both of these are automatically indexed.
Moreover, any property with an :eql:constraint:`exclusive` constraint
is also automatically indexed.
Index on a property
===================
Most commonly, indexes are declared within object type declarations and
reference a particular property. The index can be used to speed up queries
that reference that property in a filter, order by, or group by clause:
.. code-block:: sdl
type User {
required name: str;
index on (.name);
}
By indexing on ``User.name``, the query planner will have access to that index
when planning queries using the ``name`` property. This may result in better
performance as the database can look up a name in the index instead of scanning
through all ``User`` objects sequentially—though ultimately it's up to the
Postgres query planner whether to use the index.
To see if an index helps, compare query plans by adding
:ref:`analyze <ref_cli_gel_analyze>` to your queries.
.. note::
Even if your database is small now, you may benefit from an index as it grows.
Index on an expression
======================
Indexes may be defined using an arbitrary *singleton* expression that
references multiple properties of the enclosing object type.
.. important::
A singleton expression is an expression that's guaranteed to return
*at most one* element. As such, you can't index on a ``multi`` property.
Example:
.. code-block:: sdl
type User {
required first_name: str;
required last_name: str;
index on (str_lower(.first_name + ' ' + .last_name));
}
Index on multiple properties
============================
.. index:: tuple
A *composite index* references multiple properties. This can speed up queries
that filter, order, or group on multiple properties at once.
.. note::
An index on multiple properties may also be used in queries where only a
single property in the index is referenced. In many traditional database
systems, placing the most frequently used columns first in the composite
index can improve the likelihood of its use.
Read `the Postgres documentation on multicolumn indexes
<https://www.postgresql.org/docs/current/indexes-multicolumn.html>`_ to learn
more about how the query planner uses these indexes.
In |Gel|, a composite index is created by indexing on a ``tuple`` of properties:
.. code-block:: sdl
type User {
required name: str;
required email: str;
index on ((.name, .email));
}
Index on a link property
========================
.. index:: __subject__, linkprops
Link properties can also be indexed. The special placeholder
``__subject__`` refers to the source object in a link property expression:
.. code-block:: sdl
abstract link friendship {
strength: float64;
index on (__subject__@strength);
}
type User {
multi friends: User {
extending friendship;
};
}
Specify a Postgres index type
=============================
.. index:: pg::hash, pg::btree, pg::gin, pg::gist, pg::spgist, pg::brin
.. versionadded:: 3.0
Gel exposes Postgres index types that can be used directly in schemas via
the ``pg`` module:
- ``pg::hash`` : Index based on a 32-bit hash of the value
- ``pg::btree`` : B-tree index (can help with sorted data retrieval)
- ``pg::gin`` : Inverted index for multi-element data (arrays, JSON)
- ``pg::gist`` : Generalized Search Tree for range and geometric searches
- ``pg::spgist`` : Space-partitioned GiST
- ``pg::brin`` : Block Range INdex
Example:
.. code-block:: sdl
type User {
required name: str;
index pg::spgist on (.name);
}
Annotate an index
=================
.. index:: annotation
Indexes can include annotations:
.. code-block:: sdl
type User {
name: str;
index on (.name) {
annotation description := 'Indexing all users by name.';
};
}
.. _ref_eql_sdl_indexes:
Declaring indexes
=================
This section describes the syntax to use indexes in your schema.
Syntax
------
.. sdl:synopsis::
index on ( <index-expr> )
[ except ( <except-expr> ) ]
[ "{" <annotation-declarations> "}" ] ;
.. rubric:: Description
- :sdl:synopsis:`on ( <index-expr> )`
The expression to index. It must be :ref:`Immutable <ref_reference_volatility>`
but may refer to the indexed object's properties/links. The expression itself
must be parenthesized.
- :eql:synopsis:`except ( <except-expr> )`
An optional condition. If ``<except-expr>`` evaluates to ``true``, the object
is omitted from the index; if ``false`` or empty, it is included.
- :sdl:synopsis:`<annotation-declarations>`
Allows setting index :ref:`annotation <ref_eql_sdl_annotations>` to a given
value.
.. _ref_eql_ddl_indexes:
DDL commands
============
This section describes the low-level DDL commands for creating, altering, and
dropping indexes. You typically don't need to use these commands directly, but
knowing about them is useful for reviewing migrations.
Create index
------------
:eql-statement:
.. eql:synopsis::
create index on ( <index-expr> )
[ except ( <except-expr> ) ]
[ "{" <subcommand>; [...] "}" ] ;
# where <subcommand> is one of
create annotation <annotation-name> := <value>
Creates a new index for a given object type or link using *index-expr*.
- Most parameters/options match those in
:ref:`Declaring indexes <ref_eql_sdl_indexes>`.
- Allowed subcommand:
:eql:synopsis:`create annotation <annotation-name> := <value>`
Assign an annotation to this index.
See :eql:stmt:`create annotation` for details.
Example:
.. code-block:: edgeql
create type User {
create property name: str {
set default := '';
};
create index on (.name);
};
Alter index
-----------
:eql-statement:
Alter the definition of an index.
.. eql:synopsis::
alter index on ( <index-expr> ) [ except ( <except-expr> ) ]
[ "{" <subcommand>; [...] "}" ] ;
# where <subcommand> is one of
create annotation <annotation-name> := <value>
alter annotation <annotation-name> := <value>
drop annotation <annotation-name>
The command ``alter index`` is used to change the :ref:`annotations
<ref_datamodel_annotations>` of an index. The *index-expr* is used to
identify the index to be altered.
:sdl:synopsis:`on ( <index-expr> )`
The specific expression for which the index is made. Note also
that ``<index-expr>`` itself has to be parenthesized.
The following subcommands are allowed in the ``alter index`` block:
:eql:synopsis:`create annotation <annotation-name> := <value>`
Set index :eql:synopsis:`<annotation-name>` to
:eql:synopsis:`<value>`.
See :eql:stmt:`create annotation` for details.
:eql:synopsis:`alter annotation <annotation-name>;`
Alter index :eql:synopsis:`<annotation-name>`.
See :eql:stmt:`alter annotation` for details.
:eql:synopsis:`drop annotation <annotation-name>;`
Remove constraint :eql:synopsis:`<annotation-name>`.
See :eql:stmt:`drop annotation` for details.
Example:
.. code-block:: edgeql
alter type User {
alter index on (.name) {
create annotation title := 'User name index';
};
};
Drop index
----------
:eql-statement:
Remove an index from a given schema item.
.. eql:synopsis::
drop index on ( <index-expr> ) [ except ( <except-expr> ) ] ;
Removes an index from a schema item.
- :sdl:synopsis:`on ( <index-expr> )` identifies the indexed expression.
This statement can only be used as a subdefinition in another DDL statement.
Example:
.. code-block:: edgeql
alter type User {
drop index on (.name);
};
.. list-table::
:class: seealso
* - **See also**
- :ref:`Introspection > Indexes <ref_datamodel_introspection_indexes>`
================================================================================
.. File: inheritance.rst
.. _ref_datamodel_inheritance:
===========
Inheritance
===========
.. index:: abstract, extending, extends, subtype, supertype, parent type,
child type
Inheritance is a crucial aspect of schema modeling in Gel. Schema items can
*extend* one or more parent types. When extending, the child (subclass)
inherits the definition of its parents (superclass).
You can declare ``abstract`` object types, properties, links, constraints, and
annotations.
- :ref:`Objects <ref_datamodel_inheritance_objects>`
- :ref:`Properties <ref_datamodel_inheritance_props>`
- :ref:`Links <ref_datamodel_inheritance_links>`
- :ref:`Constraints <ref_datamodel_inheritance_constraints>`
- :ref:`Annotations <ref_datamodel_inheritance_annotations>`
.. _ref_datamodel_inheritance_objects:
Object types
------------
Object types can *extend* other object types. The extending type (AKA the
*subtype*) inherits all links, properties, indexes, constraints, etc. from its
*supertypes*.
.. code-block:: sdl
abstract type Animal {
species: str;
}
type Dog extending Animal {
breed: str;
}
Both abstract and concrete object types can be extended. Whether to make a
type abstract or concrete is a fairly simple decision: if you need to be
able to insert objects of the type, make it a concrete type. If objects of
the type should never be inserted and it exists only to be extended, make it
an abstract one. In the schema below the ``Animal`` type is now concrete
and can be inserted, which was not the case in the example above. The new
``CanBark`` type however is abstract and thus the database will not have
any individual ``CanBark`` objects.
.. code-block:: sdl
abstract type CanBark {
required bark_sound: str;
}
type Animal {
species: str;
}
type Dog extending Animal, CanBark {
breed: str;
}
For details on querying polymorphic data, see :ref:`EdgeQL > Select >
Polymorphic queries <ref_eql_select_polymorphic>`.
.. _ref_datamodel_inheritance_multiple:
Multiple Inheritance
^^^^^^^^^^^^^^^^^^^^
.. index:: Multiple Inheritance
Object types can :ref:`extend more
than one type <ref_eql_sdl_object_types_inheritance>` — that's called
*multiple inheritance*. This mechanism allows building complex object
types out of combinations of more basic types.
.. code-block:: sdl
abstract type HasName {
first_name: str;
last_name: str;
}
abstract type HasEmail {
email: str;
}
type Person extending HasName, HasEmail {
profession: str;
}
.. _ref_datamodel_overloading:
Overloading
^^^^^^^^^^^
.. index:: overloaded
An object type can overload an inherited property or link. All overloaded
declarations must be prefixed with the ``overloaded`` prefix to avoid
unintentional overloads.
.. code-block:: sdl
abstract type Person {
name: str;
multi friends: Person;
}
type Student extending Person {
overloaded name: str {
constraint exclusive;
}
overloaded multi friends: Student;
}
Overloaded fields cannot *generalize* the associated type; it can only make it
*more specific* by setting the type to a subtype of the original or adding
additional constraints.
.. _ref_datamodel_inheritance_props:
Properties
----------
Properties can be *concrete* (the default) or *abstract*. Abstract properties
are declared independent of a source or target, can contain :ref:`annotations
<ref_datamodel_annotations>`, and can be marked as ``readonly``.
.. code-block:: sdl
abstract property title_prop {
annotation title := 'A title.';
readonly := false;
}
.. _ref_datamodel_inheritance_links:
Links
-----
It's possible to define ``abstract`` links that aren't tied to a particular
*source* or *target*. Abstract links can be marked as readonly and contain
annotations, property declarations, constraints, and indexes.
.. code-block:: sdl
abstract link link_with_strength {
strength: float64;
index on (__subject__@strength);
}
type Person {
multi friends: Person {
extending link_with_strength;
};
}
.. _ref_datamodel_inheritance_constraints:
Constraints
-----------
Use ``abstract`` to declare reusable, user-defined constraint types.
.. code-block:: sdl
abstract constraint in_range(min: anyreal, max: anyreal) {
errmessage :=
'Value must be in range [{min}, {max}].';
using (min <= __subject__ and __subject__ < max);
}
type Player {
points: int64 {
constraint in_range(0, 100);
}
}
.. _ref_datamodel_inheritance_annotations:
Annotations
-----------
EdgeQL supports three annotation types by default: ``title``, ``description``,
and ``deprecated``. Use ``abstract annotation`` to declare custom user-defined
annotation types.
.. code-block:: sdl
abstract annotation admin_note;
type Status {
annotation admin_note := 'system-critical';
# more properties
}
By default, annotations defined on abstract types, properties, and links will
not be inherited by their subtypes. To override this behavior, use the
``inheritable`` modifier.
.. code-block:: sdl
abstract inheritable annotation admin_note;
================================================================================
.. File: casts.rst
.. _ref_datamodel_introspection_casts:
=====
Casts
=====
This section describes introspection of Gel :eql:op:`type casts
<cast>`. Features like whether the casts are implicit can be
discovered by introspecting ``schema::Cast``.
Introspection of the ``schema::Cast``:
.. code-block:: edgeql-repl
db> with module schema
... select ObjectType {
... name,
... links: {
... name,
... },
... properties: {
... name,
... }
... }
... filter .name = 'schema::Cast';
{
Object {
name: 'schema::Cast',
links: {
Object { name: '__type__' },
Object { name: 'from_type' },
Object { name: 'to_type' }
},
properties: {
Object { name: 'allow_assignment' },
Object { name: 'allow_implicit' },
Object { name: 'id' },
Object { name: 'name' }
}
}
}
Introspection of the possible casts from ``std::int64`` to other
types:
.. code-block:: edgeql-repl
db> with module schema
... select Cast {
... allow_assignment,
... allow_implicit,
... to_type: { name },
... }
... filter .from_type.name = 'std::int64'
... order by .to_type.name;
{
Object {
allow_assignment: false,
allow_implicit: true,
to_type: Object { name: 'std::bigint' }
},
Object {
allow_assignment: false,
allow_implicit: true,
to_type: Object { name: 'std::decimal' }
},
Object {
allow_assignment: true,
allow_implicit: false,
to_type: Object { name: 'std::float32' }
},
Object {
allow_assignment: false,
allow_implicit: true,
to_type: Object { name: 'std::float64' }
},
Object {
allow_assignment: true,
allow_implicit: false,
to_type: Object { name: 'std::int16' }
},
Object {
allow_assignment: true,
allow_implicit: false,
to_type: Object { name: 'std::int32' }
},
Object {
allow_assignment: false,
allow_implicit: false,
to_type: Object { name: 'std::json' }
},
Object {
allow_assignment: false,
allow_implicit: false,
to_type: Object { name: 'std::str' }
}
}
The ``allow_implicit`` property tells whether this is an *implicit cast*
in all contexts (such as when determining the type of a set of mixed
literals or resolving the argument types of functions or operators if
there's no exact match). For example, a literal ``1`` is an
:eql:type:`int64` and it is implicitly cast into a :eql:type:`bigint`
or :eql:type:`float64` if it is added to a set containing either one
of those types:
.. code-block:: edgeql-repl
db> select {1, 2n};
{1n, 2n}
db> select {1, 2.0};
{1.0, 2.0}
What happens if there's no implicit cast between a couple of scalars
in this type of example? Gel checks whether there's a scalar type
such that all of the set elements can be implicitly cast into that:
.. code-block:: edgeql-repl
db> select introspect (typeof {<int64>1, <float32>2}).name;
{'std::float64'}
The scalar types :eql:type:`int64` and :eql:type:`float32` cannot be
implicitly cast into each other, but they both can be implicitly cast
into :eql:type:`float64`.
The ``allow_assignment`` property tells whether this is an implicit
cast during assignment if a more general *implicit cast* is not
allowed. For example, consider the following type:
.. code-block:: sdl
type Example {
property p_int16: int16;
property p_float32: float32;
property p_json: json;
}
.. code-block:: edgeql-repl
db> insert Example {
... p_int16 := 1,
... p_float32 := 2
... };
{Object { id: <uuid>'...' }}
db> insert Example {
... p_json := 3 # assignment cast to json not allowed
... };
InvalidPropertyTargetError: invalid target for property
'p_json' of object type 'default::Example': 'std::int64'
(expecting 'std::json')
================================================================================
.. File: colltypes.rst
.. _ref_datamodel_introspection_collection_types:
================
Collection types
================
This section describes introspection of :ref:`collection types
<ref_datamodel_collection_types>`.
Array
-----
Introspection of the ``schema::Array``:
.. code-block:: edgeql-repl
db> with module schema
... select ObjectType {
... name,
... links: {
... name,
... },
... properties: {
... name,
... }
... }
... filter .name = 'schema::Array';
{
Object {
name: 'schema::Array',
links: {
Object { name: '__type__' },
Object { name: 'element_type' }
},
properties: {
Object { name: 'id' },
Object { name: 'name' }
}
}
}
For a type with an :eql:type:`array` property, consider the following:
.. code-block:: sdl
type User {
required property name: str;
property favorites: array<str>;
}
Introspection of the ``User`` with emphasis on properties:
.. code-block:: edgeql-repl
db> with module schema
... select ObjectType {
... name,
... properties: {
... name,
... target: {
... name,
... [is Array].element_type: { name },
... },
... },
... }
... filter .name = 'default::User';
{
Object {
name: 'default::User',
properties: {
Object {
name: 'favorites',
target: Object {
name: 'array',
element_type: Object { name: 'std::str' }
}
},
...
}
}
}
Tuple
-----
Introspection of the ``schema::Tuple``:
.. code-block:: edgeql-repl
db> with module schema
... select ObjectType {
... name,
... links: {
... name,
... },
... properties: {
... name,
... }
... }
... filter .name = 'schema::Tuple';
{
Object {
name: 'schema::Tuple',
links: {
Object { name: '__type__' },
Object { name: 'element_types' }
},
properties: {
Object { name: 'id' },
Object { name: 'name' }
}
}
}
For example, below is an introspection of the return type of
the :eql:func:`sys::get_version` function:
.. code-block:: edgeql-repl
db> with module schema
... select `Function` {
... return_type[is Tuple]: {
... element_types: {
... name,
... type: { name }
... } order by .num
... }
... }
... filter .name = 'sys::get_version';
{
Object {
return_type: Object {
element_types: {
Object {
name: 'major',
type: Object {
name: 'std::int64'
}
},
Object {
name: 'minor',
type: Object {
name: 'std::int64'
}
},
Object {
name: 'stage',
type: Object {
name: 'sys::VersionStage'
}
},
Object {
name: 'stage_no',
type: Object {
name: 'std::int64'
}
},
Object {
name: 'local',
type: Object { name: 'array' }
}
}
}
}
}
================================================================================
.. File: constraints.rst
.. _ref_datamodel_introspection_constraints:
===========
Constraints
===========
This section describes introspection of :ref:`constraints
<ref_datamodel_constraints>`.
Introspection of the ``schema::Constraint``:
.. code-block:: edgeql-repl
db> with module schema
... select ObjectType {
... name,
... links: {
... name,
... },
... properties: {
... name,
... }
... }
... filter .name = 'schema::Constraint';
{
Object {
name: 'schema::Constraint',
links: {
Object { name: '__type__' },
Object { name: 'args' },
Object { name: 'annotations' },
Object { name: 'bases' },
Object { name: 'ancestors' },
Object { name: 'params' },
Object { name: 'return_type' },
Object { name: 'subject' }
},
properties: {
Object { name: 'errmessage' },
Object { name: 'expr' },
Object { name: 'finalexpr' },
Object { name: 'id' },
Object { name: 'abstract' },
Object { name: 'name' },
Object { name: 'return_typemod' },
Object { name: 'subjectexpr' }
}
}
}
Consider the following schema:
.. code-block:: sdl
scalar type maxex_100 extending int64 {
constraint max_ex_value(100);
}
Introspection of the scalar ``maxex_100`` with focus on the constraint:
.. code-block:: edgeql-repl
db> with module schema
... select ScalarType {
... name,
... constraints: {
... name,
... expr,
... annotations: { name, @value },
... subject: { name },
... params: { name, @value, type: { name } },
... return_typemod,
... return_type: { name },
... errmessage,
... },
... }
... filter .name = 'default::maxex_100';
{
Object {
name: 'default::maxex_100',
constraints: {
Object {
name: 'std::max_ex_value',
expr: '(__subject__ <= max)',
annotations: {},
subject: Object { name: 'default::maxex_100' },
params: {
Object {
name: 'max',
type: Object { name: 'anytype' },
@value: '100'
}
},
return_typemod: 'SingletonType',
return_type: Object { name: 'std::bool' }
errmessage: '{__subject__} must be less ...',
}
}
}
}
.. list-table::
:class: seealso
* - **See also**
* - :ref:`Schema > Constraints <ref_datamodel_constraints>`
* - :ref:`SDL > Constraints <ref_eql_sdl_constraints>`
* - :ref:`DDL > Constraints <ref_eql_ddl_constraints>`
* - :ref:`Standard Library > Constraints <ref_std_constraints>`
================================================================================
.. File: functions.rst
.. _ref_datamodel_introspection_functions:
=========
Functions
=========
This section describes introspection of :ref:`functions
<ref_datamodel_functions>`.
Introspection of the ``schema::Function``:
.. code-block:: edgeql-repl
db> with module schema
... select ObjectType {
... name,
... links: {
... name,
... },
... properties: {
... name,
... }
... }
... filter .name = 'schema::Function';
{
Object {
name: 'schema::Function',
links: {
Object { name: '__type__' },
Object { name: 'annotations' },
Object { name: 'params' },
Object { name: 'return_type' }
},
properties: {
Object { name: 'id' },
Object { name: 'name' },
Object { name: 'return_typemod' }
}
}
}
Since ``params`` are quite important to functions, here's their structure:
.. code-block:: edgeql-repl
db> with module schema
... select ObjectType {
... name,
... links: {
... name,
... },
... properties: {
... name,
... }
... }
... filter .name = 'schema::Parameter';
{
Object {
name: 'schema::Parameter',
links: {
Object { name: '__type__' },
Object { name: 'type' }
},
properties: {
Object { name: 'default' },
Object { name: 'id' },
Object { name: 'kind' },
Object { name: 'name' },
Object { name: 'num' },
Object { name: 'typemod' }
}
}
}
Introspection of the built-in :eql:func:`count`:
.. code-block:: edgeql-repl
db> with module schema
... select `Function` {
... name,
... annotations: { name, @value },
... params: {
... kind,
... name,
... num,
... typemod,
... type: { name },
... default,
... },
... return_typemod,
... return_type: { name },
... }
... filter .name = 'std::count';
{
Object {
name: 'std::count',
annotations: {},
params: {
Object {
kind: 'PositionalParam',
name: 's',
num: 0,
typemod: 'SetOfType',
type: Object { name: 'anytype' },
default: {}
}
},
return_typemod: 'SingletonType',
return_type: Object { name: 'std::int64' }
}
}
.. list-table::
:class: seealso
* - **See also**
* - :ref:`Schema > Functions <ref_datamodel_functions>`
* - :ref:`SDL > Functions <ref_eql_sdl_functions>`
* - :ref:`DDL > Functions <ref_eql_ddl_functions>`
* - :ref:`Reference > Function calls <ref_reference_function_call>`
* - :ref:`Cheatsheets > Functions <ref_cheatsheet_functions>`
================================================================================
.. File: index.rst
.. _ref_datamodel_introspection:
Introspection
=============
.. index:: describe, introspect, typeof, schema module
All of the schema information in Gel is stored in the ``schema``
:ref:`module <ref_datamodel_modules>` and is accessible via
*introspection queries*.
All the introspection types are themselves extending
:eql:type:`BaseObject`, so they are also subject to introspection :ref:`as
object types <ref_datamodel_introspection_object_types>`. The following
query will give a list of all the types used in introspection:
.. code-block:: edgeql
select name := schema::ObjectType.name
filter name like 'schema::%';
There's also a couple of ways of getting the introspection type of a
particular expression. Any :eql:type:`Object` has a ``__type__`` link
to the ``schema::ObjectType``. For scalars there's the
:eql:op:`introspect` and :eql:op:`typeof` operators that can be used
to get the type of an expression.
Finally, the command :eql:stmt:`describe` can be used to get
information about Gel types in a variety of human-readable formats.
.. toctree::
:maxdepth: 3
:hidden:
objects
scalars
colltypes
functions
triggers
mutation_rewrites
indexes
constraints
operators
casts
================================================================================
.. File: indexes.rst
.. _ref_datamodel_introspection_indexes:
=======
Indexes
=======
This section describes introspection of :ref:`indexes
<ref_datamodel_indexes>`.
Introspection of the ``schema::Index``:
.. code-block:: edgeql-repl
db> with module schema
... select ObjectType {
... name,
... links: {
... name,
... },
... properties: {
... name,
... }
... }
... filter .name = 'schema::Index';
{
Object {
name: 'schema::Index',
links: {Object { name: '__type__' }},
properties: {
Object { name: 'expr' },
Object { name: 'id' },
Object { name: 'name' }
}
}
}
Consider the following schema:
.. code-block:: sdl
abstract type Addressable {
property address: str;
}
type User extending Addressable {
# define some properties and a link
required property name: str;
multi link friends: User;
# define an index for User based on name
index on (.name);
}
Introspection of ``User.name`` index:
.. code-block:: edgeql-repl
db> with module schema
... select Index {
... expr,
... }
... filter .expr like '%.name';
{
Object {
expr: '.name'
}
}
For introspection of the index within the context of its host type see
:ref:`object type introspection <ref_datamodel_introspection_object_types>`.
.. list-table::
:class: seealso
* - **See also**
* - :ref:`Schema > Indexes <ref_datamodel_indexes>`
* - :ref:`SDL > Indexes <ref_eql_sdl_indexes>`
* - :ref:`DDL > Indexes <ref_eql_ddl_indexes>`
================================================================================
.. File: mutation_rewrites.rst
.. _ref_datamodel_introspection_mutation_rewrites:
=================
Mutation rewrites
=================
This section describes introspection of :ref:`mutation rewrites
<ref_datamodel_mutation_rewrites>`.
Introspection of the ``schema::Rewrite``:
.. code-block:: edgeql-repl
db> select schema::ObjectType {
... name,
... links: {
... name
... },
... properties: {
... name
... }
... } filter .name = 'schema::Rewrite';
{
schema::ObjectType {
name: 'schema::Rewrite',
links: {
schema::Link {name: 'subject'},
schema::Link {name: '__type__'},
schema::Link {name: 'ancestors'},
schema::Link {name: 'bases'},
schema::Link {name: 'annotations'}
},
properties: {
schema::Property {name: 'inherited_fields'},
schema::Property {name: 'computed_fields'},
schema::Property {name: 'builtin'},
schema::Property {name: 'internal'},
schema::Property {name: 'name'},
schema::Property {name: 'id'},
schema::Property {name: 'abstract'},
schema::Property {name: 'is_abstract'},
schema::Property {name: 'final'},
schema::Property {name: 'is_final'},
schema::Property {name: 'kind'},
schema::Property {name: 'expr'},
},
},
}
Introspection of all properties in the ``default`` schema with a mutation
rewrite:
.. code-block:: edgeql-repl
db> select schema::ObjectType {
... name,
... properties := (
... select .properties {
... name,
... rewrites: {
... kind
... }
... } filter exists .rewrites
... )
... } filter .name ilike 'default::%'
... and exists .properties.rewrites;
{
schema::ObjectType {
name: 'default::Post',
properties: {
schema::Property {
name: 'created',
rewrites: {
schema::Rewrite {
kind: Insert
}
}
},
schema::Property {
name: 'modified',
rewrites: {
schema::Rewrite {
kind: Insert
},
schema::Rewrite {
kind: Update
}
}
},
},
},
}
Introspection of all rewrites, including the type of query (``kind``),
rewrite expression, and the object and property they are on:
.. code-block:: edgeql-repl
db> select schema::Rewrite {
... subject := (
... select .subject {
... name,
... source: {
... name
... }
... }
... ),
... kind,
... expr
... };
{
schema::Rewrite {
subject: schema::Property {
name: 'created',
source: schema::ObjectType {
name: 'default::Post'
}
},
kind: Insert,
expr: 'std::datetime_of_statement()'
},
schema::Rewrite {
subject: schema::Property {
name: 'modified',
source: schema::ObjectType {
name: 'default::Post'
}
},
kind: Insert,
expr: 'std::datetime_of_statement()'
},
schema::Rewrite {
subject: schema::Property {
name: 'modified',
source: schema::ObjectType {
name: 'default::Post'
}
},
kind: Update,
expr: 'std::datetime_of_statement()'
},
}
Introspection of all rewrites on a ``default::Post`` property named
``modified``:
.. code-block:: edgeql-repl
db> select schema::Rewrite {kind, expr}
... filter .subject.source.name = 'default::Post'
... and .subject.name = 'modified';
{
schema::Rewrite {
kind: Insert,
expr: 'std::datetime_of_statement()'
},
schema::Rewrite {
kind: Update,
expr: 'std::datetime_of_statement()'
}
}
.. list-table::
:class: seealso
* - **See also**
* - :ref:`Schema > Mutation rewrites <ref_datamodel_mutation_rewrites>`
* - :ref:`SDL > Mutation rewrites <ref_eql_sdl_mutation_rewrites>`
* - :ref:`DDL > Mutation rewrites <ref_eql_ddl_mutation_rewrites>`
================================================================================
.. File: objects.rst
.. _ref_datamodel_introspection_object_types:
============
Object types
============
This section describes introspection of :ref:`object types
<ref_datamodel_object_types>`.
Introspection of the ``schema::ObjectType``:
.. code-block:: edgeql-repl
db> with module schema
... select ObjectType {
... name,
... links: {
... name,
... },
... properties: {
... name,
... }
... }
... filter .name = 'schema::ObjectType';
{
Object {
name: 'schema::ObjectType',
links: {
Object { name: '__type__' },
Object { name: 'annotations' },
Object { name: 'bases' },
Object { name: 'constraints' },
Object { name: 'indexes' },
Object { name: 'links' },
Object { name: 'ancestors' },
Object { name: 'pointers' },
Object { name: 'properties' }
},
properties: {
Object { name: 'id' },
Object { name: 'abstract' },
Object { name: 'name' }
}
}
}
Consider the following schema:
.. code-block:: sdl
abstract type Addressable {
address: str;
}
type User extending Addressable {
# define some properties and a link
required name: str;
multi friends: User;
# define an index for User based on name
index on (.name);
}
Introspection of ``User``:
.. code-block:: edgeql-repl
db> with module schema
... select ObjectType {
... name,
... abstract,
... bases: { name },
... ancestors: { name },
... annotations: { name, @value },
... links: {
... name,
... cardinality,
... required,
... target: { name },
... },
... properties: {
... name,
... cardinality,
... required,
... target: { name },
... },
... constraints: { name },
... indexes: { expr },
... }
... filter .name = 'default::User';
{
Object {
name: 'default::User',
abstract: false,
bases: {Object { name: 'default::Addressable' }},
ancestors: {
Object { name: 'std::BaseObject' },
Object { name: 'std::Object' },
Object { name: 'default::Addressable' }
},
annotations: {},
links: {
Object {
name: '__type__',
cardinality: 'One',
required: {},
target: Object { name: 'schema::Type' }
},
Object {
name: 'friends',
cardinality: 'Many',
required: false,
target: Object { name: 'default::User' }
}
},
properties: {
Object {
name: 'address',
cardinality: 'One',
required: false,
target: Object { name: 'std::str' }
},
Object {
name: 'id',
cardinality: 'One',
required: true,
target: Object { name: 'std::uuid' }
},
Object {
name: 'name',
cardinality: 'One',
required: true,
target: Object { name: 'std::str' }
}
},
constraints: {},
indexes: {
Object {
expr: '.name'
}
}
}
}
.. list-table::
:class: seealso
* - **See also**
* - :ref:`Schema > Object types <ref_datamodel_object_types>`
* - :ref:`SDL > Object types <ref_eql_sdl_object_types>`
* - :ref:`DDL > Object types <ref_eql_ddl_object_types>`
* - :ref:`Cheatsheets > Object types <ref_cheatsheet_object_types>`
================================================================================
.. File: operators.rst
.. _ref_datamodel_introspection_operators:
=========
Operators
=========
This section describes introspection of Gel operators. Much like
functions, operators have parameters and return types as well as a few
other features.
Introspection of the ``schema::Operator``:
.. code-block:: edgeql-repl
db> with module schema
... select ObjectType {
... name,
... links: {
... name,
... },
... properties: {
... name,
... }
... }
... filter .name = 'schema::Operator';
{
Object {
name: 'schema::Operator',
links: {
Object { name: '__type__' },
Object { name: 'annotations' },
Object { name: 'params' },
Object { name: 'return_type' }
},
properties: {
Object { name: 'id' },
Object { name: 'name' },
Object { name: 'operator_kind' },
Object { name: 'return_typemod' }
}
}
}
Since ``params`` are quite important to operators, here's their structure:
.. code-block:: edgeql-repl
db> with module schema
... select ObjectType {
... name,
... links: {
... name,
... },
... properties: {
... name,
... }
... }
... filter .name = 'schema::Parameter';
{
Object {
name: 'schema::Parameter',
links: {
Object { name: '__type__' },
Object { name: 'type' }
},
properties: {
Object { name: 'default' },
Object { name: 'id' },
Object { name: 'kind' },
Object { name: 'name' },
Object { name: 'num' },
Object { name: 'typemod' }
}
}
}
Introspection of the :eql:op:`and` operator:
.. code-block:: edgeql-repl
db> with module schema
... select Operator {
... name,
... operator_kind,
... annotations: { name, @value },
... params: {
... kind,
... name,
... num,
... typemod,
... type: { name },
... default,
... },
... return_typemod,
... return_type: { name },
... }
... filter .name = 'std::AND';
{
Object {
name: 'std::AND',
operator_kind: 'Infix',
annotations: {},
params: {
Object {
kind: 'PositionalParam',
name: 'a',
num: 0,
typemod: 'SingletonType',
type: Object { name: 'std::bool' },
default: {}
},
Object {
kind: 'PositionalParam',
name: 'b',
num: 1,
typemod: 'SingletonType',
type: Object { name: 'std::bool' },
default: {}
}
},
return_typemod: 'SingletonType',
return_type: Object { name: 'std::bool' }
}
}
================================================================================
.. File: scalars.rst
.. _ref_datamodel_introspection_scalar_types:
============
Scalar types
============
This section describes introspection of :ref:`scalar types
<ref_datamodel_scalar_types>`.
Introspection of the ``schema::ScalarType``:
.. code-block:: edgeql-repl
db> with module schema
... select ObjectType {
... name,
... links: {
... name,
... },
... properties: {
... name,
... }
... }
... filter .name = 'schema::ScalarType';
{
Object {
name: 'schema::ScalarType',
links: {
Object { name: '__type__' },
Object { name: 'annotations' },
Object { name: 'bases' },
Object { name: 'constraints' },
Object { name: 'ancestors' }
},
properties: {
Object { name: 'default' },
Object { name: 'enum_values' },
Object { name: 'id' },
Object { name: 'abstract' },
Object { name: 'name' }
}
}
}
Introspection of the built-in scalar :eql:type:`str`:
.. code-block:: edgeql-repl
db> with module schema
... select ScalarType {
... name,
... default,
... enum_values,
... abstract,
... bases: { name },
... ancestors: { name },
... annotations: { name, @value },
... constraints: { name },
... }
... filter .name = 'std::str';
{
Object {
name: 'std::str',
default: {},
enum_values: {},
abstract: {},
bases: {Object { name: 'std::anyscalar' }},
ancestors: {Object { name: 'std::anyscalar' }},
annotations: {},
constraints: {}
}
}
For an :ref:`enumerated scalar type <ref_std_enum>`,
consider the following:
.. code-block:: sdl
scalar type Color extending enum<Red, Green, Blue>;
Introspection of the enum scalar ``Color``:
.. code-block:: edgeql-repl
db> with module schema
... select ScalarType {
... name,
... default,
... enum_values,
... abstract,
... bases: { name },
... ancestors: { name },
... annotations: { name, @value },
... constraints: { name },
... }
... filter .name = 'default::Color';
{
Object {
name: 'default::Color',
default: {},
enum_values: ['Red', 'Green', 'Blue'],
abstract: {},
bases: {Object { name: 'std::anyenum' }},
ancestors: {
Object { name: 'std::anyscalar' },
Object { name: 'std::anyenum' }
},
annotations: {},
constraints: {}
}
}
================================================================================
.. File: triggers.rst
.. _ref_datamodel_introspection_triggers:
=========
Triggers
=========
This section describes introspection of :ref:`triggers
<ref_datamodel_triggers>`.
Introspection of ``schema::Trigger``:
.. code-block:: edgeql-repl
db> with module schema
... select ObjectType {
... name,
... links: {
... name,
... },
... properties: {
... name,
... }
... } filter .name = 'schema::Trigger';
{
schema::ObjectType {
name: 'schema::Trigger',
links: {
schema::Link {name: 'subject'},
schema::Link {name: '__type__'},
schema::Link {name: 'ancestors'},
schema::Link {name: 'bases'},
schema::Link {name: 'annotations'}
},
properties: {
schema::Property {name: 'inherited_fields'},
schema::Property {name: 'computed_fields'},
schema::Property {name: 'builtin'},
schema::Property {name: 'internal'},
schema::Property {name: 'name'},
schema::Property {name: 'id'},
schema::Property {name: 'abstract'},
schema::Property {name: 'is_abstract'},
schema::Property {name: 'final'},
schema::Property {name: 'is_final'},
schema::Property {name: 'timing'},
schema::Property {name: 'kinds'},
schema::Property {name: 'scope'},
schema::Property {name: 'expr'},
},
},
}
Introspection of a trigger named ``log_insert`` on the ``User`` type:
.. lint-off
.. code-block:: edgeql-repl
db> with module schema
... select Trigger {
... name,
... kinds,
... timing,
... scope,
... expr,
... subject: {
... name
... }
... } filter .name = 'log_insert';
{
schema::Trigger {
name: 'log_insert',
kinds: {Insert},
timing: After,
scope: Each,
expr: 'insert default::Log { action := \'insert\', target_name := __new__.name }',
subject: schema::ObjectType {name: 'default::User'},
},
}
.. lint-on
.. list-table::
:class: seealso
* - **See also**
* - :ref:`Schema > Triggers <ref_datamodel_triggers>`
* - :ref:`SDL > Triggers <ref_eql_sdl_triggers>`
* - :ref:`DDL > Triggers <ref_eql_ddl_triggers>`
================================================================================
.. File: linkprops.rst
.. _ref_datamodel_linkprops:
===============
Link properties
===============
.. index:: property, link property, linkprops, link table, relations, @
Links, like objects, can also contain **properties**. These are used to store metadata about the link. Due to how they're persisted under the hood,
link properties have a few additional constraints: they're always ``single``
and ``optional``.
Link properties require non-trivial syntax to use them, so they are considered
to be an advanced feature. In many cases, regular properties should be used
instead. To paraphrase a famous quote: "Link properties are like a parachute,
you don't need them very often, but when you do, they can be clutch."
.. note::
In practice, link properties are best used with many-to-many relationships
(``multi`` links without any exclusive constraints). For one-to-one,
one-to-many, and many-to-one relationships the same data should be stored in
object properties instead.
Declaration
===========
Let's a create a ``Person.friends`` link with a ``strength`` property
corresponding to the strength of the friendship.
.. code-block:: sdl
type Person {
required name: str { constraint exclusive };
multi friends: Person {
strength: float64;
}
}
Constraints
===========
Now let's ensure that the ``@strength`` property is always non-negative:
.. code-block:: sdl
type Person {
required name: str { constraint exclusive };
multi friends: Person {
strength: float64;
constraint expression on (
__subject__@strength >= 0
);
}
}
Indexes
=======
To add an index on a link property, we have to refactor our code and define
an abstract link ``friendship`` that will contain the ``strength`` property
with an index on it:
.. code-block:: sdl
abstract link friendship {
strength: float64;
index on (__subject__@strength);
}
type Person {
required name: str { constraint exclusive };
multi friends: Person {
extending friendship;
};
}
Conceptualizing link properties
===============================
A way to conceptualize the difference between a regular property and
a link property is that regular properties are used to construct an object,
while link properties are used to construct the link between objects.
For example, here the ``name`` and ``email`` properties are used to construct a
``Person`` object:
.. code-block:: edgeql
insert Person {
name := "Jane",
email := "jane@jane.com"
}
Now let's insert a ``Person`` object linking it to another ``Person`` object
setting the ``@strength`` property to the link between them:
.. code-block:: edgeql
insert Person {
name := "Bob",
email := "bob@bob.com",
friends := (
insert Person {
name := "Jane",
email := "jane@jane.com",
@strength := 3.14
}
)
}
So we're not using ``@strength`` to construct a particular ``Person`` object,
but to quantify a link between two ``Person`` objects.
Inserting
=========
What if we want to insert a ``Person`` object while linking it to another
``Person`` that's already in the database?
The ``@strength`` property then will be specified in the *shape* of a
``select`` subquery:
.. code-block:: edgeql
insert Person {
name := "Bob",
friends := (
select detached Person {
@strength := 3.14
}
filter .name = "Alice"
)
}
.. note::
We are using the :eql:op:`detached` operator to unbind the
``Person`` reference from the scope of the ``insert`` query.
When doing a nested insert, link properties can be directly included in the
inner ``insert`` subquery:
.. code-block:: edgeql
insert Person {
name := "Bob",
friends := (
insert Person {
name := "Jane",
@strength := 3.14
}
)
}
Similarly, ``with`` can be used to capture an expression returning an
object type, after which a link property can be added when linking it to
another object type:
.. code-block:: edgeql
with
alice := (
insert Person {
name := "Alice"
}
unless conflict on .name
else (
select Person
filter .name = "Alice" limit 1
)
)
insert Person {
name := "Bob",
friends := alice {
@strength := 3.14
}
};
Updating
========
.. code-block:: edgeql
update Person
filter .name = "Bob"
set {
friends += (
select .friends {
@strength := 3.7
}
filter .name = "Alice"
)
};
The example updates the ``@strength`` property of Bob's friends link to
Alice to 3.7.
In the context of multi links the ``+=`` operator works like an
an insert/update operator.
To update one or more links in a multi link, you can select from the current
linked objects, as the example does. Use a ``detached`` selection if you
want to insert/update a wider selection of linked objects instead.
Selecting
=========
To select a link property, you can use the ``@<>name`` syntax inside the
select *shape*. Keep in mind, that you're not selecting a property on
an object with this syntax, but rather on the link, in this case ``friends``:
.. code-block:: edgeql-repl
gel> select Person {
.... name,
.... friends: {
.... name,
.... @strength
.... }
.... };
{
default::Person {name: 'Alice', friends: {}},
default::Person {
name: 'Bob',
friends: {
default::Person {name: 'Alice', @strength: 3.7}
}
},
}
Unions
======
A link property cannot be referenced in a set union *except* in the case of
a :ref:`for loop <ref_eql_for>`. That means this will *not* work:
.. code-block:: edgeql
# 🚫 Does not work
insert Movie {
title := 'The Incredible Hulk',
actors := {(
select Person {
@character_name := 'The Hulk'
} filter .name = 'Mark Ruffalo'
),
(
select Person {
@character_name := 'Iron Man'
} filter .name = 'Robert Downey Jr.'
)}
};
That query will produce an error: ``QueryError: invalid reference to link
property in top level shape``
You can use this workaround instead:
.. code-block:: edgeql
# ✅ Works!
insert Movie {
title := 'The Incredible Hulk',
actors := assert_distinct((
with characters := {
('The Hulk', 'Mark Ruffalo'),
('Iron Man', 'Robert Downey Jr.')
}
for character in characters union (
select Person {
@character_name := character.0
} filter .name = character.1
)
))
};
Note that we are also required to wrap the ``actors`` query with
:eql:func:`assert_distinct` here to assure the compiler that the result set
is distinct.
With computed backlinks
=======================
Specifying link properties of a computed backlink in your shape is also
supported. If you have this schema:
.. code-block:: sdl
type Person {
required name: str;
multi follows: Person {
followed: datetime {
default := datetime_of_statement();
};
};
multi link followers := .<follows[is Person];
}
this query will work as expected:
.. code-block:: edgeql
select Person {
name,
followers: {
name,
@followed
}
};
even though ``@followed`` is a link property of ``follows`` and we are
accessing is through the computed backlink ``followers`` instead.
.. list-table::
:class: seealso
* - **See also**
* - :ref:`Links and link properties <ref_datamodel_link_properties>`
* - :ref:`Properties in schema <ref_eql_sdl_props>`
* - :ref:`Properties with DDL <ref_eql_ddl_props>`
================================================================================
.. File: links.rst
.. _ref_datamodel_links:
=====
Links
=====
Links define a relationship between two
:ref:`object types <ref_datamodel_object_types>` in Gel.
Links in |Gel| are incredibly powerful and flexible. They can be used to model
relationships of any cardinality, can be traversed in both directions,
can be polymorphic, can have constraints, and many other things.
Links are directional
=====================
Links are *directional*: they have a **source** (the type on which they are
declared) and a **target** (the type they point to).
E.g. the following schema defines a link from ``Person`` to ``Person`` and
a link from ``Company`` to ``Person``:
.. code-block:: sdl
type Person {
link best_friend: Person;
}
type Company {
multi link employees: Person;
}
The ``employees`` link's source is ``Company`` and its target is ``Person``.
The ``link`` keyword is optional, and can be omitted.
Link cardinality
================
.. index:: single, multi
All links have a cardinality: either ``single`` or ``multi``. The default is
``single`` (a "to-one" link). Use the ``multi`` keyword to declare a "to-many"
link:
.. code-block:: sdl
type Person {
multi friends: Person;
}
Required links
==============
.. index:: required, optional, not null
All links are either ``optional`` or ``required``; the default is ``optional``.
Use the ``required`` keyword to declare a required link. A required link must
point to *at least one* target instance, and if the cardinality of the required
link is ``single``, it must point to *exactly one* target instance. In this
scenario, every ``Person`` must have *exactly one* ``best_friend``:
.. code-block:: sdl
type Person {
required best_friend: Person;
}
Links with cardinality ``multi`` can also be ``required``;
``required multi`` links must point to *at least one* target object:
.. code-block:: sdl
type Person {
name: str;
}
type GroupChat {
required multi members: Person;
}
Attempting to create a ``GroupChat`` with no members would fail.
Exclusive constraints
=====================
.. index:: constraint exclusive
You can add an ``exclusive`` constraint to a link to guarantee that no other
instances can link to the same target(s):
.. code-block:: sdl
type Person {
name: str;
}
type GroupChat {
required multi members: Person {
constraint exclusive;
}
}
With ``exclusive`` on ``GroupChat.members``, two ``GroupChat`` objects cannot
link to the same ``Person``; put differently, no ``Person`` can be a
``member`` of multiple ``GroupChat`` objects.
Backlinks
=========
.. index:: backlink
In Gel you can traverse links in reverse to find objects that link to
the object. You can do that directly in your query. E.g. for this example
schema:
.. code-block:: sdl
type Author {
name: str;
}
type Article {
title: str;
multi authors: Author;
}
You can find all articles by "John Doe" by traversing the ``authors``
link in reverse:
.. code-block:: edgeql
select Author {
articles := .<authors[is Article]
}
filter .name = "John Doe";
While the ``.<authors[is Article]`` exppression looks complicated,
the syntax is easy to read once you understand the structure of it:
* ``.<`` is used to traverse a link in reverse, it's the reverse of
the familiar ``.`` operator.
* ``authors`` is the name of the link that the type on the other side
has to point to ``Author``. In this case we know that ``Article``
has a link ``authors`` to ``Author``, so we use it!
* ``[is Article]`` is a filter that ensures we only traverse links
that point to ``Article`` objects.
If there's a backlink that you will be traversing often, you can declare it
as a computed link:
.. code-block:: sdl-diff
type Author {
name: str;
+ articles := .<authors[is Article];
}
Last point to note: **backlinks** work in reverse to find objects that link
to the object, and therefore assume ``multi`` as a default.
Use the ``single`` keyword to declare a "to-one" backlink computed link:
.. code-block:: sdl
type CompanyEmployee {
single company := .<employees[is Company];
}
Default values
==============
.. index:: default
Links can declare a default value in the form of an EdgeQL expression, which
will be executed upon insertion. In this example, new people are automatically
assigned three random friends:
.. code-block:: sdl
type Person {
required name: str;
multi friends: Person {
default := (select Person order by random() limit 3);
}
}
Modeling relations
==================
.. index:: cardinality, one-to-one, one-to-many, many-to-one, many-to-many,
link table, association table
By combining *link cardinality* and *exclusivity constraints*, we can model
every kind of relationship: one-to-one, one-to-many, many-to-one, and
many-to-many.
.. list-table::
* - **Relation type**
- **Cardinality**
- **Exclusive**
* - One-to-one
- ``single``
- Yes
* - One-to-many
- ``multi``
- Yes
* - Many-to-one
- ``single``
- No
* - Many-to-many
- ``multi``
- No
.. _ref_guide_many_to_one:
Many-to-one
-----------
Many-to-one relationships typically represent concepts like ownership,
membership, or hierarchies. For example, ``Person`` and ``Shirt``. One person
may own many shirts, and a shirt is (usually) owned by just one person.
.. code-block:: sdl
type Person {
required name: str
}
type Shirt {
required color: str;
owner: Person;
}
Since links are ``single`` by default, each ``Shirt`` only corresponds to
one ``Person``. In the absence of any exclusivity constraints, multiple shirts
can link to the same ``Person``. Thus, we have a one-to-many relationship
between ``Person`` and ``Shirt``.
When fetching a ``Person``, it's possible to deeply fetch their collection of
``Shirts`` by traversing the ``Shirt.owner`` link *in reverse*, known as a
**backlink**. See the :ref:`select docs <ref_eql_statements_select>` to
learn more.
.. _ref_guide_one_to_many:
One-to-many
-----------
Conceptually, one-to-many and many-to-one relationships are identical; the
"directionality" is a matter of perspective. Here, the same "shirt owner"
relationship is represented with a ``multi`` link:
.. code-block:: sdl
type Person {
required name: str;
multi shirts: Shirt {
# ensures a one-to-many relationship
constraint exclusive;
}
}
type Shirt {
required color: str;
}
.. note::
Don't forget the ``exclusive`` constraint! Without it, the relationship
becomes many-to-many.
Under the hood, a ``multi`` link is stored in an intermediate `association
table <https://en.wikipedia.org/wiki/Associative_entity>`_, whereas a
``single`` link is stored as a column in the object type where it is declared.
.. note::
Choosing a link direction can be tricky. Should you model this
relationship as one-to-many (with a ``multi`` link) or as many-to-one
(with a ``single`` link and a backlink)? A general rule of thumb:
- Use a ``multi`` link if the relationship is relatively stable and
not updated frequently, and the set of related objects is typically
small. For example, a list of postal addresses in a user profile.
- Otherwise, prefer a single link from one object type and a computed
backlink on the other. This can be more efficient and is generally
recommended for 1:N relations:
.. code-block:: sdl
type Post {
required author: User;
}
type User {
multi posts := (.<author[is Post])
}
.. _ref_guide_one_to_one:
One-to-one
----------
Under a *one-to-one* relationship, the source object links to a single instance
of the target type, and vice versa. As an example, consider a schema to
represent assigned parking spaces:
.. code-block:: sdl
type Employee {
required name: str;
assigned_space: ParkingSpace {
constraint exclusive;
}
}
type ParkingSpace {
required number: int64;
}
All links are ``single`` unless otherwise specified, so no ``Employee`` can
have more than one ``assigned_space``. The :eql:constraint:`exclusive`
constraint guarantees that a given ``ParkingSpace`` can't be assigned to
multiple employees. Together, these form a one-to-one relationship.
.. _ref_guide_many_to_many:
Many-to-many
------------
A *many-to-many* relation is the least constrained kind of relationship. There
is no exclusivity or cardinality constraint in either direction. As an example,
consider a simple app where a ``User`` can "like" their favorite ``Movie``:
.. code-block:: sdl
type User {
required name: str;
multi likes: Movie;
}
type Movie {
required title: str;
}
A user can like multiple movies. And in the absence of an ``exclusive``
constraint, each movie can be liked by multiple users, creating a many-to-many
relationship.
.. note::
Links are always distinct. It's not possible to link the **same** objects
twice. For example:
.. code-block:: sdl
type User {
required name: str;
multi watch_history: Movie {
seen_at: datetime;
};
}
type Movie {
required title: str;
}
In this model, a user can't watch the same movie more than once (the link
from a specific user to a specific movie can exist only once). One approach
is to store multiple timestamps in an array on the link property:
.. code-block:: sdl
type User {
required name: str;
multi watch_history: Movie {
seen_at: array<datetime>;
};
}
type Movie {
required title: str;
}
Alternatively, you might introduce a dedicated type:
.. code-block:: sdl
type User {
required name: str;
multi watch_history := .<user[is WatchHistory];
}
type Movie {
required title: str;
}
type WatchHistory {
required user: User;
required movie: Movie;
seen_at: datetime;
}
Remember to use **single** links in the join table so you don't end up
with extra tables.
.. _ref_datamodel_link_properties:
Link properties
===============
.. index:: linkprops, metadata, link table
Like object types, links in Gel can contain **properties**. Link properties
can store metadata about the link, such as the *date* a link was created
or the *strength* of the relationship:
.. code-block:: sdl
type Person {
name: str;
multi family_members: Person {
relationship: str;
}
}
.. note::
Link properties can only be **primitive** data (scalars, enums,
arrays, or tuples) — *not* links to other objects. Also note that
link properties cannot be made required. They are always optional
by design.
Link properties are especially useful with many-to-many relationships, where
the link itself is a distinct concept with its own data. For relations
like one-to-one or one-to-many, it's often clearer to store data in the
object type itself instead of in a link property.
Read more about link properties in the :ref:`dedicated link properties article
<ref_datamodel_linkprops>`.
Inserting and updating link properties
--------------------------------------
To add a link with a link property, include the property name (prefixed by
``@``) in the shape:
.. code-block:: edgeql
insert Person {
name := "Bob",
family_members := (
select detached Person {
@relationship := "sister"
}
filter .name = "Alice"
)
};
Updating a link's property on an **existing** link is similar. You can select
the link from within the object being updated:
.. code-block:: edgeql
update Person
filter .name = "Bob"
set {
family_members := (
select .family_members {
@relationship := "step-sister"
}
filter .name = "Alice"
)
};
.. warning::
A link property cannot be referenced in a set union *except* in the case of
a :ref:`for loop <ref_eql_for>`. For instance:
.. code-block:: edgeql
# 🚫 Does not work
insert Movie {
title := 'The Incredible Hulk',
characters := {
(
select Person {
@character_name := 'The Hulk'
}
filter .name = 'Mark Ruffalo'
),
(
select Person {
@character_name := 'Abomination'
}
filter .name = 'Tim Roth'
)
}
};
will produce an error ``QueryError: invalid reference to link property in
top level shape``.
One workaround is to insert them via a ``for`` loop, combined with
:eql:func:`assert_distinct`:
.. code-block:: edgeql
# ✅ Works!
insert Movie {
title := 'The Incredible Hulk',
characters := assert_distinct((
with actors := {
('The Hulk', 'Mark Ruffalo'),
('Abomination', 'Tim Roth')
},
for actor in actors union (
select Person {
@character_name := actor.0
}
filter .name = actor.1
)
))
};
Querying link properties
------------------------
To query a link property, add the link property's name (prefixed with ``@``)
in the shape:
.. code-block:: edgeql-repl
db> select Person {
... name,
... family_members: {
... name,
... @relationship
... }
... };
.. note::
In the results above, Bob has a *step-sister* property on the link to
Alice, but Alice does not automatically have a property describing Bob.
Changes to link properties are not mirrored on the "backlink" side unless
explicitly updated, because link properties cannot be required.
.. note::
For a full guide on modeling, inserting, updating, and querying link
properties, see the :ref:`Using Link Properties <ref_datamodel_linkprops>`
guide.
.. _ref_datamodel_link_deletion:
Deletion policies
=================
.. index:: on target delete, on source delete, restrict, delete source, allow,
deferred restrict, delete target, if orphan
Links can declare their own **deletion policy** for when the **target** or
**source** is deleted.
Target deletion
---------------
The clause ``on target delete`` determines the action when the target object is
deleted:
- ``restrict`` (default) — raises an exception if the target is deleted.
- ``delete source`` — deletes the source when the target is deleted (a cascade).
- ``allow`` — removes the target from the link if the target is deleted.
- ``deferred restrict`` — like ``restrict`` but defers the error until the
end of the transaction if the object remains linked.
.. code-block:: sdl
type MessageThread {
title: str;
}
type Message {
content: str;
chat: MessageThread {
on target delete delete source;
}
}
.. _ref_datamodel_links_source_deletion:
Source deletion
---------------
The clause ``on source delete`` determines the action when the **source** is
deleted:
- ``allow`` — deletes the source, removing the link to the target.
- ``delete target`` — unconditionally deletes the target as well.
- ``delete target if orphan`` — deletes the target if and only if it's no
longer linked by any other object *via the same link*.
.. code-block:: sdl
type MessageThread {
title: str;
multi messages: Message {
on source delete delete target;
}
}
type Message {
content: str;
}
You can add ``if orphan`` if you'd like to avoid deleting a target that remains
linked elsewhere via the **same** link name.
.. code-block:: sdl-diff
type MessageThread {
title: str;
multi messages: Message {
- on source delete delete target;
+ on source delete delete target if orphan;
}
}
.. note::
The ``if orphan`` qualifier **does not** apply globally across
all links in the database or even all links from the same type. If another
link *by a different name* or *with a different on-target-delete* policy
points at the same object, it *doesn't* prevent the object from being
considered "orphaned" for the link that includes ``if orphan``.
.. _ref_datamodel_link_polymorphic:
Polymorphic links
=================
.. index:: abstract, subtypes, polymorphic
Links can be **polymorphic**, i.e., have an ``abstract`` target. In the
example below, we have an abstract type ``Person`` with concrete subtypes
``Hero`` and ``Villain``:
.. code-block:: sdl
abstract type Person {
name: str;
}
type Hero extending Person {
# additional fields
}
type Villain extending Person {
# additional fields
}
A polymorphic link can target any non-abstract subtype:
.. code-block:: sdl
type Movie {
title: str;
multi characters: Person;
}
When querying a polymorphic link, you can filter by a specific subtype, cast
the link to a subtype, etc. See :ref:`Polymorphic Queries <ref_eql_select_polymorphic>`
for details.
Abstract links
==============
.. index:: abstract
It's possible to define ``abstract`` links that aren't tied to a particular
source or target, and then extend them in concrete object types. This can help
eliminate repetitive declarations:
.. code-block:: sdl
abstract link link_with_strength {
strength: float64;
index on (__subject__@strength);
}
type Person {
multi friends: Person {
extending link_with_strength;
};
}
.. _ref_eql_sdl_links_overloading:
Overloading
===========
.. index:: overloaded
When an inherited link is modified (by adding more constraints or changing its
target type, etc.), the ``overloaded`` keyword is required. This prevents
unintentional overloading due to name clashes:
.. code-block:: sdl
abstract type Friendly {
# this type can have "friends"
multi friends: Friendly;
}
type User extending Friendly {
# overload the link target to to be specifically User
overloaded multi friends: User;
# ... other links and properties
}
.. _ref_eql_sdl_links:
.. _ref_eql_sdl_links_syntax:
Declaring links
===============
This section describes the syntax to use links in your schema.
Syntax
------
.. sdl:synopsis::
# Concrete link form used inside type declaration:
[ overloaded ] [{required | optional}] [{single | multi}]
[ link ] <name> : <type>
[ "{"
[ extending <base> [, ...] ; ]
[ default := <expression> ; ]
[ readonly := {true | false} ; ]
[ on target delete <action> ; ]
[ on source delete <action> ; ]
[ <annotation-declarations> ]
[ <property-declarations> ]
[ <constraint-declarations> ]
...
"}" ]
# Computed link form used inside type declaration:
[{required | optional}] [{single | multi}]
[ link ] <name> := <expression>;
# Computed link form used inside type declaration (extended):
[ overloaded ] [{required | optional}] [{single | multi}]
link <name> [: <type>]
[ "{"
using (<expression>) ;
[ extending <base> [, ...] ; ]
[ <annotation-declarations> ]
[ <constraint-declarations> ]
...
"}" ]
# Abstract link form:
abstract link <name>
[ "{"
[ extending <base> [, ...] ; ]
[ readonly := {true | false} ; ]
[ <annotation-declarations> ]
[ <property-declarations> ]
[ <constraint-declarations> ]
[ <index-declarations> ]
...
"}" ]
There are several forms of link declaration, as shown in the syntax synopsis
above:
- the first form is the canonical definition form;
- the second form is used for defining a
:ref:`computed link <ref_datamodel_computed>`;
- and the last form is used to define an abstract link.
The following options are available:
:eql:synopsis:`overloaded`
If specified, indicates that the link is inherited and that some
feature of it may be altered in the current object type. It is an
error to declare a link as *overloaded* if it is not inherited.
:eql:synopsis:`required`
If specified, the link is considered *required* for the parent
object type. It is an error for an object to have a required
link resolve to an empty value. Child links **always** inherit
the *required* attribute, i.e it is not possible to make a
required link non-required by extending it.
:eql:synopsis:`optional`
This is the default qualifier assumed when no qualifier is
specified, but it can also be specified explicitly. The link is
considered *optional* for the parent object type, i.e. it is
possible for the link to resolve to an empty value.
:eql:synopsis:`multi`
Specifies that there may be more than one instance of this link
in an object, in other words, ``Object.link`` may resolve to a set
of a size greater than one.
:eql:synopsis:`single`
Specifies that there may be at most *one* instance of this link
in an object, in other words, ``Object.link`` may resolve to a set
of a size not greater than one. ``single`` is assumed if nether
``multi`` nor ``single`` qualifier is specified.
:eql:synopsis:`extending <base> [, ...]`
Optional clause specifying the *parents* of the new link item.
Use of ``extending`` creates a persistent schema relationship
between the new link and its parents. Schema modifications
to the parent(s) propagate to the child.
If the same *property* name exists in more than one parent, or
is explicitly defined in the new link and at least one parent,
then the data types of the property targets must be *compatible*.
If there is no conflict, the link properties are merged to form a
single property in the new link item.
:eql:synopsis:`<type>`
The type must be a valid :ref:`type expression <ref_eql_types>`
denoting an object type.
The valid SDL sub-declarations are listed below:
:eql:synopsis:`default := <expression>`
Specifies the default value for the link as an EdgeQL expression.
The default value is used in an ``insert`` statement if an explicit
value for this link is not specified.
The expression must be :ref:`Stable <ref_reference_volatility>`.
:eql:synopsis:`readonly := {true | false}`
If ``true``, the link is considered *read-only*. Modifications
of this link are prohibited once an object is created. All of the
derived links **must** preserve the original *read-only* value.
:sdl:synopsis:`<annotation-declarations>`
Set link :ref:`annotation <ref_eql_sdl_annotations>`
to a given *value*.
:sdl:synopsis:`<property-declarations>`
Define a concrete :ref:`property <ref_eql_sdl_props>` on the link.
:sdl:synopsis:`<constraint-declarations>`
Define a concrete :ref:`constraint <ref_eql_sdl_constraints>` on the link.
:sdl:synopsis:`<index-declarations>`
Define an :ref:`index <ref_eql_sdl_indexes>` for this abstract
link. Note that this index can only refer to link properties.
.. _ref_eql_ddl_links:
DDL commands
============
This section describes the low-level DDL commands for creating, altering, and
dropping links. You typically don't need to use these commands directly, but
knowing about them is useful for reviewing migrations.
Create link
-----------
:eql-statement:
:eql-haswith:
Define a new link.
.. eql:synopsis::
[ with <with-item> [, ...] ]
{create|alter} type <TypeName> "{"
[ ... ]
create [{required | optional}] [{single | multi}]
link <name>
[ extending <base> [, ...] ]: <type>
[ "{" <subcommand>; [...] "}" ] ;
[ ... ]
"}"
# Computed link form:
[ with <with-item> [, ...] ]
{create|alter} type <TypeName> "{"
[ ... ]
create [{required | optional}] [{single | multi}]
link <name> := <expression>;
[ ... ]
"}"
# Abstract link form:
[ with <with-item> [, ...] ]
create abstract link [<module>::]<name> [extending <base> [, ...]]
[ "{" <subcommand>; [...] "}" ]
# where <subcommand> is one of
set default := <expression>
set readonly := {true | false}
create annotation <annotation-name> := <value>
create property <property-name> ...
create constraint <constraint-name> ...
on target delete <action>
on source delete <action>
reset on target delete
create index on <index-expr>
Description
^^^^^^^^^^^
The combinations of ``create type ... create link`` and ``alter type ...
create link`` define a new concrete link for a given object type, in DDL form.
There are three forms of ``create link``:
1. The canonical definition form (specifying a target type).
2. The computed link form (declaring a link via an expression).
3. The abstract link form (declaring a module-level link).
Parameters
^^^^^^^^^^^
Most sub-commands and options mirror those found in the
:ref:`SDL link declaration <ref_eql_sdl_links_syntax>`. In DDL form:
- ``set default := <expression>`` specifies a default value.
- ``set readonly := {true | false}`` makes the link read-only or not.
- ``create annotation <annotation-name> := <value>`` adds an annotation.
- ``create property <property-name> ...`` defines a property on the link.
- ``create constraint <constraint-name> ...`` defines a constraint on the link.
- ``on target delete <action>`` and ``on source delete <action>`` specify
deletion policies.
- ``reset on target delete`` resets the target deletion policy to default
or inherited.
- ``create index on <index-expr>`` creates an index on the link.
Examples
^^^^^^^^
.. code-block:: edgeql
alter type User {
create multi link friends: User
};
.. code-block:: edgeql
alter type User {
create link special_group := (
select __source__.friends
filter .town = __source__.town
)
};
.. code-block:: edgeql
create abstract link orderable {
create property weight: std::int64
};
alter type User {
create multi link interests extending orderable: Interest
};
Alter link
----------
:eql-statement:
:eql-haswith:
Changes the definition of a link.
.. eql:synopsis::
[ with <with-item> [, ...] ]
{create|alter} type <TypeName> "{"
[ ... ]
alter link <name>
[ "{" ] <subcommand>; [...] [ "}" ];
[ ... ]
"}"
[ with <with-item> [, ...] ]
alter abstract link [<module>::]<name>
[ "{" ] <subcommand>; [...] [ "}" ];
# where <subcommand> is one of
set default := <expression>
reset default
set readonly := {true | false}
reset readonly
rename to <newname>
extending ...
set required
set optional
reset optionality
set single
set multi
reset cardinality
set type <typename> [using (<conversion-expr>)]
reset type
using (<computed-expr>)
create annotation <annotation-name> := <value>
alter annotation <annotation-name> := <value>
drop annotation <annotation-name>
create property <property-name> ...
alter property <property-name> ...
drop property <property-name> ...
create constraint <constraint-name> ...
alter constraint <constraint-name> ...
drop constraint <constraint-name> ...
on target delete <action>
on source delete <action>
create index on <index-expr>
drop index on <index-expr>
Description
^^^^^^^^^^^
This command modifies an existing link on a type. It can also be used on
an abstract link at the module level.
Parameters
^^^^^^^^^^
- ``rename to <newname>`` changes the link's name.
- ``extending ...`` changes or adds link parents.
- ``set required`` / ``set optional`` changes the link optionality.
- ``reset optionality`` reverts optionality to default or inherited value.
- ``set single`` / ``set multi`` changes cardinality.
- ``reset cardinality`` reverts cardinality to default or inherited value.
- ``set type <typename> [using (<expr>)]`` changes the link's target type.
- ``reset type`` reverts the link's type to inherited.
- ``using (<expr>)`` changes the expression of a computed link.
- ``create annotation``, ``alter annotation``, ``drop annotation`` manage
annotations.
- ``create property``, ``alter property``, ``drop property`` manage link
properties.
- ``create constraint``, ``alter constraint``, ``drop constraint`` manage
link constraints.
- ``on target delete <action>`` and ``on source delete <action>`` manage
deletion policies.
- ``reset on target delete`` reverts the target deletion policy.
- ``create index on <index-expr>`` / ``drop index on <index-expr>`` manage
indexes on link properties.
Examples
^^^^^^^^
.. code-block:: edgeql
alter type User {
alter link friends create annotation title := "Friends";
};
.. code-block:: edgeql
alter abstract link orderable rename to sorted;
.. code-block:: edgeql
alter type User {
alter link special_group using (
# at least one of the friend's interests
# must match the user's
select __source__.friends
filter .interests IN __source__.interests
);
};
Drop link
---------
:eql-statement:
:eql-haswith:
Removes the specified link from the schema.
.. eql:synopsis::
[ with <with-item> [, ...] ]
alter type <TypeName> "{"
[ ... ]
drop link <name>
[ ... ]
"}"
[ with <with-item> [, ...] ]
drop abstract link [<module>]::<name>
Description
^^^^^^^^^^^
- ``alter type ... drop link <name>`` removes the link from an object type.
- ``drop abstract link <name>`` removes an abstract link from the schema.
Examples
^^^^^^^^
.. code-block:: edgeql
alter type User drop link friends;
.. code-block:: edgeql
drop abstract link orderable;
.. list-table::
:class: seealso
* - **See also**
- :ref:`Introspection > Object types <ref_datamodel_introspection_object_types>`
================================================================================
.. File: migrations.rst
.. _ref_datamodel_migrations:
==========
Migrations
==========
|Gel's| baked-in migration system lets you painlessly evolve your schema over
time. Just update the contents of your |.gel| file(s) and use the |Gel| CLI
to *create* and *apply* migrations.
.. code-block:: bash
$ gel migration create
Created dbschema/migrations/00001.edgeql
$ gel migrate
Applied dbschema/migrations/00001.edgeql
Refer to the :ref:`creating and applying migrations <ref_intro_migrations>`
guide for more information on how to use the migration system.
This document describes how migrations are implemented.
The migrations flow
===================
The migration flow is as follows:
1. The user edits the |.gel| files in the ``dbschema`` directory.
This makes the schema described in the |.gel| files **different** from the
actual schema in the database.
2. The user runs the :gelcmd:`migration create` command to create a new
migration (a sequence of low-level DDL commands).
* The CLI reads the |.gel| files and sends them to the |Gel| server, to
analyze the changes.
* The |Gel| server generates a migration plan and sends it back to the CLI.
* The migration plan might require clarification from the user.
If so, the CLI and the |Gel| server will go back and forth presenting
the user with a sequence of questions, until the migration plan is
clear and approved by the user.
3. The CLI writes the migration plan to a new file in the ``dbschema/migrations``
directory.
4. The user runs the :gelcmd:`migrate` command to apply the migration to the
database.
5. The user checks in the updated |.gel| files and the new
``dbschema/migrations`` migration file (created by :gelcmd:`migration create`)
into version control.
Command line tools
==================
The two most important commands are:
* :gelcmd:`migration create`
* :gelcmd:`migrate`
Automatic migrations
====================
Sometimes when you're prototyping something new you don't want to spend
time worrying about migrations. There's no data to lose and not much code
that depends on the schema just yet.
For this use case you can use the :gelcmd:`watch` command, which will
monitor your |.gel| files and automatically create and apply migrations
for you in the background.
.. _ref_eql_ddl:
Data definition language (DDL)
==============================
The migration plan is a sequence of DDL commands. DDL commands are low-level
instructions that describe the changes to the schema.
SDL and your |.gel| files are like a 3D printer: you design the final shape,
and the system puts a database together for you. Using DDL is like building a
house the traditional way: to add a window, you first need a frame; to have a
frame, you need a wall; and so on.
If your schema looks like this:
.. code-block:: sdl
type User {
required name: str;
}
then the corresponding DDL might look like this:
.. code-block:: edgeql
create type User {
create required property name: str;
}
There are some circumstances where users might want to use DDL directly.
But in most cases you just need to learn how to read them to understand
the migration plan. Luckily, the DDL and SDL syntaxes were designed in tandem
and are very similar.
Most documentation pages on Gel's schema have a section about DDL commands,
e.g. :ref:`object types DDL <ref_eql_ddl_object_types>`.
.. _ref_eql_ddl_migrations:
Migration DDL commands
======================
Migrations themselves are a sequence of special DDL commands.
Like all DDL commands, ``start migration`` and other migration commands are
considered low-level. Users are encouraged to use the built-in
:ref:`migration tools <ref_cli_gel_migration>` instead.
However, if you want to implement your own migration tools, this section
will give you a good understanding of how Gel migrations work under the hood.
Start migration
---------------
:eql-statement:
Start a migration block.
.. eql:synopsis::
start migration to "{"
<sdl-declaration> ;
[ ... ]
"}" ;
Parameters
^^^^^^^^^^
:eql:synopsis:`<sdl-declaration>`
Complete schema text (content of all |.gel| files) defined with
the declarative :ref:`Gel schema definition language <ref_eql_sdl>`.
Description
^^^^^^^^^^^
The command ``start migration`` defines a migration of the schema to a
new state. The target schema state is described using :ref:`SDL
<ref_eql_sdl>` and describes the entire schema. This is important to
remember when creating a migration to add a few more things to an
existing schema as all the existing schema objects and the new ones
must be included in the ``start migration`` command. Objects that
aren't included in the command will be removed from the new schema
(which may result in data loss).
This command also starts a transaction block if not inside a
transaction already.
While inside a migration block, all issued EdgeQL statements are not executed
immediately and are instead recorded to be part of the migration script. Aside
from normal EdgeQL commands the following special migration commands are
available:
* :eql:stmt:`describe current migration` -- return a list of statements
currently recorded as part of the migration;
* :eql:stmt:`populate migration` -- auto-populate the migration with
system-generated DDL statements to achieve the target schema state;
* :eql:stmt:`abort migration` -- abort the migration block and discard the
migration;
* :eql:stmt:`commit migration` -- commit the migration by executing the
migration script statements and recording the migration into the system
migration log.
Example
^^^^^^^
Create a new migration to a target schema specified by the Gel Schema
syntax:
.. code-block:: edgeql
start migration to {
module default {
type User {
property username: str;
};
};
};
.. _ref_eql_ddl_migrations_create:
create migration
----------------
:eql-statement:
Create a new migration using an explicit EdgeQL script.
.. eql:synopsis::
create migration "{"
<edgeql-statement> ;
[ ... ]
"}" ;
Parameters
^^^^^^^^^^
:eql:synopsis:`<edgeql-statement>`
Any valid EdgeQL statement, except ``database``, ``branch``, ``role``,
``configure``, ``migration``, or ``transaction`` statements.
Description
^^^^^^^^^^^
The command ``create migration`` executes all the nested EdgeQL commands
and records the migration into the system migration log.
Example
^^^^^^^
Create a new migration to a target schema specified by the Gel Schema
syntax:
.. code-block:: edgeql
create migration {
create type default::User {
create property username: str;
}
};
Abort migration
---------------
:eql-statement:
Abort the current migration block and discard the migration.
.. eql:synopsis::
abort migration ;
Description
^^^^^^^^^^^
The command ``abort migration`` is used to abort a migration block started by
:eql:stmt:`start migration`. Issuing ``abort migration`` outside of a
migration block is an error.
Example
^^^^^^^
Start a migration block and then abort it:
.. code-block:: edgeql
start migration to {
module default {
type User;
};
};
abort migration;
Populate migration
------------------
:eql-statement:
Populate the current migration with system-generated statements.
.. eql:synopsis::
populate migration ;
Description
^^^^^^^^^^^
The command ``populate migration`` is used within a migration block started by
:eql:stmt:`start migration` to automatically fill the migration with
system-generated statements to achieve the desired target schema state. If
the system is unable to automatically find a satisfactory sequence of
statements to perform the migration, an error is returned. Issuing ``populate
migration`` outside of a migration block is also an error.
.. warning::
The statements generated by ``populate migration`` may drop schema objects,
which may result in data loss. Make sure to inspect the generated
migration using :eql:stmt:`describe current migration` before running
:eql:stmt:`commit migration`!
Example
^^^^^^^
Start a migration block and populate it with auto-generated statements.
.. code-block:: edgeql
start migration to {
module default {
type User;
};
};
populate migration;
Describe current migration
--------------------------
:eql-statement:
Describe the migration in the current migration block.
.. eql:synopsis::
describe current migration [ as {ddl | json} ];
Description
^^^^^^^^^^^
The command ``describe current migration`` generates a description of
the migration in the current migration block in the specified output
format:
:eql:synopsis:`as ddl`
Show a sequence of statements currently recorded as part of the migration
using valid :ref:`DDL <ref_eql_ddl>` syntax. The output will indicate
if the current migration is fully defined, i.e. the recorded statements
bring the schema to the state specified by :eql:stmt:`start migration`.
:eql:synopsis:`as json`
Provide a machine-readable description of the migration using the following
JSON format:
.. code-block::
{
// Name of the parent migration
"parent": "<parent-migration-name>",
// Whether the confirmed DDL makes the migration complete,
// i.e. there are no more statements to issue.
"complete": {true|false},
// List of confirmed migration statements
"confirmed": [
"<stmt text>",
...
],
// The variants of the next statement
// suggested by the system to advance
// the migration script.
"proposed": {
"statements": [{
"text": "<stmt text template>"
}],
"required-user-input": [
{
"placeholder": "<placeholder variable>",
"prompt": "<statement prompt>"
},
...
],
"confidence": (0..1), // confidence coefficient
"prompt": "<operation prompt>",
"prompt_id": "<prompt id>",
// Whether the operation is considered to be non-destructive.
"data_safe": {true|false}
}
}
Where:
:eql:synopsis:`<stmt text>`
Regular statement text.
:eql:synopsis:`<stmt text template>`
Statement text template with interpolation points using the ``\(name)``
syntax.
:eql:synopsis:`<placeholder variable>`
The name of an interpolation variable in the statement text template
for which the user prompt is given.
:eql:synopsis:`<statement prompt>`
The text of a user prompt for an interpolation variable.
:eql:synopsis:`<operation prompt>`
Prompt for the proposed migration step.
:eql:synopsis:`<prompt id>`
An opaque string identifier for a particular operation prompt.
The client should not repeat prompts with the same prompt id.
Commit migration
----------------
:eql-statement:
Commit the current migration to the database.
.. eql:synopsis::
commit migration ;
Description
^^^^^^^^^^^
The command ``commit migration`` executes all the commands defined by
the current migration and records the migration as the most recent
migration in the database.
Issuing ``commit migration`` outside of a migration block initiated
by :eql:stmt:`start migration` is an error.
Example
^^^^^^^
Create and execute the current migration:
.. code-block:: edgeql
commit migration;
Reset schema to initial
-----------------------
:eql-statement:
Reset the database schema to its initial state.
.. eql:synopsis::
reset schema to initial ;
.. warning::
This command will drop all entities and, as a consequence, all data. You
won't want to use this statement on a production instance unless you want
to lose all that instance's data.
Migration rewrites DDL commands
===============================
Migration rewrites allow you to change the migration history as long as your
final schema matches the current database schema.
Start migration rewrite
-----------------------
Start a migration rewrite.
.. eql:synopsis::
start migration rewrite ;
Once the migration rewrite is started, you can run any arbitrary DDL until you
are ready to :ref:`commit <ref_eql_ddl_migrations_rewrites_commit>` your new
migration history. The most useful DDL in this context will be :ref:`create
migration <ref_eql_ddl_migrations_create>` statements, which will allow you to
create a sequence of migrations that will become your new migration history.
Declare savepoint
-----------------
Establish a new savepoint within the current migration rewrite.
.. eql:synopsis::
declare savepoint <savepoint-name> ;
Parameters
^^^^^^^^^^
:eql:synopsis:`<savepoint-name>`
The name which will be used to identify the new savepoint if you need to
later release it or roll back to it.
Release savepoint
-----------------
Destroys a savepoint previously defined in the current migration rewrite.
.. eql:synopsis::
release savepoint <savepoint-name> ;
Parameters
^^^^^^^^^^
:eql:synopsis:`<savepoint-name>`
The name of the savepoint to be released.
Rollback to savepoint
---------------------
Rollback to the named savepoint.
.. eql:synopsis::
rollback to savepoint <savepoint-name> ;
All changes made after the savepoint are discarded. The savepoint remains valid
and can be rolled back to again later, if needed.
Parameters
^^^^^^^^^^
:eql:synopsis:`<savepoint-name>`
The name of the savepoint to roll back to.
Rollback
--------
Rollback the entire migration rewrite.
.. eql:synopsis::
rollback ;
All updates made within the transaction are discarded.
.. _ref_eql_ddl_migrations_rewrites_commit:
Commit migration rewrite
------------------------
Commit a migration rewrite.
.. eql:synopsis::
commit migration rewrite ;
================================================================================
.. File: modules.rst
.. _ref_datamodel_modules:
.. _ref_eql_sdl_modules:
=======
Modules
=======
Each |branch| has a schema consisting of several **modules**, each with
a unique name. Modules can be used to organize large schemas into
logical units. In practice, though, most users put their entire
schema inside a single module called ``default``.
.. code-block:: sdl
module default {
# declare types here
}
.. _ref_name_resolution:
Name resolution
===============
When you define a module that references schema objects from another module,
you must use a *fully-qualified* name in the form
``other_module_name::object_name``:
.. code-block:: sdl
module A {
type User extending B::AbstractUser;
}
module B {
abstract type AbstractUser {
required name: str;
}
}
Reserved module names
=====================
The following module names are reserved by |Gel| and contain pre-defined
types, utility functions, and operators:
* ``std``: standard types, functions, and operators in the :ref:`standard
library <ref_std>`
* ``math``: algebraic and statistical :ref:`functions <ref_std_math>`
* ``cal``: local (non-timezone-aware) and relative date/time :ref:`types and
functions <ref_std_datetime>`
* ``schema``: types describing the :ref:`introspection
<ref_datamodel_introspection>` schema
* ``sys``: system-wide entities, such as user roles and
:ref:`databases <ref_datamodel_databases>`
* ``cfg``: configuration and settings
Modules are containers
======================
They can contain types, functions, and other modules. Here's an example of an
empty module:
.. code-block:: sdl
module my_module {}
And here's an example of a module with a type:
.. code-block:: sdl
module my_module {
type User {
required name: str;
}
}
Nested modules
==============
.. code-block:: sdl
module dracula {
type Person {
required name: str;
multi places_visited: City;
strength: int16;
}
module combat {
function fight(
one: dracula::Person,
two: dracula::Person
) -> str
using (
(one.name ?? 'Fighter 1') ++ ' wins!'
IF (one.strength ?? 0) > (two.strength ?? 0)
ELSE (two.name ?? 'Fighter 2') ++ ' wins!'
);
}
}
You can chain together module names in a fully-qualified name to traverse a
tree of nested modules. For example, to call the ``fight`` function in the
nested module example above, you would use
``dracula::combat::fight(<arguments>)``.
Declaring modules
=================
This section describes the syntax to declare a module in your schema.
Syntax
------
.. sdl:synopsis::
module <ModuleName> "{"
[ <schema-declarations> ]
...
"}"
Define a nested module:
.. sdl:synopsis::
module <ParentModuleName> "{"
[ <schema-declarations> ]
module <ModuleName> "{"
[ <schema-declarations> ]
"}"
...
"}"
Description
^^^^^^^^^^^
The module block declaration defines a new module similar to the
:eql:stmt:`create module` command, but it also allows putting the
module content as nested declarations:
:sdl:synopsis:`<schema-declarations>`
Define various schema items that belong to this module.
Unlike :eql:stmt:`create module`, a module block with the
same name can appear multiple times in an SDL document. In that case
all blocks with the same name are merged into a single module under
that name. For example:
.. code-block:: sdl
module my_module {
abstract type Named {
required name: str;
}
}
module my_module {
type User extending Named;
}
The above is equivalent to:
.. code-block:: sdl
module my_module {
abstract type Named {
required name: str;
}
type User extending Named;
}
Typically, in the documentation examples of SDL the *module block* is
omitted and instead its contents are described without assuming which
specific module they belong to.
It's also possible to declare modules implicitly. In this style, SDL
declaration uses a :ref:`fully-qualified name <ref_name_resolution>` for the
item that is being declared. The *module* part of the *fully-qualified* name
implies that a module by that name will be automatically created in the
schema. The following declaration is equivalent to the previous examples,
but it declares module ``my_module`` implicitly:
.. code-block:: sdl
abstract type my_module::Named {
required name: str;
}
type my_module::User extending my_module::Named;
A module block can be nested inside another module block to create a nested
module. If you want to reference an entity in a nested module by its
fully-qualified name, you will need to include all of the containing
modules' names: ``<ParentModuleName>::<ModuleName>::<EntityName>``
.. _ref_eql_ddl_modules:
DDL commands
============
This section describes the low-level DDL commands for creating and dropping
modules. You typically don't need to use these commands directly, but
knowing about them is useful for reviewing migrations.
Create module
-------------
:eql-statement:
Create a new module.
.. eql:synopsis::
create module [ <parent-name>:: ] <name>
[ if not exists ];
There's a :ref:`corresponding SDL declaration <ref_eql_sdl_modules>`
for a module, although in SDL a module declaration is likely to also
include that module's content.
Description
^^^^^^^^^^^
The command ``create module`` defines a new module for the current
:versionreplace:`database;5.0:branch`. The name of the new module must be
distinct from any existing module in the current
:versionreplace:`database;5.0:branch`. Unlike :ref:`SDL module declaration
<ref_eql_sdl_modules>` the ``create module`` command does not have sub-commands;
module contents are created separately.
Parameters
^^^^^^^^^^
:eql:synopsis:`if not exists`
Normally, creating a module that already exists is an error, but
with this flag the command will succeed. It is useful for scripts
that add something to a module or, if the module is missing, the
module is created as well.
Examples
^^^^^^^^
Create a new module:
.. code-block:: edgeql
create module payments;
Create a new nested module:
.. code-block:: edgeql
create module payments::currencies;
Drop module
-----------
:eql-statement:
Remove a module.
.. eql:synopsis::
drop module <name> ;
Description
^^^^^^^^^^^
The command ``drop module`` removes an existing empty module from the
current :versionreplace:`database;5.0:branch`. If the module contains any
schema items, this command will fail.
Examples
^^^^^^^^
Remove a module:
.. code-block:: edgeql
drop module payments;
================================================================================
.. File: mutation_rewrites.rst
.. _ref_datamodel_mutation_rewrites:
=================
Mutation rewrites
=================
.. index:: rewrite, insert, update, using, __subject__, __specified__, __old__,
modify, modification
Mutation rewrites allow you to intercept database mutations (i.e.,
:ref:`inserts <ref_eql_insert>` and/or :ref:`updates <ref_eql_update>`) and set
the value of a property or link to the result of an expression you define. They
can be defined in your schema.
Mutation rewrites are complementary to :ref:`triggers
<ref_datamodel_triggers>`. While triggers are unable to modify the triggering
object, mutation rewrites are built for that purpose.
Example: last modified
======================
Here's an example of a mutation rewrite that updates a property of a ``Post``
type to reflect the time of the most recent modification:
.. code-block:: sdl
type Post {
required title: str;
required body: str;
modified: datetime {
rewrite insert, update using (datetime_of_statement())
}
}
Every time a ``Post`` is updated, the mutation rewrite will be triggered,
updating the ``modified`` property:
.. code-block:: edgeql-repl
db> insert Post {
... title := 'One wierd trick to fix all your spelling errors'
... };
{default::Post {id: 19e024dc-d3b5-11ed-968c-37f5d0159e5f}}
db> select Post {title, modified};
{
default::Post {
title: 'One wierd trick to fix all your spelling errors',
modified: <datetime>'2023-04-05T13:23:49.488335Z',
},
}
db> update Post
... filter .id = <uuid>'19e024dc-d3b5-11ed-968c-37f5d0159e5f'
... set {title := 'One weird trick to fix all your spelling errors'};
{default::Post {id: 19e024dc-d3b5-11ed-968c-37f5d0159e5f}}
db> select Post {title, modified};
{
default::Post {
title: 'One weird trick to fix all your spelling errors',
modified: <datetime>'2023-04-05T13:25:04.119641Z',
},
}
In some cases, you will want different rewrites depending on the type of query.
Here, we will add an ``insert`` rewrite and an ``update`` rewrite:
.. code-block:: sdl
type Post {
required title: str;
required body: str;
created: datetime {
rewrite insert using (datetime_of_statement())
}
modified: datetime {
rewrite update using (datetime_of_statement())
}
}
With this schema, inserts will set the ``Post`` object's ``created`` property
while updates will set the ``modified`` property:
.. code-block:: edgeql-repl
db> insert Post {
... title := 'One wierd trick to fix all your spelling errors'
... };
{default::Post {id: 19e024dc-d3b5-11ed-968c-37f5d0159e5f}}
db> select Post {title, created, modified};
{
default::Post {
title: 'One wierd trick to fix all your spelling errors',
created: <datetime>'2023-04-05T13:23:49.488335Z',
modified: {},
},
}
db> update Post
... filter .id = <uuid>'19e024dc-d3b5-11ed-968c-37f5d0159e5f'
... set {title := 'One weird trick to fix all your spelling errors'};
{default::Post {id: 19e024dc-d3b5-11ed-968c-37f5d0159e5f}}
db> select Post {title, created, modified};
{
default::Post {
title: 'One weird trick to fix all your spelling errors',
created: <datetime>'2023-04-05T13:23:49.488335Z',
modified: <datetime>'2023-04-05T13:25:04.119641Z',
},
}
.. note::
Each property may have a single ``insert`` and a single ``update`` mutation
rewrite rule, or they may have a single rule that covers both.
Mutation context
================
.. index:: rewrite, __subject__, __specified__, __old__
Inside the rewrite rule's expression, you have access to a few special values:
* ``__subject__`` refers to the object type with the new property and link
values.
* ``__specified__`` is a named tuple with a key for each property or link in
the type and a boolean value indicating whether this value was explicitly set
in the mutation.
* ``__old__`` refers to the object type with the previous property and link
values (available for update-only mutation rewrites).
Here are some examples of the special values in use. Maybe your blog hosts
articles about particularly controversial topics. You could use ``__subject__``
to enforce a "cooling off" period before publishing a blog post:
.. code-block:: sdl
type Post {
required title: str;
required body: str;
publish_time: datetime {
rewrite insert, update using (
__subject__.publish_time ?? datetime_of_statement() +
cal::to_relative_duration(days := 10)
)
}
}
Here we take the post's ``publish_time`` if set or the time the statement is
executed and add 10 days to it. That should give our authors time to consider
if they want to make any changes before a post goes live.
You can omit ``__subject__`` in many cases and achieve the same thing:
.. code-block:: sdl-diff
type Post {
required title: str;
required body: str;
publish_time: datetime {
rewrite insert, update using (
- __subject__.publish_time ?? datetime_of_statement() +
+ .publish_time ?? datetime_of_statement() +
cal::to_relative_duration(days := 10)
)
}
}
but only if the path prefix has not changed. In the following schema, for
example, the ``__subject__`` in the rewrite rule is required, because in the
context of the nested ``select`` query, the leading dot resolves from the
``User`` path:
.. code-block:: sdl
type Post {
required title: str;
required body: str;
author_email: str;
author_name: str {
rewrite insert, update using (
(select User {name} filter .email = __subject__.author_email).name
)
}
}
type User {
name: str;
email: str;
}
.. note::
Learn more about how this works in our documentation on :ref:`path
resolution <ref_eql_path_resolution>`.
Using ``__specified__``, we can determine which fields were specified in the
mutation. This would allow us to track when a single property was last modified
as in the ``title_modified`` property in this schema:
.. code-block:: sdl
type Post {
required title: str;
required body: str;
title_modified: datetime {
rewrite update using (
datetime_of_statement()
if __specified__.title
else __old__.title_modified
)
}
}
``__specified__.title`` will be ``true`` if that value was set as part of the
update, and this rewrite mutation rule will update ``title_modified`` to
``datetime_of_statement()`` in that case.
Another way you might use this is to set a default value but allow overriding:
.. code-block:: sdl
type Post {
required title: str;
required body: str;
modified: datetime {
rewrite update using (
datetime_of_statement()
if not __specified__.modified
else .modified
)
}
}
Here, we rewrite ``modified`` on updates to ``datetime_of_statement()`` unless
``modified`` was set in the update. In that case, we allow the specified value
to be set. This is different from a :ref:`default
<ref_datamodel_props_default_values>` value because the rewrite happens on each
update whereas a default value is applied only on insert of a new object.
One shortcoming in using ``__specified__`` to decide whether to update the
``modified`` property is that we still don't know whether the value changed —
only that it was specified in the query. It's possible the value specified was
the same as the existing value. You'd need to check the value itself to decide
if it has changed.
This is easy enough for a single value, but what if you want a global
``modified`` property that is updated only if any of the properties or links
were changed? That could get cumbersome quickly for an object of any
complexity.
Instead, you might try casting ``__subject__`` and ``__old__`` to ``json`` and
comparing them:
.. code-block:: sdl
type Post {
required title: str;
required body: str;
modified: datetime {
rewrite update using (
datetime_of_statement()
if <json>__subject__ {**} != <json>__old__ {**}
else __old__.modified
)
}
}
Lastly, if we want to add an ``author`` property that can be set for each write
and keep a history of all the authors, we can do this with the help of
``__old__``:
.. code-block:: sdl
type Post {
required title: str;
required body: str;
author: str;
all_authors: array<str> {
default := <array<str>>[];
rewrite update using (
__old__.all_authors
++ [__subject__.author]
);
}
}
On insert, our ``all_authors`` property will get initialized to an empty array
of strings. We will rewrite updates to concatenate that array with an array
containing the new author value.
Cached computed
===============
.. index:: cached computeds, caching computeds
Mutation rewrites can be used to effectively create a cached computed value as
demonstrated with the ``byline`` property in this schema:
.. code-block:: sdl
type Post {
required title: str;
required body: str;
author: str;
created: datetime {
rewrite insert using (datetime_of_statement())
}
byline: str {
rewrite insert, update using (
'by ' ++
__subject__.author ++
' on ' ++
to_str(__subject__.created, 'Mon DD, YYYY')
)
}
}
The ``byline`` property will be updated on each insert or update, but the value
will not need to be calculated at read time like a proper :ref:`computed
property <ref_datamodel_computed>`.
.. _ref_eql_sdl_mutation_rewrites:
.. _ref_eql_sdl_mutation_rewrites_syntax:
Declaring mutation rewrites
===========================
This section describes the syntax to declare mutation rewrites in your schema.
Syntax
------
Define a new mutation rewrite corresponding to the :ref:`more explicit DDL
commands <ref_eql_ddl_mutation_rewrites>`.
.. sdl:synopsis::
rewrite {insert | update} [, ...]
using <expr>
Mutation rewrites must be defined inside a property or link block.
Description
^^^^^^^^^^^
This declaration defines a new trigger with the following options:
:eql:synopsis:`insert | update [, ...]`
The query type (or types) the rewrite runs on. Separate multiple values
with commas to invoke the same rewrite for multiple types of queries.
:eql:synopsis:`<expr>`
The expression to be evaluated to produce the new value of the property.
.. _ref_eql_ddl_mutation_rewrites:
DDL commands
============
This section describes the low-level DDL commands for creatin and
dropping mutation rewrites. You typically don't need to use these commands
directly, but knowing about them is useful for reviewing migrations.
Create rewrite
--------------
:eql-statement:
Define a new mutation rewrite.
When creating a new property or link:
.. eql:synopsis::
{create | alter} type <type-name> "{"
create { property | link } <prop-or-link-name>: <type> "{"
create rewrite {insert | update} [, ...]
using <expr>
"}" ;
"}" ;
When altering an existing property or link:
.. eql:synopsis::
{create | alter} type <type-name> "{"
alter { property | link } <prop-or-link-name> "{"
create rewrite {insert | update} [, ...]
using <expr>
"}" ;
"}" ;
Description
^^^^^^^^^^^
The command ``create rewrite`` nested under ``create type`` or ``alter type``
and then under ``create property/link`` or ``alter property/link`` defines a
new mutation rewrite for the given property or link on the given object.
Parameters
^^^^^^^^^^
:eql:synopsis:`<type-name>`
The name (optionally module-qualified) of the type containing the rewrite.
:eql:synopsis:`<prop-or-link-name>`
The name (optionally module-qualified) of the property or link being
rewritten.
:eql:synopsis:`insert | update [, ...]`
The query type (or types) that are rewritten. Separate multiple values with
commas to invoke the same rewrite for multiple types of queries.
Examples
^^^^^^^^
Declare two mutation rewrites on new properties: one that sets a ``created``
property when a new object is inserted and one that sets a ``modified``
property on each update:
.. code-block:: edgeql
alter type User {
create property created: datetime {
create rewrite insert using (datetime_of_statement());
};
create property modified: datetime {
create rewrite update using (datetime_of_statement());
};
};
Drop rewrite
------------
:eql-statement:
Drop a mutation rewrite.
.. eql:synopsis::
alter type <type-name> "{"
alter property <prop-or-link-name> "{"
drop rewrite {insert | update} ;
"}" ;
"}" ;
Description
^^^^^^^^^^^
The command ``drop rewrite`` inside an ``alter type`` block and further inside
an ``alter property`` block removes the definition of an existing mutation
rewrite on the specified property or link of the specified type.
Parameters
^^^^^^^^^^
:eql:synopsis:`<type-name>`
The name (optionally module-qualified) of the type containing the rewrite.
:eql:synopsis:`<prop-or-link-name>`
The name (optionally module-qualified) of the property or link being
rewritten.
:eql:synopsis:`insert | update [, ...]`
The query type (or types) that are rewritten. Separate multiple values with
commas to invoke the same rewrite for multiple types of queries.
Example
^^^^^^^
Remove the ``insert`` rewrite of the ``created`` property on the ``User`` type:
.. code-block:: edgeql
alter type User {
alter property created {
drop rewrite insert;
};
};
.. list-table::
:class: seealso
* - **See also**
* - :ref:`Introspection > Mutation rewrites
<ref_datamodel_introspection_mutation_rewrites>`
================================================================================
.. File: objects.rst
.. _ref_datamodel_object_types:
============
Object Types
============
.. index:: type, tables, models
*Object types* are the primary components of a Gel schema. They are
analogous to SQL *tables* or ORM *models*, and consist of :ref:`properties
<ref_datamodel_props>` and :ref:`links <ref_datamodel_links>`.
Properties
==========
Properties are used to attach primitive/scalar data to an object type.
For the full documentation on properties, see :ref:`ref_datamodel_props`.
.. code-block:: sdl
type Person {
email: str;
}
Using in a query:
.. code-block:: edgeql
select Person {
email
};
Links
=====
Links are used to define relationships between object types. For the full
documentation on links, see :ref:`ref_datamodel_links`.
.. code-block:: sdl
type Person {
email: str;
best_friend: Person;
}
Using in a query:
.. code-block:: edgeql
select Person {
email,
best_friend: {
email
}
};
ID
==
.. index:: uuid, primary key
There's no need to manually declare a primary key on your object types. All
object types automatically contain a property ``id`` of type ``UUID`` that's
*required*, *globally unique*, *readonly*, and has an index on it.
The ``id`` is assigned upon creation and cannot be changed.
Using in a query:
.. code-block:: edgeql
select Person { id };
select Person { email } filter .id = <uuid>'123e4567-e89b-...';
Abstract types
==============
.. index:: abstract, inheritance
Object types can either be *abstract* or *non-abstract*. By default all object
types are non-abstract. You can't create or store instances of abstract types
(a.k.a. mixins), but they're a useful way to share functionality and
structure among other object types.
.. code-block:: sdl
abstract type HasName {
first_name: str;
last_name: str;
}
.. _ref_datamodel_objects_inheritance:
.. _ref_eql_sdl_object_types_inheritance:
Inheritance
===========
.. index:: extending, extends, subtypes, supertypes
Object types can *extend* other object types. The extending type (AKA the
*subtype*) inherits all links, properties, indexes, constraints, etc. from its
*supertypes*.
.. code-block:: sdl
abstract type HasName {
first_name: str;
last_name: str;
}
type Person extending HasName {
email: str;
best_friend: Person;
}
Using in a query:
.. code-block:: edgeql
select Person {
first_name,
email,
best_friend: {
last_name
}
};
.. _ref_datamodel_objects_multiple_inheritance:
Multiple Inheritance
====================
Object types can extend more than one type — that's called
*multiple inheritance*. This mechanism allows building complex object
types out of combinations of more basic types.
.. note::
Gel's multiple inheritance should not be confused with the multiple
inheritance of C++ or Python, where the complexity usually arises
from fine-grained mixing of logic. Gel's multiple inheritance is
structural and allows for natural composition.
.. code-block:: sdl-diff
abstract type HasName {
first_name: str;
last_name: str;
}
+ abstract type HasEmail {
+ email: str;
+ }
- type Person extending HasName {
+ type Person extending HasName, HasEmail {
- email: str;
best_friend: Person;
}
If multiple supertypes share links or properties, those properties must be
of the same type and cardinality.
.. _ref_eql_sdl_object_types:
.. _ref_eql_sdl_object_types_syntax:
Defining object types
=====================
This section describes the syntax to declare object types in your schema.
Syntax
------
.. sdl:synopsis::
[abstract] type <TypeName> [extending <supertype> [, ...] ]
[ "{"
[ <annotation-declarations> ]
[ <property-declarations> ]
[ <link-declarations> ]
[ <constraint-declarations> ]
[ <index-declarations> ]
...
"}" ]
Description
^^^^^^^^^^^
This declaration defines a new object type with the following options:
:eql:synopsis:`abstract`
If specified, the created type will be *abstract*.
:eql:synopsis:`<TypeName>`
The name (optionally module-qualified) of the new type.
:eql:synopsis:`extending <supertype> [, ...]`
Optional clause specifying the *supertypes* of the new type.
Use of ``extending`` creates a persistent type relationship
between the new subtype and its supertype(s). Schema modifications
to the supertype(s) propagate to the subtype.
References to supertypes in queries will also include objects of
the subtype.
If the same *link* name exists in more than one supertype, or
is explicitly defined in the subtype and at least one supertype,
then the data types of the link targets must be *compatible*.
If there is no conflict, the links are merged to form a single
link in the new type.
These sub-declarations are allowed in the ``Type`` block:
:sdl:synopsis:`<annotation-declarations>`
Set object type :ref:`annotation <ref_eql_sdl_annotations>`
to a given *value*.
:sdl:synopsis:`<property-declarations>`
Define a concrete :ref:`property <ref_eql_sdl_props>` for this object type.
:sdl:synopsis:`<link-declarations>`
Define a concrete :ref:`link <ref_eql_sdl_links>` for this object type.
:sdl:synopsis:`<constraint-declarations>`
Define a concrete :ref:`constraint <ref_eql_sdl_constraints>` for this
object type.
:sdl:synopsis:`<index-declarations>`
Define an :ref:`index <ref_eql_sdl_indexes>` for this object type.
.. _ref_eql_ddl_object_types:
DDL commands
============
This section describes the low-level DDL commands for creating, altering, and
dropping object types. You typically don't need to use these commands directly,
but knowing about them is useful for reviewing migrations.
Create type
-----------
:eql-statement:
:eql-haswith:
Define a new object type.
.. eql:synopsis::
[ with <with-item> [, ...] ]
create [abstract] type <name> [ extending <supertype> [, ...] ]
[ "{" <subcommand>; [...] "}" ] ;
# where <subcommand> is one of
create annotation <annotation-name> := <value>
create link <link-name> ...
create property <property-name> ...
create constraint <constraint-name> ...
create index on <index-expr>
Description
^^^^^^^^^^^
The command ``create type`` defines a new object type for use in the
current |branch|.
If *name* is qualified with a module name, then the type is created
in that module, otherwise it is created in the current module.
The type name must be distinct from that of any existing schema item
in the module.
Parameters
^^^^^^^^^^
Most sub-commands and options of this command are identical to the
:ref:`SDL object type declaration <ref_eql_sdl_object_types_syntax>`,
with some additional features listed below:
:eql:synopsis:`with <with-item> [, ...]`
Alias declarations.
The ``with`` clause allows specifying module aliases
that can be referenced by the command. See :ref:`ref_eql_statements_with`
for more information.
The following subcommands are allowed in the ``create type`` block:
:eql:synopsis:`create annotation <annotation-name> := <value>`
Set object type :eql:synopsis:`<annotation-name>` to
:eql:synopsis:`<value>`.
See :eql:stmt:`create annotation` for details.
:eql:synopsis:`create link <link-name> ...`
Define a new link for this object type. See
:eql:stmt:`create link` for details.
:eql:synopsis:`create property <property-name> ...`
Define a new property for this object type. See
:eql:stmt:`create property` for details.
:eql:synopsis:`create constraint <constraint-name> ...`
Define a concrete constraint for this object type. See
:eql:stmt:`create constraint` for details.
:eql:synopsis:`create index on <index-expr>`
Define a new :ref:`index <ref_datamodel_indexes>`
using *index-expr* for this object type. See
:eql:stmt:`create index` for details.
Example
^^^^^^^
Create an object type ``User``:
.. code-block:: edgeql
create type User {
create property name: str;
};
Alter type
----------
:eql-statement:
:eql-haswith:
Change the definition of an object type.
.. eql:synopsis::
[ with <with-item> [, ...] ]
alter type <name>
[ "{" <subcommand>; [...] "}" ] ;
[ with <with-item> [, ...] ]
alter type <name> <subcommand> ;
# where <subcommand> is one of
rename to <newname>
extending <parent> [, ...]
create annotation <annotation-name> := <value>
alter annotation <annotation-name> := <value>
drop annotation <annotation-name>
create link <link-name> ...
alter link <link-name> ...
drop link <link-name> ...
create property <property-name> ...
alter property <property-name> ...
drop property <property-name> ...
create constraint <constraint-name> ...
alter constraint <constraint-name> ...
drop constraint <constraint-name> ...
create index on <index-expr>
drop index on <index-expr>
Description
^^^^^^^^^^^
The command ``alter type`` changes the definition of an object type.
*name* must be a name of an existing object type, optionally qualified
with a module name.
Parameters
^^^^^^^^^^
:eql:synopsis:`with <with-item> [, ...]`
Alias declarations.
The ``with`` clause allows specifying module aliases
that can be referenced by the command. See :ref:`ref_eql_statements_with`
for more information.
:eql:synopsis:`<name>`
The name (optionally module-qualified) of the type being altered.
:eql:synopsis:`extending <parent> [, ...]`
Alter the supertype list. The full syntax of this subcommand is:
.. eql:synopsis::
extending <parent> [, ...]
[ first | last | before <exparent> | after <exparent> ]
This subcommand makes the type a subtype of the specified list
of supertypes. The requirements for the parent-child relationship
are the same as when creating an object type.
It is possible to specify the position in the parent list
using the following optional keywords:
* ``first`` -- insert parent(s) at the beginning of the
parent list,
* ``last`` -- insert parent(s) at the end of the parent list,
* ``before <parent>`` -- insert parent(s) before an
existing *parent*,
* ``after <parent>`` -- insert parent(s) after an existing
*parent*.
:eql:synopsis:`alter annotation <annotation-name>;`
Alter object type annotation :eql:synopsis:`<annotation-name>`.
See :eql:stmt:`alter annotation` for details.
:eql:synopsis:`drop annotation <annotation-name>`
Remove object type :eql:synopsis:`<annotation-name>`.
See :eql:stmt:`drop annotation` for details.
:eql:synopsis:`alter link <link-name> ...`
Alter the definition of a link for this object type. See
:eql:stmt:`alter link` for details.
:eql:synopsis:`drop link <link-name>`
Remove a link item from this object type. See
:eql:stmt:`drop link` for details.
:eql:synopsis:`alter property <property-name> ...`
Alter the definition of a property item for this object type.
See :eql:stmt:`alter property` for details.
:eql:synopsis:`drop property <property-name>`
Remove a property item from this object type. See
:eql:stmt:`drop property` for details.
:eql:synopsis:`alter constraint <constraint-name> ...`
Alter the definition of a constraint for this object type. See
:eql:stmt:`alter constraint` for details.
:eql:synopsis:`drop constraint <constraint-name>;`
Remove a constraint from this object type. See
:eql:stmt:`drop constraint` for details.
:eql:synopsis:`drop index on <index-expr>`
Remove an :ref:`index <ref_datamodel_indexes>` defined as *index-expr*
from this object type. See :eql:stmt:`drop index` for details.
All the subcommands allowed in the ``create type`` block are also
valid subcommands for the ``alter type`` block.
Example
^^^^^^^
Alter the ``User`` object type to make ``name`` required:
.. code-block:: edgeql
alter type User {
alter property name {
set required;
}
};
Drop type
---------
:eql-statement:
:eql-haswith:
Remove the specified object type from the schema.
.. eql:synopsis::
drop type <name> ;
Description
^^^^^^^^^^^
The command ``drop type`` removes the specified object type from the
schema. All subordinate schema items defined on this type,
such as links and indexes, are removed as well.
Example
^^^^^^^
Remove the ``User`` object type:
.. code-block:: edgeql
drop type User;
.. list-table::
:class: seealso
* - **See also**
* - :ref:`Introspection > Object types
<ref_datamodel_introspection_object_types>`
* - :ref:`Cheatsheets > Object types <ref_cheatsheet_object_types>`
================================================================================
.. File: primitives.rst
.. _ref_datamodel_primitives:
==========
Primitives
==========
|Gel| has a robust type system consisting of primitive and object types.
types. Primitive types are used to declare *properties* on object types,
as query and function arguments, as as well as in other contexts.
.. _ref_datamodel_scalars:
Built-in scalar types
=====================
Gel comes with a range of built-in scalar types, such as:
* String: :eql:type:`str`
* Boolean: :eql:type:`bool`
* Various numeric types: :eql:type:`int16`, :eql:type:`int32`,
:eql:type:`int64`, :eql:type:`float32`, :eql:type:`float64`, :eql:type:`bigint`, :eql:type:`decimal`
* JSON: :eql:type:`json`,
* UUID: :eql:type:`uuid`,
* Date/time: :eql:type:`datetime`, :eql:type:`duration`
:eql:type:`cal::local_datetime`, :eql:type:`cal::local_date`,
:eql:type:`cal::local_time`, :eql:type:`cal::relative_duration`,
:eql:type:`cal::date_duration`
* Miscellaneous: :eql:type:`sequence`, :eql:type:`bytes`, etc.
Custom scalars
==============
You can extend built-in scalars with additional constraints or annotations.
Here's an example of a non-negative custom ``int64`` variant:
.. code-block:: sdl
scalar type posint64 extending int64 {
constraint min_value(0);
}
.. _ref_datamodel_enums:
Enums
=====
Enum types are created by extending the abstract :eql:type:`enum` type, e.g.:
.. code-block:: sdl
scalar type Color extending enum<Red, Green, Blue>;
type Shirt {
color: Color;
}
which can be queries with:
.. code-block:: edgeql
select Shirt filter .color = Color.Red;
For a full reference on enum types, see the :ref:`Enum docs <ref_std_enum>`.
.. _ref_datamodel_arrays:
Arrays
======
Arrays store zero or more primitive values of the same type in an ordered list.
Arrays cannot contain object types or other arrays, but can contain virtually
any other type.
.. code-block:: sdl
type Person {
str_array: array<str>;
json_array: array<json>;
tuple_array: array<tuple<float32, float32>>;
# INVALID: arrays of object types not allowed:
# friends: array<Person>
# INVALID: arrays cannot be nested:
# nested_array: array<array<str>>
# VALID: arrays can contain tuples with arrays in them
nested_array_via_tuple: array<tuple<array<str>>>
}
Array syntax in EdgeQL is very intuitive (indexing starts at ``0``):
.. code-block:: edgeql
select [1, 2, 3];
select [1, 2, 3][1] = 2; # true
For a full reference on array types, see the :ref:`Array docs <ref_std_array>`.
.. _ref_datamodel_tuples:
Tuples
======
Like arrays, tuples are ordered sequences of primitive data. Unlike arrays,
each element of a tuple can have a distinct type. Tuple elements can be *any
type*, including primitives, objects, arrays, and other tuples.
.. code-block:: sdl
type Person {
unnamed_tuple: tuple<str, bool, int64>;
nested_tuple: tuple<tuple<str, tuple<bool, int64>>>;
tuple_of_arrays: tuple<array<str>, array<int64>>;
}
Optionally, you can assign a *key* to each element of the tuple. Tuples
containing explicit keys are known as *named tuples*. You must assign keys to
all elements (or none of them).
.. code-block:: sdl
type BlogPost {
metadata: tuple<title: str, published: bool, upvotes: int64>;
}
Named and unnamed tuples are the same data structure under the hood. You can
add, remove, and change keys in a tuple type after it's been declared. For
details, see :ref:`Tuples <ref_eql_literal_tuple>`.
.. note::
When you query an *unnamed* tuple using one of EdgeQL's
:ref:`client libraries <ref_clients_index>`, its value is converted to a
list/array. When you fetch a named tuple, it is converted into an
object/dictionary/hashmap depending on the language.
.. _ref_datamodel_ranges:
Ranges
======
Ranges represent some interval of values. The intervals can be bound or
unbound on either end. They can also be empty, containing no values. Only
some scalar types have corresponding range types:
- Numeric ranges: ``range<int32>``, ``range<int64>``, ``range<float32>``,
``range<float64>``, ``range<decimal>``
- Date/time ranges: ``range<datetime>``, ``range<cal::local_datetime>``,
``range<cal::local_date>``
Example:
.. code-block:: sdl
type DieRoll {
values: range<int64>;
}
For a full reference on ranges, functions and operators see the
:ref:`Range docs <ref_std_range>`.
Sequences
=========
To represent an auto-incrementing integer property, declare a custom scalar
that extends the abstract ``sequence`` type. Creating a sequence type
initializes a global ``int64`` counter that auto-increments whenever a new
object is created. All properties that point to the same sequence type will
share the counter.
.. code-block:: sdl
scalar type ticket_number extending sequence;
type Ticket {
number: ticket_number;
rendered_number := 'TICKET-\(.number)';
}
For a full reference on sequences, see the :ref:`Sequence docs <ref_std_sequence>`.
.. _ref_eql_sdl_scalars:
.. _ref_eql_sdl_scalars_syntax:
Declaring scalars
=================
This section describes the syntax to declare a custom scalar type in your
schema.
Syntax
------
.. sdl:synopsis::
[abstract] scalar type <TypeName> [extending <supertype> [, ...] ]
[ "{"
[ <annotation-declarations> ]
[ <constraint-declarations> ]
...
"}" ]
Description
^^^^^^^^^^^
This declaration defines a new object type with the following options:
:eql:synopsis:`abstract`
If specified, the created scalar type will be *abstract*.
:eql:synopsis:`<TypeName>`
The name (optionally module-qualified) of the new scalar type.
:eql:synopsis:`extending <supertype>`
Optional clause specifying the *supertype* of the new type.
If :eql:synopsis:`<supertype>` is an
:eql:type:`enumerated type <std::enum>` declaration then
an enumerated scalar type is defined.
Use of ``extending`` creates a persistent type relationship
between the new subtype and its supertype(s). Schema modifications
to the supertype(s) propagate to the subtype.
The valid SDL sub-declarations are listed below:
:sdl:synopsis:`<annotation-declarations>`
Set scalar type :ref:`annotation <ref_eql_sdl_annotations>`
to a given *value*.
:sdl:synopsis:`<constraint-declarations>`
Define a concrete :ref:`constraint <ref_eql_sdl_constraints>` for
this scalar type.
.. _ref_eql_ddl_scalars:
DDL commands
============
This section describes the low-level DDL commands for creating, altering, and
dropping scalar types. You typically don't need to use these commands directly,
but knowing about them is useful for reviewing migrations.
Create scalar
-------------
:eql-statement:
:eql-haswith:
Define a new scalar type.
.. eql:synopsis::
[ with <with-item> [, ...] ]
create [abstract] scalar type <name> [ extending <supertype> ]
[ "{" <subcommand>; [...] "}" ] ;
# where <subcommand> is one of
create annotation <annotation-name> := <value>
create constraint <constraint-name> ...
Description
^^^^^^^^^^^
The command ``create scalar type`` defines a new scalar type for use in the
current |branch|.
If *name* is qualified with a module name, then the type is created
in that module, otherwise it is created in the current module.
The type name must be distinct from that of any existing schema item
in the module.
If the ``abstract`` keyword is specified, the created type will be
*abstract*.
All non-abstract scalar types must have an underlying core
implementation. For user-defined scalar types this means that
``create scalar type`` must have another non-abstract scalar type
as its *supertype*.
The most common use of ``create scalar type`` is to define a scalar
subtype with constraints.
Most sub-commands and options of this command are identical to the
:ref:`SDL scalar type declaration <ref_eql_sdl_scalars_syntax>`. The
following subcommands are allowed in the ``create scalar type`` block:
:eql:synopsis:`create annotation <annotation-name> := <value>;`
Set scalar type's :eql:synopsis:`<annotation-name>` to
:eql:synopsis:`<value>`.
See :eql:stmt:`create annotation` for details.
:eql:synopsis:`create constraint <constraint-name> ...`
Define a new constraint for this scalar type. See
:eql:stmt:`create constraint` for details.
Examples
^^^^^^^^
Create a new non-negative integer type:
.. code-block:: edgeql
create scalar type posint64 extending int64 {
create constraint min_value(0);
};
Create a new enumerated type:
.. code-block:: edgeql
create scalar type Color
extending enum<Black, White, Red>;
Alter scalar
------------
:eql-statement:
:eql-haswith:
Alter the definition of a scalar type.
.. eql:synopsis::
[ with <with-item> [, ...] ]
alter scalar type <name>
"{" <subcommand>; [...] "}" ;
# where <subcommand> is one of
rename to <newname>
extending ...
create annotation <annotation-name> := <value>
alter annotation <annotation-name> := <value>
drop annotation <annotation-name>
create constraint <constraint-name> ...
alter constraint <constraint-name> ...
drop constraint <constraint-name> ...
Description
^^^^^^^^^^^
The command ``alter scalar type`` changes the definition of a scalar type.
*name* must be a name of an existing scalar type, optionally qualified
with a module name.
The following subcommands are allowed in the ``alter scalar type`` block:
:eql:synopsis:`rename to <newname>;`
Change the name of the scalar type to *newname*.
:eql:synopsis:`extending ...`
Alter the supertype list. It works the same way as in
:eql:stmt:`alter type`.
:eql:synopsis:`alter annotation <annotation-name>;`
Alter scalar type :eql:synopsis:`<annotation-name>`.
See :eql:stmt:`alter annotation` for details.
:eql:synopsis:`drop annotation <annotation-name>`
Remove scalar type's :eql:synopsis:`<annotation-name>` from
:eql:synopsis:`<value>`.
See :eql:stmt:`drop annotation` for details.
:eql:synopsis:`alter constraint <constraint-name> ...`
Alter the definition of a constraint for this scalar type. See
:eql:stmt:`alter constraint` for details.
:eql:synopsis:`drop constraint <constraint-name>`
Remove a constraint from this scalar type. See
:eql:stmt:`drop constraint` for details.
All the subcommands allowed in the ``create scalar type`` block are also
valid subcommands for ``alter scalar type`` block.
Examples
^^^^^^^^
Define a new constraint on a scalar type:
.. code-block:: edgeql
alter scalar type posint64 {
create constraint max_value(100);
};
Add one more label to an enumerated type:
.. code-block:: edgeql
alter scalar type Color
extending enum<Black, White, Red, Green>;
Drop scalar
-----------
:eql-statement:
:eql-haswith:
Remove a scalar type.
.. eql:synopsis::
[ with <with-item> [, ...] ]
drop scalar type <name> ;
Description
^^^^^^^^^^^
The command ``drop scalar type`` removes a scalar type.
Parameters
^^^^^^^^^^
*name*
The name (optionally qualified with a module name) of an existing
scalar type.
Example
^^^^^^^
Remove a scalar type:
.. code-block:: edgeql
drop scalar type posint64;
================================================================================
.. File: properties.rst
.. _ref_datamodel_props:
==========
Properties
==========
.. index:: property, primitive types, fields, columns
Properties are used to associate primitive data with an :ref:`object type <ref_datamodel_object_types>` or :ref:`link <ref_datamodel_link_properties>`.
.. code-block:: sdl
type Player {
property email: str;
points: int64;
is_online: bool;
}
Properties are associated with a *name* (e.g. ``email``) and a primitive
type (e.g. ``str``).
The term *primitive type* is an umbrella term that
encompasses :ref:`scalar types <ref_datamodel_scalars>` like ``str``,
:ref:`arrays <ref_datamodel_arrays>` and :ref:`tuples <ref_datamodel_tuples>`,
:ref:`and more <ref_datamodel_primitives>`.
Properties can be declared using the ``property`` keyword if that improves
readability, or it can be ommitted.
Required properties
===================
.. index:: required, optional, not null
Properties can be either ``optional`` (the default) or ``required``.
E.g. here we have a ``User`` type that's guaranteed to have an ``email``,
but ``name`` is optional and can be empty:
.. code-block:: sdl
type User {
required email: str;
optional name: str;
}
Since ``optional`` keyword is the default, we can omit it:
.. code-block:: sdl
type User {
required email: str;
name: str;
}
.. _ref_datamodel_props_cardinality:
Cardinality
===========
.. index:: cardinality, single, multi
Properties have a **cardinality**:
* ``prop: type``, short for ``single prop: type``, can either hold zero or
one value (that's the default).
* ``multi prop: type`` can hold an *unordered set* of values, which can
be zero, one, or more values of type ``type``.
For example:
.. code-block:: sdl
type User {
# "single" keyword isn't necessary here:
# properties are single by default
single name: str;
# an unordered set of strings
multi nicknames: str;
# an unordered set of string arrays
multi set_of_arrays: array<str>;
}
multi vs. arrays
================
``multi`` properties are stored differently than arrays under the hood.
Essentially they are stored in a separate table ``(owner_id, value)``.
.. rubric:: Pros of multi properties vs. arrays
* ``multi`` properties allow efficient search and mutation of large sets.
Arrays are much slower for those operations.
* ``multi`` properties can have indexes and constraints appied to
individual elements; arrays, in general, cannot.
* It's easier to aggregate sets and operate on them than on arrays.
In many cases arrays would require :ref:`unpacking them into a set
<ref_eql_set_array_conversion>` first.
.. rubric:: Cons of multi properties vs. arrays
* On small sets, arrays are faster to retrieve.
* It's easier to retain the original order in arrays. Arrays are ordered,
but sets are not.
.. _ref_datamodel_props_default_values:
Default values
==============
.. index:: default
Properties can have a default value. This default can be a static value or an
arbitrary EdgeQL expression, which will be evaluated upon insertion.
.. code-block:: sdl
type Player {
required points: int64 {
default := 0;
}
required latitude: float64 {
default := (360 * random() - 180);
}
}
Readonly properties
===================
.. index:: readonly, immutable
Properties can be marked as ``readonly``. In the example below, the
``User.external_id`` property can be set at the time of creation but not
modified thereafter.
.. code-block:: sdl
type User {
required external_id: uuid {
readonly := true;
}
}
Constraints
===========
.. index:: constraint
Properties can be augmented wth constraints. The example below showcases a
subset of Gel's built-in constraints.
.. code-block:: sdl
type BlogPost {
title: str {
constraint exclusive; # all post titles must be unique
constraint min_len_value(8);
constraint max_len_value(30);
constraint regexp(r'^[A-Za-z0-9 ]+$');
}
status: str {
constraint one_of('Draft', 'InReview', 'Published');
}
upvotes: int64 {
constraint min_value(0);
constraint max_value(9999);
}
}
You can constrain properties with arbitrary :ref:`EdgeQL <ref_edgeql>` expressions
returning ``bool``. To reference the value of the property, use the special scope
keyword ``__subject__``.
.. code-block:: sdl
type BlogPost {
title: str {
constraint expression on (
__subject__ = str_trim(__subject__)
);
}
}
The constraint above guarantees that ``BlogPost.title`` doesn't contain any
leading or trailing whitespace by checking that the raw string is equal to the
trimmed version. It uses the built-in :eql:func:`str_trim` function.
For a full reference of built-in constraints, see the :ref:`Constraints
reference <ref_std_constraints>`.
Annotations
===========
.. index:: annotation, metadata, title, description, deprecated
Properties can contain annotations, small human-readable notes. The built-in
annotations are ``title``, ``description``, and ``deprecated``. You may also
declare :ref:`custom annotation types <ref_datamodel_inheritance_annotations>`.
.. code-block:: sdl
type User {
email: str {
annotation title := 'Email address';
}
}
Abstract properties
===================
.. index:: abstract property
Properties can be *concrete* (the default) or *abstract*. Abstract properties
are declared independent of a source or target, can contain :ref:`annotations
<ref_datamodel_annotations>`, constraints, indexes, and can be marked as
``readonly``.
.. code-block:: sdl
abstract property email_prop {
annotation title := 'An email address';
readonly := true;
}
type Student {
# inherits annotations and "readonly := true"
email: str {
extending email_prop;
};
}
Overloading properties
======================
Any time we want to amend an inherited property (e.g. to add a constraint),
the ``overloaded`` keyword must be used. This is to prevent unintentional
overloading due to a name clash:
.. code-block:: sdl
abstract type Named {
optional name: str;
}
type User extending Named {
# make "name" required
overloaded required name: str;
}
.. _ref_eql_sdl_props:
.. _ref_eql_sdl_props_syntax:
Declaring properties
====================
Syntax
------
This section describes the syntax to declare properties in your schema.
.. sdl:synopsis::
# Concrete property form used inside type declaration:
[ overloaded ] [{required | optional}] [{single | multi}]
[ property ] <name> : <type>
[ "{"
[ extending <base> [, ...] ; ]
[ default := <expression> ; ]
[ readonly := {true | false} ; ]
[ <annotation-declarations> ]
[ <constraint-declarations> ]
...
"}" ]
# Computed property form used inside type declaration:
[{required | optional}] [{single | multi}]
[ property ] <name> := <expression>;
# Computed property form used inside type declaration (extended):
[ overloaded ] [{required | optional}] [{single | multi}]
property <name> [: <type>]
[ "{"
using (<expression>) ;
[ extending <base> [, ...] ; ]
[ <annotation-declarations> ]
[ <constraint-declarations> ]
...
"}" ]
# Abstract property form:
abstract property [<module>::]<name>
[ "{"
[extending <base> [, ...] ; ]
[ readonly := {true | false} ; ]
[ <annotation-declarations> ]
...
"}" ]
Description
^^^^^^^^^^^
There are several forms of ``property`` declaration, as shown in the
syntax synopsis above. The first form is the canonical definition
form, the second and third forms are used for defining a
:ref:`computed property <ref_datamodel_computed>`, and the last
one is a form to define an ``abstract property``.
The abstract form allows declaring the property directly inside
a :ref:`module <ref_eql_sdl_modules>`.
Concrete property forms are always used as sub-declarations
for an :ref:`object type <ref_eql_sdl_object_types>` or
a :ref:`link <ref_eql_sdl_links>`.
The following options are available:
:eql:synopsis:`overloaded`
If specified, indicates that the property is inherited and that some
feature of it may be altered in the current object type. It is an
error to declare a property as *overloaded* if it is not inherited.
:eql:synopsis:`required`
If specified, the property is considered *required* for the parent
object type. It is an error for an object to have a required
property resolve to an empty value. Child properties **always**
inherit the *required* attribute, i.e it is not possible to make a
required property non-required by extending it.
:eql:synopsis:`optional`
This is the default qualifier assumed when no qualifier is
specified, but it can also be specified explicitly. The property
is considered *optional* for the parent object type, i.e. it is
possible for the property to resolve to an empty value.
:eql:synopsis:`multi`
Specifies that there may be more than one instance of this
property in an object, in other words, ``Object.property`` may
resolve to a set of a size greater than one.
:eql:synopsis:`single`
Specifies that there may be at most *one* instance of this
property in an object, in other words, ``Object.property`` may
resolve to a set of a size not greater than one. ``single`` is
assumed if nether ``multi`` nor ``single`` qualifier is specified.
:eql:synopsis:`extending <base> [, ...]`
Optional clause specifying the *parents* of the new property item.
Use of ``extending`` creates a persistent schema relationship
between the new property and its parents. Schema modifications
to the parent(s) propagate to the child.
:eql:synopsis:`<type>`
The type must be a valid :ref:`type expression <ref_eql_types>`
denoting a non-abstract scalar or a container type.
The valid SDL sub-declarations are listed below:
:eql:synopsis:`default := <expression>`
Specifies the default value for the property as an EdgeQL expression.
The default value is used in an ``insert`` statement if an explicit
value for this property is not specified.
The expression must be :ref:`Stable <ref_reference_volatility>`.
:eql:synopsis:`readonly := {true | false}`
If ``true``, the property is considered *read-only*.
Modifications of this property are prohibited once an object is
created. All of the derived properties **must** preserve the
original *read-only* value.
:sdl:synopsis:`<annotation-declarations>`
Set property :ref:`annotation <ref_eql_sdl_annotations>`
to a given *value*.
:sdl:synopsis:`<constraint-declarations>`
Define a concrete :ref:`constraint <ref_eql_sdl_constraints>` on
the property.
.. _ref_eql_ddl_props:
DDL commands
============
This section describes the low-level DDL commands for creating, altering, and
dropping properties. You typically don't need to use these commands directly,
but knowing about them is useful for reviewing migrations.
.. _ref_eql_ddl_props_syntax:
Create property
---------------
:eql-statement:
:eql-haswith:
Define a new property.
.. eql:synopsis::
[ with <with-item> [, ...] ]
{create|alter} {type|link} <SourceName> "{"
[ ... ]
create [{required | optional}] [{single | multi}]
property <name>
[ extending <base> [, ...] ] : <type>
[ "{" <subcommand>; [...] "}" ] ;
[ ... ]
"}"
# Computed property form:
[ with <with-item> [, ...] ]
{create|alter} {type|link} <SourceName> "{"
[ ... ]
create [{required | optional}] [{single | multi}]
property <name> := <expression>;
[ ... ]
"}"
# Abstract property form:
[ with <with-item> [, ...] ]
create abstract property [<module>::]<name> [extending <base> [, ...]]
[ "{" <subcommand>; [...] "}" ]
# where <subcommand> is one of
set default := <expression>
set readonly := {true | false}
create annotation <annotation-name> := <value>
create constraint <constraint-name> ...
Parameters
^^^^^^^^^^
Most sub-commands and options of this command are identical to the
:ref:`SDL property declaration <ref_eql_sdl_props_syntax>`. The
following subcommands are allowed in the ``create property`` block:
:eql:synopsis:`set default := <expression>`
Specifies the default value for the property as an EdgeQL expression.
Other than a slight syntactical difference this is the same as the
corresponding SDL declaration.
:eql:synopsis:`set readonly := {true | false}`
Specifies whether the property is considered *read-only*. Other
than a slight syntactical difference this is the same as the
corresponding SDL declaration.
:eql:synopsis:`create annotation <annotation-name> := <value>`
Set property :eql:synopsis:`<annotation-name>` to
:eql:synopsis:`<value>`.
See :eql:stmt:`create annotation` for details.
:eql:synopsis:`create constraint`
Define a concrete constraint on the property.
See :eql:stmt:`create constraint` for details.
Examples
^^^^^^^^
Define a new link ``address`` on the ``User`` object type:
.. code-block:: edgeql
alter type User {
create property address: str
};
Define a new :ref:`computed property <ref_datamodel_computed>`
``number_of_connections`` on the ``User`` object type counting the
number of interests:
.. code-block:: edgeql
alter type User {
create property number_of_connections :=
count(.interests)
};
Define a new abstract link ``orderable`` with ``weight`` property:
.. code-block:: edgeql
create abstract link orderable {
create property weight: std::int64
};
Alter property
--------------
:eql-statement:
:eql-haswith:
Change the definition of a property.
.. eql:synopsis::
[ with <with-item> [, ...] ]
{create | alter} {type | link} <source> "{"
[ ... ]
alter property <name>
[ "{" ] <subcommand>; [...] [ "}" ];
[ ... ]
"}"
[ with <with-item> [, ...] ]
alter abstract property [<module>::]<name>
[ "{" ] <subcommand>; [...] [ "}" ];
# where <subcommand> is one of
set default := <expression>
reset default
set readonly := {true | false}
reset readonly
rename to <newname>
extending ...
set required [using (<conversion-expr)]
set optional
reset optionality
set single [using (<conversion-expr)]
set multi
reset cardinality [using (<conversion-expr)]
set type <typename> [using (<conversion-expr)]
reset type
using (<computed-expr>)
create annotation <annotation-name> := <value>
alter annotation <annotation-name> := <value>
drop annotation <annotation-name>
create constraint <constraint-name> ...
alter constraint <constraint-name> ...
drop constraint <constraint-name> ...
Parameters
^^^^^^^^^^
:eql:synopsis:`<source>`
The name of an object type or link on which the property is defined.
May be optionally qualified with module.
:eql:synopsis:`<name>`
The unqualified name of the property to modify.
:eql:synopsis:`<module>`
Optional name of the module to create or alter the abstract property in.
If not specified, the current module is used.
The following subcommands are allowed in the ``alter link`` block:
:eql:synopsis:`rename to <newname>`
Change the name of the property to :eql:synopsis:`<newname>`.
All concrete properties inheriting from this property are
also renamed.
:eql:synopsis:`extending ...`
Alter the property parent list. The full syntax of this subcommand is:
.. eql:synopsis::
extending <name> [, ...]
[ first | last | before <parent> | after <parent> ]
This subcommand makes the property a child of the specified list
of parent property items. The requirements for the parent-child
relationship are the same as when creating a property.
It is possible to specify the position in the parent list
using the following optional keywords:
* ``first`` -- insert parent(s) at the beginning of the
parent list,
* ``last`` -- insert parent(s) at the end of the parent list,
* ``before <parent>`` -- insert parent(s) before an
existing *parent*,
* ``after <parent>`` -- insert parent(s) after an existing
*parent*.
:eql:synopsis:`set required [using (<conversion-expr)]`
Make the property *required*.
:eql:synopsis:`set optional`
Make the property no longer *required* (i.e. make it *optional*).
:eql:synopsis:`reset optionality`
Reset the optionality of the property to the default value (``optional``),
or, if the property is inherited, to the value inherited from properties in
supertypes.
:eql:synopsis:`set single [using (<conversion-expr)]`
Change the maximum cardinality of the property set to *one*. Only
valid for concrete properties.
:eql:synopsis:`set multi`
Change the maximum cardinality of the property set to
*greater than one*. Only valid for concrete properties.
:eql:synopsis:`reset cardinality [using (<conversion-expr)]`
Reset the maximum cardinality of the property to the default value
(``single``), or, if the property is inherited, to the value inherited
from properties in supertypes.
:eql:synopsis:`set type <typename> [using (<conversion-expr)]`
Change the type of the property to the specified
:eql:synopsis:`<typename>`. The optional ``using`` clause specifies
a conversion expression that computes the new property value from the old.
The conversion expression must return a singleton set and is evaluated
on each element of ``multi`` properties. A ``using`` clause must be
provided if there is no implicit or assignment cast from old to new type.
:eql:synopsis:`reset type`
Reset the type of the property to the type inherited from properties
of the same name in supertypes. It is an error to ``reset type`` on
a property that is not inherited.
:eql:synopsis:`using (<computed-expr>)`
Change the expression of a :ref:`computed property
<ref_datamodel_computed>`. Only valid for concrete properties.
:eql:synopsis:`alter annotation <annotation-name>;`
Alter property annotation :eql:synopsis:`<annotation-name>`.
See :eql:stmt:`alter annotation` for details.
:eql:synopsis:`drop annotation <annotation-name>;`
Remove property annotation :eql:synopsis:`<annotation-name>`.
See :eql:stmt:`drop annotation` for details.
:eql:synopsis:`alter constraint <constraint-name> ...`
Alter the definition of a constraint for this property. See
:eql:stmt:`alter constraint` for details.
:eql:synopsis:`drop constraint <constraint-name>;`
Remove a constraint from this property. See
:eql:stmt:`drop constraint` for details.
:eql:synopsis:`reset default`
Remove the default value from this property, or reset it to the value
inherited from a supertype, if the property is inherited.
:eql:synopsis:`reset readonly`
Set property writability to the default value (writable), or, if the
property is inherited, to the value inherited from properties in
supertypes.
All the subcommands allowed in the ``create property`` block are also
valid subcommands for ``alter property`` block.
Examples
^^^^^^^^
Set the ``title`` annotation of property ``address`` of object type
``User`` to ``"Home address"``:
.. code-block:: edgeql
alter type User {
alter property address
create annotation title := "Home address";
};
Add a maximum-length constraint to property ``address`` of object type
``User``:
.. code-block:: edgeql
alter type User {
alter property address {
create constraint max_len_value(500);
};
};
Rename the property ``weight`` of link ``orderable`` to ``sort_by``:
.. code-block:: edgeql
alter abstract link orderable {
alter property weight rename to sort_by;
};
Redefine the :ref:`computed property <ref_datamodel_computed>`
``number_of_connections`` to be the number of friends:
.. code-block:: edgeql
alter type User {
alter property number_of_connections using (
count(.friends)
)
};
Drop property
-------------
:eql-statement:
:eql-haswith:
Remove a property from the schema.
.. eql:synopsis::
[ with <with-item> [, ...] ]
{create|alter} type <TypeName> "{"
[ ... ]
drop link <name>
[ ... ]
"}"
[ with <with-item> [, ...] ]
drop abstract property <name> ;
Example
^^^^^^^
Remove property ``address`` from type ``User``:
.. code-block:: edgeql
alter type User {
drop property address;
};
================================================================================
.. File: triggers.rst
.. _ref_datamodel_triggers:
.. _ref_eql_sdl_triggers:
========
Triggers
========
.. index:: trigger, after insert, after update, after delete, for each, for all,
when, do, __new__, __old__
Triggers allow you to define an expression to be executed whenever a given
query type is run on an object type. The original query will *trigger* your
pre-defined expression to run in a transaction along with the original query.
These can be defined in your schema.
Important notes
===============
Triggers are an advanced feature and have some caveats that
you should be aware of.
Consider using mutation rewrites
--------------------------------
Triggers cannot be used to *modify* the object that set off the trigger,
although they can be used with :eql:func:`assert` to do *validation* on
that object. If you need to modify the object, you can use :ref:`mutation
rewrites <ref_datamodel_mutation_rewrites>`.
Unified trigger query execution
-------------------------------
All queries within triggers, along with the initial triggering query, are
compiled into a single combined SQL query under the hood. Keep this in mind
when designing triggers that modify existing records. If multiple ``update``
queries within your triggers target the same object, only one of these
queries will ultimately be executed. To ensure all desired updates on an
object are applied, consolidate them into a single ``update`` query within
one trigger, instead of distributing them across multiple updates.
Multi-stage trigger execution
-----------------------------
In some cases, a trigger can cause another trigger to fire. When this
happens, Gel completes all the triggers fired by the initial query
before kicking off a new "stage" of triggers. In the second stage, any
triggers fired by the initial stage of triggers will fire. Gel will
continue adding trigger stages until all triggers are complete.
The exception to this is when triggers would cause a loop or would cause
the same trigger to be run in two different stages. These triggers will
generate an error.
Data visibility
---------------
Any query in your trigger will return the state of the database *after* the
triggering query. If this query's results include the object that flipped
the trigger, the results will contain that object in the same state as
``__new__``.
Example: audit log
==================
Here's an example that creates a simple **audit log** type so that we can keep
track of what's happening to our users in a database. First, we will create a
``Log`` type:
.. code-block:: sdl
type Log {
action: str;
timestamp: datetime {
default := datetime_current();
}
target_name: str;
change: str;
}
With the ``Log`` type in place, we can write some triggers that will
automatically create ``Log`` objects for any insert, update, or delete queries
on the ``Person`` type:
.. code-block:: sdl
type Person {
required name: str;
trigger log_insert after insert for each do (
insert Log {
action := 'insert',
target_name := __new__.name
}
);
trigger log_update after update for each do (
insert Log {
action := 'update',
target_name := __new__.name,
change := __old__.name ++ '->' ++ __new__.name
}
);
trigger log_delete after delete for each do (
insert Log {
action := 'delete',
target_name := __old__.name
}
);
}
In a trigger's expression, we have access to the ``__old__`` and/or ``__new__``
variables which capture the object before and after the query. Triggers on
``update`` can use both variables. Triggers on ``delete`` can use ``__old__``.
Triggers on ``insert`` can use ``__new__``.
Now, whenever we run a query, we get a log entry as well:
.. code-block:: edgeql-repl
db> insert Person {name := 'Jonathan Harker'};
{default::Person {id: b4d4e7e6-bd19-11ed-8363-1737d8d4c3c3}}
db> select Log {action, timestamp, target_name, change};
{
default::Log {
action: 'insert',
timestamp: <datetime>'2023-03-07T18:56:02.403817Z',
target_name: 'Jonathan Harker',
change: {}
}
}
db> update Person filter .name = 'Jonathan Harker'
... set {name := 'Mina Murray'};
{default::Person {id: b4d4e7e6-bd19-11ed-8363-1737d8d4c3c3}}
db> select Log {action, timestamp, target_name, change};
{
default::Log {
action: 'insert',
timestamp: <datetime>'2023-03-07T18:56:02.403817Z',
target_name: 'Jonathan Harker',
change: {}
},
default::Log {
action: 'update',
timestamp: <datetime>'2023-03-07T18:56:39.520889Z',
target_name: 'Mina Murray',
change: 'Jonathan Harker->Mina Murray'
},
}
db> delete Person filter .name = 'Mina Murray';
{default::Person {id: b4d4e7e6-bd19-11ed-8363-1737d8d4c3c3}}
db> select Log {action, timestamp, target_name, change};
{
default::Log {
action: 'insert',
timestamp: <datetime>'2023-03-07T18:56:02.403817Z',
target_name: 'Jonathan Harker',
change: {}
},
default::Log {
action: 'update',
timestamp: <datetime>'2023-03-07T18:56:39.520889Z',
target_name: 'Mina Murray',
change: 'Jonathan Harker->Mina Murray'
},
default::Log {
action: 'delete',
timestamp: <datetime>'2023-03-07T19:00:52.636084Z',
target_name: 'Mina Murray',
change: {}
},
}
Our audit logging works, but the update logs have a major shortcoming: they
log an update even when nothing changes. Any time an ``update`` query runs,
we get a log, even if the values are the same. We can prevent that by
using the trigger's ``when`` to run the trigger conditionally. Here's a
rework of our ``update`` logging query:
.. code-block:: sdl-invalid
trigger log_update after update for each
when (__old__.name != __new__.name)
do (
insert Log {
action := 'update',
target_name := __new__.name,
change := __old__.name ++ '->' ++ __new__.name
}
);
If this object were more complicated and we had many properties to compare,
we could use a ``json`` cast to compare them all in one shot:
.. code-block:: sdl-invalid
trigger log_update after update for each
when (<json>__old__ {**} != <json>__new__ {**})
do (
insert Log {
action := 'update',
target_name := __new__.name,
change := __old__.name ++ '->' ++ __new__.name
}
);
You might find that one log entry per row is too granular or too noisy for your
use case. In that case, a ``for all`` trigger may be a better fit. Here's a
schema that changes the ``Log`` type so that each object can log multiple
writes by making ``target_name`` and ``change`` :ref:`multi properties
<ref_datamodel_props_cardinality>` and switches to ``for all`` triggers:
.. code-block:: sdl-diff
type Log {
action: str;
timestamp: datetime {
default := datetime_current();
}
- target_name: str;
- change: str;
+ multi target_name: str;
+ multi change: str;
}
type Person {
required name: str;
- trigger log_insert after insert for each do (
+ trigger log_insert after insert for all do (
insert Log {
action := 'insert',
target_name := __new__.name
}
);
- trigger log_update after update for each do (
+ trigger log_update after update for all do (
insert Log {
action := 'update',
target_name := __new__.name,
change := __old__.name ++ '->' ++ __new__.name
}
);
- trigger log_delete after delete for each do (
+ trigger log_delete after delete for all do (
insert Log {
action := 'delete',
target_name := __old__.name
}
);
}
Under this new schema, each query matching the trigger gets a single ``Log``
object instead of one ``Log`` object per row:
.. code-block:: edgeql-repl
db> for name in {'Jonathan Harker', 'Mina Murray', 'Dracula'}
... union (
... insert Person {name := name}
... );
{
default::Person {id: 3836f9c8-d393-11ed-9638-3793d3a39133},
default::Person {id: 38370a8a-d393-11ed-9638-d3e9b92ca408},
default::Person {id: 38370abc-d393-11ed-9638-5390f3cbd375},
}
db> select Log {action, timestamp, target_name, change};
{
default::Log {
action: 'insert',
timestamp: <datetime>'2023-03-07T19:12:21.113521Z',
target_name: {'Jonathan Harker', 'Mina Murray', 'Dracula'},
change: {},
},
}
db> for change in {
... (old_name := 'Jonathan Harker', new_name := 'Jonathan'),
... (old_name := 'Mina Murray', new_name := 'Mina')
... }
... union (
... update Person filter .name = change.old_name set {
... name := change.new_name
... }
... );
{
default::Person {id: 3836f9c8-d393-11ed-9638-3793d3a39133},
default::Person {id: 38370a8a-d393-11ed-9638-d3e9b92ca408},
}
db> select Log {action, timestamp, target_name, change};
{
default::Log {
action: 'insert',
timestamp: <datetime>'2023-04-05T09:21:17.514089Z',
target_name: {'Jonathan Harker', 'Mina Murray', 'Dracula'},
change: {},
},
default::Log {
action: 'update',
timestamp: <datetime>'2023-04-05T09:35:30.389571Z',
target_name: {'Jonathan', 'Mina'},
change: {'Jonathan Harker->Jonathan', 'Mina Murray->Mina'},
},
}
Example: validation
===================
.. index:: trigger, validate, assert
Triggers may also be used for validation by calling :eql:func:`assert` inside
the trigger. In this example, the ``Person`` type has two multi links to other
``Person`` objects named ``friends`` and ``enemies``. These two links should be
mutually exclusive, so we have written a trigger to make sure there are no
common objects linked in both.
.. code-block:: sdl
type Person {
required name: str;
multi friends: Person;
multi enemies: Person;
trigger prohibit_frenemies after insert, update for each do (
assert(
not exists (__new__.friends intersect __new__.enemies),
message := "Invalid frenemies",
)
)
}
With this trigger in place, it is impossible to link the same ``Person`` as
both a friend and an enemy of any other person.
.. code-block:: edgeql-repl
db> insert Person {name := 'Quincey Morris'};
{default::Person {id: e4a55480-d2de-11ed-93bd-9f4224fc73af}}
db> insert Person {name := 'Dracula'};
{default::Person {id: e7f2cff0-d2de-11ed-93bd-279780478afb}}
db> update Person
... filter .name = 'Quincey Morris'
... set {
... enemies := (
... select detached Person filter .name = 'Dracula'
... )
... };
{default::Person {id: e4a55480-d2de-11ed-93bd-9f4224fc73af}}
db> update Person
... filter .name = 'Quincey Morris'
... set {
... friends := (
... select detached Person filter .name = 'Dracula'
... )
... };
gel error: GelError: Invalid frenemies
Example: logging
================
Declare a trigger that inserts a ``Log`` object for each new ``User`` object:
.. code-block:: sdl
type User {
required name: str;
trigger log_insert after insert for each do (
insert Log {
action := 'insert',
target_name := __new__.name
}
);
}
Declare a trigger that inserts a ``Log`` object conditionally when an update
query makes a change to a ``User`` object:
.. code-block:: sdl
type User {
required name: str;
trigger log_update after update for each
when (<json>__old__ {**} != <json>__new__ {**})
do (
insert Log {
action := 'update',
target_name := __new__.name,
change := __old__.name ++ '->' ++ __new__.name
}
);
}
.. _ref_eql_sdl_triggers_syntax:
Declaring triggers
==================
This section describes the syntax to declare a trigger in your schema.
Syntax
------
.. sdl:synopsis::
type <type-name> "{"
trigger <name>
after
{insert | update | delete} [, ...]
for {each | all}
[ when (<condition>) ]
do <expr>
"}"
Description
-----------
This declaration defines a new trigger with the following options:
:eql:synopsis:`<type-name>`
The name (optionally module-qualified) of the type to be triggered on.
:eql:synopsis:`<name>`
The name of the trigger.
:eql:synopsis:`insert | update | delete [, ...]`
The query type (or types) to trigger on. Separate multiple values with
commas to invoke the same trigger for multiple types of queries.
:eql:synopsis:`each`
The expression will be evaluated once per modified object. ``__new__`` and
``__old__`` in this context within the expression will refer to a single
object.
:eql:synopsis:`all`
The expression will be evaluted once for the entire query, even if multiple
objects were modified. ``__new__`` and ``__old__`` in this context within
the expression refer to sets of the modified objects.
.. versionadded:: 4.0
:eql:synopsis:`when (<condition>)`
Optionally provide a condition for the trigger. If the condition is
met, the trigger will run. If not, the trigger is skipped.
:eql:synopsis:`<expr>`
The expression to be evaluated when the trigger is invoked.
The trigger name must be distinct from that of any existing trigger
on the same type.
.. _ref_eql_ddl_triggers:
DDL commands
============
This section describes the low-level DDL commands for creating and dropping
triggers. You typically don't need to use these commands directly, but
knowing about them is useful for reviewing migrations.
Create trigger
--------------
:eql-statement:
:ref:`Define <ref_eql_sdl_triggers>` a new trigger.
.. eql:synopsis::
{create | alter} type <type-name> "{"
create trigger <name>
after
{insert | update | delete} [, ...]
for {each | all}
[ when (<condition>) ]
do <expr>
"}"
Description
^^^^^^^^^^^
The command ``create trigger`` nested under ``create type`` or ``alter type``
defines a new trigger for a given object type.
The trigger name must be distinct from that of any existing trigger
on the same type.
Parameters
^^^^^^^^^^
The options of this command are identical to the
:ref:`SDL trigger declaration <ref_eql_sdl_triggers_syntax>`.
Example
^^^^^^^
Declare a trigger that inserts a ``Log`` object for each new ``User`` object:
.. code-block:: edgeql
alter type User {
create trigger log_insert after insert for each do (
insert Log {
action := 'insert',
target_name := __new__.name
}
);
};
.. versionadded:: 4.0
Declare a trigger that inserts a ``Log`` object conditionally when an update
query makes a change to a ``User`` object:
.. code-block:: edgeql
alter type User {
create trigger log_update after update for each
when (<json>__old__ {**} != <json>__new__ {**})
do (
insert Log {
action := 'update',
target_name := __new__.name,
change := __old__.name ++ '->' ++ __new__.name
}
);
}
Drop trigger
------------
:eql-statement:
Remove a trigger.
.. eql:synopsis::
alter type <type-name> "{"
drop trigger <name>;
"}"
Description
^^^^^^^^^^^
The command ``drop trigger`` inside an ``alter type`` block removes the
definition of an existing trigger on the specified type.
Parameters
^^^^^^^^^^
:eql:synopsis:`<type-name>`
The name (optionally module-qualified) of the type being triggered on.
:eql:synopsis:`<name>`
The name of the trigger.
Example
^^^^^^^
Remove the ``log_insert`` trigger on the ``User`` type:
.. code-block:: edgeql
alter type User {
drop trigger log_insert;
};
.. list-table::
:class: seealso
* - **See also**
* - :ref:`Introspection > Triggers <ref_datamodel_introspection_triggers>`
================================================================================
.. File: analyze.rst
.. _ref_eql_analyze:
Analyze
=======
.. index:: analyze, explain, performance, postgres query planner
Prefix an EdgeQL query with ``analyze`` to run a performance analysis of that
query.
.. code-block:: edgeql-repl
db> analyze select Hero {
... name,
... secret_identity,
... villains: {
... name,
... nemesis: {
... name
... }
... }
... };
──────────────────────────────────────── Query ────────────────────────────────────────
analyze select ➊ Hero {name, secret_identity, ➋ villains: {name, ➌ nemesis: {name}}};
──────────────────────── Coarse-grained Query Plan ────────────────────────
│ Time Cost Loops Rows Width │ Relations
➊ root │ 0.0 69709.48 1.0 0.0 32 │ Hero
╰──➋ .villains │ 0.0 92.9 0.0 0.0 32 │ Villain, Hero.villains
╰──➌ .nemesis │ 0.0 8.18 0.0 0.0 32 │ Hero
.. note::
In addition to using the ``analyze`` statement in the CLI or UI's REPL, you
may also run performance analysis via our CLI's :ref:`analyze command
<ref_cli_gel_analyze>` and the UI's query builder (accessible by running
:ref:`ref_cli_gel_ui` to invoke your instance's UI) by prepending your
query with ``analyze``. This method offers helpful visualizations to to
make it easy to understand your query's performance.
After analyzing a query, you may run the ``\expand`` command in the REPL to see
more fine-grained performance metrics on the previously analyzed query.
.. list-table::
:class: seealso
* - **See also**
* - :ref:`CLI > gel analyze <ref_cli_gel_analyze>`
* - :ref:`Reference > EdgeQL > analyze <ref_eql_statements_analyze>`
================================================================================
.. File: delete.rst
.. _ref_eql_delete:
Delete
======
.. index:: delete
The ``delete`` command is used to delete objects from the database.
.. code-block:: edgeql
delete Hero
filter .name = 'Iron Man';
Clauses
-------
Deletion statements support ``filter``, ``order by``, ``offset``, and ``limit``
clauses. See :ref:`EdgeQL > Select <ref_eql_select>` for full documentation
on these clauses.
.. code-block:: edgeql
delete Hero
filter .name ilike 'the %'
order by .name
offset 10
limit 5;
Link deletion
-------------
.. index:: ConstraintViolationError
Every link is associated with a *link deletion policy*. By default, it isn't
possible to delete an object linked to by another.
.. code-block:: edgeql-repl
db> delete Hero filter .name = "Yelena Belova";
ConstraintViolationError: deletion of default::Hero
(af7076e0-3e98-11ec-abb3-b3435bbe7c7e) is prohibited by link target policy
{}
This deletion failed because Yelena is still in the ``characters`` list of
the Black Widow movie. We must destroy this link before Yelena can be
deleted.
.. code-block:: edgeql-repl
db> update Movie
... filter .title = "Black Widow"
... set {
... characters -= (select Hero filter .name = "Yelena Belova")
... };
{default::Movie {id: af706c7c-3e98-11ec-abb3-4bbf3f18a61a}}
db> delete Hero filter .name = "Yelena Belova";
{default::Hero {id: af7076e0-3e98-11ec-abb3-b3435bbe7c7e}}
To avoid this behavior, we could update the ``Movie.characters`` link to use
the ``allow`` deletion policy.
.. code-block:: sdl-diff
type Movie {
required title: str { constraint exclusive };
required release_year: int64;
- multi characters: Person;
+ multi characters: Person {
+ on target delete allow;
+ };
}
Cascading deletes
^^^^^^^^^^^^^^^^^
.. index:: delete cascade, delete source, delete target, deletion policy
If a link uses the ``delete source`` policy, then deleting a *target* of the
link will also delete the object that links to it (the *source*). This behavior
can be used to implement cascading deletes; be careful with this power!
The full list of deletion policies is documented at :ref:`Schema > Links
<ref_datamodel_link_deletion>`.
Return value
------------
.. index:: delete, returning
A ``delete`` statement returns the set of deleted objects. You can pass this
set into ``select`` to fetch properties and links of the (now-deleted)
objects. This is the last moment this data will be available before being
permanently deleted.
.. code-block:: edgeql-repl
db> with movie := (delete Movie filter .title = "Untitled")
... select movie {id, title};
{default::Movie {
id: b11303c6-40ac-11ec-a77d-d393cdedde83,
title: 'Untitled',
}}
.. list-table::
:class: seealso
* - **See also**
* - :ref:`Reference > Commands > Delete <ref_eql_statements_delete>`
* - :ref:`Cheatsheets > Deleting data <ref_cheatsheet_delete>`
================================================================================
.. File: for.rst
.. _ref_eql_for:
For
===
.. index:: for in, union
EdgeQL supports a top-level ``for`` statement. These "for loops" iterate over
each element of some input set, execute some expression with it, and merge the
results into a single output set.
.. code-block:: edgeql-repl
db> for number in {0, 1, 2, 3}
... union (
... select { number, number + 0.5 }
... );
{0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5}
This statement iterates through each number in the set. Inside the loop, the
``number`` variable is bound to a singleton set. The inner expression is
executed for every element of the input set, and the results of each execution
are merged into a single output set.
.. note::
The ``union`` keyword is required prior to |EdgeDB| 5.0 and is intended to
indicate explicitly that the results of each loop execution are ultimately
merged.
.. versionadded: 5.0
If the body of ``for`` is a statement — ``select``, ``insert``, ``update``,
``delete``, ``group``, or ``with`` — ``union`` and the parentheses
surrounding the statement are no longer required:
.. code-block:: edgeql-repl
db> for number in {0, 1, 2, 3}
... select { number, number + 0.5 }
{0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5}
Bulk inserts
------------
.. index:: bulk inserts
The ``for`` statement is commonly used for bulk inserts.
.. code-block:: edgeql-repl
db> for hero_name in {'Cersi', 'Ikaris', 'Thena'}
... union (
... insert Hero { name := hero_name }
... );
{
default::Hero {id: d7d7e0f6-40ae-11ec-87b1-3f06bed494b9},
default::Hero {id: d7d7f870-40ae-11ec-87b1-f712a4efc3a5},
default::Hero {id: d7d7f8c0-40ae-11ec-87b1-6b8685d56610}
}
This statement iterates through each name in the list of names. Inside the
loop, ``hero_name`` is bound to a ``str`` singleton, so it can be assigned to
``Hero.name``.
Instead of literal sets, it's common to use a :ref:`json <ref_std_json>`
parameter for bulk inserts. This value is then "unpacked" into a set of
``json`` elements and used inside the ``for`` loop:
.. code-block:: edgeql-repl
db> with
... raw_data := <json>$data,
... for item in json_array_unpack(raw_data) union (
... insert Hero { name := <str>item['name'] }
... );
Parameter <json>$data: [{"name":"Sersi"},{"name":"Ikaris"},{"name":"Thena"}]
{
default::Hero {id: d7d7e0f6-40ae-11ec-87b1-3f06bed494b9},
default::Hero {id: d7d7f870-40ae-11ec-87b1-f712a4efc3a5},
default::Hero {id: d7d7f8c0-40ae-11ec-87b1-6b8685d56610}
}
A similar approach can be used for bulk updates.
.. _ref_eql_for_conditional_dml:
Conditional DML
---------------
.. index:: for, if else, unless conflict
.. versionadded:: 4.0
DML is now supported in ``if..else``.
DML (i.e., :ref:`insert <ref_eql_insert>`, :ref:`update <ref_eql_update>`,
:ref:`delete <ref_eql_delete>`) is not supported in :eql:op:`if..else`. If you
need to do one of these conditionally, you can use a ``for`` loop as a
workaround. For example, you might want to write this conditional:
.. code-block::
# 🚫 Does not work
with admin := (select User filter .role = 'admin')
select admin if exists admin
else (insert User {role := 'admin'});
Because of the lack of support for DML in a conditional, this query will fail.
Here's how you can accomplish the same thing using the workaround:
.. code-block:: edgeql
# ✅ Works!
with
admin := (select User filter .role = 'admin'),
new := (for _ in (select () filter not exists admin) union (
insert User {role := 'admin'}
)),
select {admin, new};
The ``admin`` alias represents the condition we want to test for. In this case,
"do we have a ``User`` object with a value of ``admin`` for the ``role``
property?" In the ``new`` alias, we write a ``for`` loop with a ``select``
query that will produce a set with a single value if that object we queried for
does *not* exist. (You can use ``exists`` instead of ``not exists`` in the
nested ``select`` inside the ``for`` loop if you don't want to invert the
condition.)
A set with a single value results in a single iteration of the ``for`` loop.
Inside that loop, we run our conditional DML — in this case to insert an admin
user. Then we ``select`` both aliases to execute both of their queries. The
query will return the ``User`` object. This in effect gives us a query that
will insert a ``User`` object with a ``role`` of ``admin`` if none exists or
return that object if it *does* exist.
.. note::
If you're trying to conditionally run DML in response to a violation of an
exclusivity constraint, you don't need this workaround. You should use
:ref:`unless conflict <ref_eql_insert_conflicts>` instead.
.. list-table::
:class: seealso
* - **See also**
* - :ref:`Reference > Commands > For <ref_eql_statements_for>`
================================================================================
.. File: group.rst
.. _ref_eql_group:
Group
=====
.. index:: group by, group using by, key, grouping, elements, analytics,
aggregate, rollup, cube, partition
EdgeQL supports a top-level ``group`` statement. This is used to partition
sets into subsets based on some parameters. These subsets then can be
additionally aggregated to provide some analytics.
The most basic format is just using the bare :eql:stmt:`group` to group a set
of objects by some property:
.. code-block:: edgeql-repl
db> group Movie by .release_year;
{
{
key: {release_year: 2016},
grouping: {'release_year'},
elements: {
default::Movie {title: 'Captain America: Civil War'},
default::Movie {title: 'Doctor Strange'},
},
},
{
key: {release_year: 2017},
grouping: {'release_year'},
elements: {
default::Movie {title: 'Spider-Man: Homecoming'},
default::Movie {title: 'Thor: Ragnarok'},
},
},
{
key: {release_year: 2018},
grouping: {'release_year'},
elements: {default::Movie {title: 'Ant-Man and the Wasp'}},
},
{
key: {release_year: 2019},
grouping: {'release_year'},
elements: {default::Movie {title: 'Spider-Man: No Way Home'}},
},
{
key: {release_year: 2021},
grouping: {'release_year'},
elements: {default::Movie {title: 'Black Widow'}},
},
...
}
Notice that the result of ``group`` is a set of :ref:`free objects
<ref_eql_select_free_objects>` with three fields:
* ``key``: another free object containing the specific value of the
grouping parameter for a given subset.
* ``grouping``: set of names of grouping parameters, i.e. the specific
names that also appear in the ``key`` free object.
* ``elements``: the actual subset of values that match the ``key``.
In the ``group`` statement, referring to the property in the ``by`` clause
**must** be done by using the leading dot shothand ``.release_year``. The
property name then shows up in ``grouping`` and ``key`` to indicate the
defining characteristics of the particular result. Alternatively, we can give
it an alias in an optional ``using`` clause and then that alias can be used in
the ``by`` clause and will appear in the results:
.. code-block:: edgeql-repl
db> group Movie {title}
... using year := .release_year by year;
{
{
key: {year: 2016},
grouping: {'year'},
elements: {
default::Movie {title: 'Captain America: Civil War'},
default::Movie {title: 'Doctor Strange'},
},
},
{
key: {year: 2017},
grouping: {'year'},
elements: {
default::Movie {title: 'Spider-Man: Homecoming'},
default::Movie {title: 'Thor: Ragnarok'},
},
},
{
key: {year: 2018},
grouping: {'year'},
elements: {default::Movie {title: 'Ant-Man and the Wasp'}},
},
{
key: {year: 2019},
grouping: {'year'},
elements: {default::Movie {title: 'Spider-Man: No Way Home'}},
},
{
key: {year: 2021},
grouping: {'year'},
elements: {default::Movie {title: 'Black Widow'}},
},
...
}
The ``using`` clause is perfect for defining a more complex expression to
group things by. For example, instead of grouping by the ``release_year`` we
can group by the release decade:
.. code-block:: edgeql-repl
db> group Movie {title}
... using decade := .release_year // 10
... by decade;
{
{
{
key: {decade: 200},
grouping: {'decade'},
elements: {
default::Movie {title: 'Spider-Man'},
default::Movie {title: 'Spider-Man 2'},
default::Movie {title: 'Spider-Man 3'},
default::Movie {title: 'Iron Man'},
default::Movie {title: 'The Incredible Hulk'},
},
},
{
key: {decade: 201},
grouping: {'decade'},
elements: {
default::Movie {title: 'Iron Man 2'},
default::Movie {title: 'Thor'},
default::Movie {title: 'Captain America: The First Avenger'},
default::Movie {title: 'The Avengers'},
default::Movie {title: 'Iron Man 3'},
default::Movie {title: 'Thor: The Dark World'},
default::Movie {title: 'Captain America: The Winter Soldier'},
default::Movie {title: 'Ant-Man'},
default::Movie {title: 'Captain America: Civil War'},
default::Movie {title: 'Doctor Strange'},
default::Movie {title: 'Spider-Man: Homecoming'},
default::Movie {title: 'Thor: Ragnarok'},
default::Movie {title: 'Ant-Man and the Wasp'},
default::Movie {title: 'Spider-Man: No Way Home'},
},
},
{
key: {decade: 202},
grouping: {'decade'},
elements: {default::Movie {title: 'Black Widow'}},
},
}
It's also possible to group by more than one parameter, so we can group by
whether the movie ``title`` contains a colon *and* the decade it was released.
Additionally, let's only consider more recent movies, say, released after
2015, so that we're not overwhelmed by all the combination of results:
.. code-block:: edgeql-repl
db> with
... # Apply the group query only to more recent movies
... M := (select Movie filter .release_year > 2015)
... group M {title}
... using
... decade := .release_year // 10,
... has_colon := .title like '%:%'
... by decade, has_colon;
{
{
key: {decade: 201, has_colon: false},
grouping: {'decade', 'has_colon'},
elements: {
default::Movie {title: 'Ant-Man and the Wasp'},
default::Movie {title: 'Doctor Strange'},
},
},
{
key: {decade: 201, has_colon: true},
grouping: {'decade', 'has_colon'},
elements: {
default::Movie {title: 'Captain America: Civil War'},
default::Movie {title: 'Spider-Man: No Way Home'},
default::Movie {title: 'Thor: Ragnarok'},
default::Movie {title: 'Spider-Man: Homecoming'},
},
},
{
key: {decade: 202, has_colon: false},
grouping: {'decade', 'has_colon'},
elements: {default::Movie {title: 'Black Widow'}},
},
}
Once we break a set into partitions, we can also use :ref:`aggregate
<ref_eql_set_aggregate>` functions to provide some analytics about the data.
For example, for the above partitioning (by decade and presence of ``:`` in
the ``title``) we can calculate how many movies are in each subset as well as
the average number of words in the movie titles:
.. code-block:: edgeql-repl
db> with
... # Apply the group query only to more recent movies
... M := (select Movie filter .release_year > 2015),
... groups := (
... group M {title}
... using
... decade := .release_year // 10 - 200,
... has_colon := .title like '%:%'
... by decade, has_colon
... )
... select groups {
... key := .key {decade, has_colon},
... count := count(.elements),
... avg_words := math::mean(
... len(str_split(.elements.title, ' ')))
... };
{
{key: {decade: 1, has_colon: false}, count: 2, avg_words: 3},
{key: {decade: 1, has_colon: true}, count: 4, avg_words: 3},
{key: {decade: 2, has_colon: false}, count: 1, avg_words: 2},
}
.. note::
It is possible to produce results that are grouped in multiple different
ways using :ref:`grouping sets <ref_eql_statements_group>`. This may be
useful in more sophisticated analytics.
.. list-table::
:class: seealso
* - **See also**
* - :ref:`Reference > Commands > Group <ref_eql_statements_group>`
================================================================================
.. File: index.rst
.. versioned-section::
.. _ref_edgeql:
======
EdgeQL
======
.. toctree::
:maxdepth: 3
:hidden:
literals
sets
paths
types
parameters
select
insert
update
delete
for
group
with
analyze
path_resolution
transactions
EdgeQL is a next-generation query language designed to match SQL in power and
surpass it in terms of clarity, brevity, and intuitiveness. It's used to query
the database, insert/update/delete data, modify/introspect the schema, manage
transactions, and more.
Design goals
------------
EdgeQL is a spiritual successor to SQL designed with a few core principles in
mind.
**Compatible with modern languages**. A jaw-dropping amount of effort has been
spent attempting to `bridge the gap <https://en.wikipedia.org/wiki/
Object%E2%80%93relational_impedance_mismatch>`_ between the *relational*
paradigm of SQL and the *object-oriented* nature of modern programming
languages. Gel sidesteps this problem by modeling data in an
*object-relational* way.
**Strongly typed**. EdgeQL is *inextricably tied* to Gel's rigorous
object-oriented type system. The type of all expressions is statically
inferred by Gel.
**Designed for programmers**. EdgeQL prioritizes syntax over keywords; It uses
``{ curly braces }`` to define scopes/structures and the *assignment
operator* ``:=`` to set values. The result is a query language that looks more
like code and less like word soup.
.. All told, EdgeQL syntax contains roughly 180
.. reserved keywords; by comparison Postgres-flavored SQL contains `469
.. <https://www.postgresql.org/docs/current/sql-keywords-appendix.html>`_.
.. **Compiles to SQL**. All EdgeQL queries, no matter how complex, compile to a
.. single PostgreSQL query under the hood. With the exception of ``group by``,
.. EdgeQL is equivalent to SQL in terms of power and expressivity.
**Easy deep querying**. Gel's object-relational nature makes it painless
to write deep, performant queries that traverse links, no ``JOINs`` required.
**Composable**. `Unlike SQL
<https://www.geldata.com/blog/we-can-do-better-than-sql#lack-of-orthogonality>`_,
EdgeQL's syntax is readily composable; queries can be cleanly nested without
worrying about Cartesian explosion.
================================================================================
.. File: insert.rst
.. _ref_eql_insert:
Insert
======
.. index:: insert, returning
The ``insert`` command is used to create instances of object types. The code
samples on this page assume the following schema:
.. code-block:: sdl
module default {
abstract type Person {
required name: str { constraint exclusive };
}
type Hero extending Person {
secret_identity: str;
multi villains := .<nemesis[is Villain];
}
type Villain extending Person {
nemesis: Hero;
}
type Movie {
required title: str { constraint exclusive };
required release_year: int64;
multi characters: Person;
}
}
.. _ref_eql_insert_basic:
Basic usage
-----------
You can ``insert`` instances of any *non-abstract* object type.
.. code-block:: edgeql-repl
db> insert Hero {
... name := "Spider-Man",
... secret_identity := "Peter Parker"
... };
{default::Hero {id: b0fbe9de-3e90-11ec-8c12-ffa2d5f0176a}}
Similar to :ref:`selecting fields <ref_eql_shapes>` in ``select``, ``insert``
statements include a *shape* specified with ``curly braces``; the values of
properties/links are assigned with the ``:=`` operator.
Optional links or properties can be omitted entirely, as well as those with a
``default`` value (like ``id``).
.. code-block:: edgeql-repl
db> insert Hero {
... name := "Spider-Man"
... # secret_identity is omitted
... };
{default::Hero {id: b0fbe9de-3e90-11ec-8c12-ffa2d5f0176a}}
You can only ``insert`` instances of concrete (non-abstract) object types.
.. code-block:: edgeql-repl
db> insert Person {
... name := "The Man With No Name"
... };
error: QueryError: cannot insert into abstract object type 'default::Person'
By default, ``insert`` returns only the inserted object's ``id`` as seen in the
examples above. If you want to get additional data back, you may wrap your
``insert`` with a ``select`` and apply a shape specifying any properties and
links you want returned:
.. code-block:: edgeql-repl
db> select (insert Hero {
... name := "Spider-Man"
... # secret_identity is omitted
... }) {id, name};
{
default::Hero {
id: b0fbe9de-3e90-11ec-8c12-ffa2d5f0176a,
name: "Spider-Man"
}
}
You can use :ref:`ref_eql_with` to tidy this up if you prefer:
.. code-block:: edgeql-repl
db> with NewHero := (insert Hero {
... name := "Spider-Man"
... # secret_identity is omitted
... })
... select NewHero {
... id,
... name,
... }
{
default::Hero {
id: b0fbe9de-3e90-11ec-8c12-ffa2d5f0176a,
name: "Spider-Man"
}
}
.. _ref_eql_insert_links:
Inserting links
---------------
.. index:: inserting links
EdgeQL's composable syntax makes link insertion painless. Below, we insert
"Spider-Man: No Way Home" and include all known heroes and villains as
``characters`` (which is basically true).
.. code-block:: edgeql-repl
db> insert Movie {
... title := "Spider-Man: No Way Home",
... release_year := 2021,
... characters := (
... select Person
... filter .name in {
... 'Spider-Man',
... 'Doctor Strange',
... 'Doc Ock',
... 'Green Goblin'
... }
... )
... };
{default::Movie {id: 9b1cf9e6-3e95-11ec-95a2-138eeb32759c}}
To assign to the ``Movie.characters`` link, we're using a *subquery*. This
subquery is executed and resolves to a set of type ``Person``, which is
assignable to ``characters``. Note that the inner ``select Person`` statement
is wrapped in parentheses; this is required for all subqueries in EdgeQL.
Now let's assign to a *single link*.
.. code-block:: edgeql-repl
db> insert Villain {
... name := "Doc Ock",
... nemesis := (select Hero filter .name = "Spider-Man")
... };
This query is valid because the inner subquery is guaranteed to return at most
one ``Hero`` object, due to the uniqueness constraint on ``Hero.name``. If you
are filtering on a non-exclusive property, use ``assert_single`` to guarantee
that the subquery will return zero or one results. If more than one result is
returned, this query will fail at runtime.
.. code-block:: edgeql-repl
db> insert Villain {
... name := "Doc Ock",
... nemesis := assert_single((
... select Hero
... filter .secret_identity = "Peter B. Parker"
... ))
... };
.. _ref_eql_insert_nested:
Nested inserts
--------------
.. index:: nested inserts
Just as we used subqueries to populate links with existing objects, we can also
execute *nested inserts*.
.. code-block:: edgeql-repl
db> insert Villain {
... name := "The Mandarin",
... nemesis := (insert Hero {
... name := "Shang-Chi",
... secret_identity := "Shaun"
... })
... };
{default::Villain {id: d47888a0-3e7b-11ec-af13-fb68c8777851}}
Now let's write a nested insert for a ``multi`` link.
.. code-block:: edgeql-repl
db> insert Movie {
... title := "Black Widow",
... release_year := 2021,
... characters := {
... (select Hero filter .name = "Black Widow"),
... (insert Hero { name := "Yelena Belova"}),
... (insert Villain {
... name := "Dreykov",
... nemesis := (select Hero filter .name = "Black Widow")
... })
... }
... };
{default::Movie {id: af706c7c-3e98-11ec-abb3-4bbf3f18a61a}}
We are using :ref:`set literal syntax <ref_eql_set_constructor>` to construct a
set literal containing several ``select`` and ``insert`` subqueries. This set
contains a mix of ``Hero`` and ``Villain`` objects; since these are both
subtypes of ``Person`` (the expected type of ``Movie.characters``), this is
valid.
You also can't *assign* to a computed property or link; these fields don't
actually exist in the database.
.. code-block:: edgeql-repl
db> insert Hero {
... name := "Ant-Man",
... villains := (select Villain)
... };
error: QueryError: modification of computed link 'villains' of object type
'default::Hero' is prohibited
.. _ref_eql_insert_with:
With block
----------
.. index:: with insert
In the previous query, we selected Black Widow twice: once in the
``characters`` set and again as the ``nemesis`` of Dreykov. In circumstances
like this, pulling a subquery into a ``with`` block lets you avoid
duplication.
.. code-block:: edgeql-repl
db> with black_widow := (select Hero filter .name = "Black Widow")
... insert Movie {
... title := "Black Widow",
... release_year := 2021,
... characters := {
... black_widow,
... (insert Hero { name := "Yelena Belova"}),
... (insert Villain {
... name := "Dreykov",
... nemesis := black_widow
... })
... }
... };
{default::Movie {id: af706c7c-3e98-11ec-abb3-4bbf3f18a61a}}
The ``with`` block can contain an arbitrary number of clauses; later clauses
can reference earlier ones.
.. code-block:: edgeql-repl
db> with
... black_widow := (select Hero filter .name = "Black Widow"),
... yelena := (insert Hero { name := "Yelena Belova"}),
... dreykov := (insert Villain {name := "Dreykov", nemesis := black_widow})
... insert Movie {
... title := "Black Widow",
... release_year := 2021,
... characters := { black_widow, yelena, dreykov }
... };
{default::Movie {id: af706c7c-3e98-11ec-abb3-4bbf3f18a61a}}
.. _ref_eql_insert_conflicts:
Conflicts
---------
.. index:: unless conflict on, else
|Gel| provides a general-purpose mechanism for gracefully handling possible
exclusivity constraint violations. Consider a scenario where we are trying to
``insert`` Eternals (the ``Movie``), but we can't remember if it already exists
in the database.
.. code-block:: edgeql-repl
db> insert Movie {
... title := "Eternals",
... release_year := 2021
... }
... unless conflict on .title
... else (select Movie);
{default::Movie {id: af706c7c-3e98-11ec-abb3-4bbf3f18a61a}}
This query attempts to ``insert`` Eternals. If it already exists in the
database, it will violate the uniqueness constraint on ``Movie.title``, causing
a *conflict* on the ``title`` field. The ``else`` clause is then executed and
returned instead. In essence, ``unless conflict`` lets us "catch" exclusivity
conflicts and provide a fallback expression.
.. note::
Note that the ``else`` clause is simply ``select Movie``. There's no need to
apply additional filters on ``Movie``; in the context of the ``else`` clause,
``Movie`` is bound to the conflicting object.
.. note::
Using ``unless conflict`` on :ref:`multi properties
<ref_datamodel_props_cardinality>` is only supported in 2.10 and later.
.. _ref_eql_upsert:
Upserts
^^^^^^^
.. index:: upserts, unless conflict on, else update
There are no limitations on what the ``else`` clause can contain; it can be any
EdgeQL expression, including an :ref:`update <ref_eql_update>` statement. This
lets you express *upsert* logic in a single EdgeQL query.
.. code-block:: edgeql-repl
db> with
... title := "Eternals",
... release_year := 2021
... insert Movie {
... title := title,
... release_year := release_year
... }
... unless conflict on .title
... else (
... update Movie set { release_year := release_year }
... );
{default::Movie {id: f1bf5ac0-3e9d-11ec-b78d-c7dfb363362c}}
When a conflict occurs during the initial ``insert``, the statement falls back
to the ``update`` statement in the ``else`` clause. This updates the
``release_year`` of the conflicting object.
.. note::
It can be useful to know the outcome of an upsert. Here's an example
showing how you can return that:
.. code-block:: edgeql-repl
db> with
... title := "Eternals",
... release_year := 2021,
... movie := (
... insert Movie {
... title := title,
... release_year := release_year
... }
... unless conflict on .title
... else (
... update Movie set { release_year := release_year }
... )
... )
... select movie {
... is_new := (movie not in Movie)
... };
{default::Movie {is_new: true}}
This technique exploits the fact that a ``select`` will not return an
object inserted in the same query. We know that, if the record exists, we
updated it. If it does not, we inserted it.
By wrapping your upsert in a ``select`` and putting a shape on it that
queries for the object and returns whether or not it exists (as ``is_new``,
in this example), you can easily see whether the object was inserted or
updated.
If you want to also return some of the ``Movie`` object's data, drop
additional property names into the shape alongside ``is_new``. If you're on
3.0+, you can add ``Movie.*`` to the shape alongside ``is_new`` to get back
all of the ``Movie`` object's properties. You could even silo the data off,
keeping it separate from the ``is_new`` computed value like this:
.. code-block:: edgeql-repl
db> with
... title := "Eternals",
... release_year := 2021,
... movie := (
... insert Movie {
... title := title,
... release_year := release_year
... }
... unless conflict on .title
... else (
... update Movie set { release_year := release_year }
... )
... )
... select {
... data := (select movie {*}),
... is_new := (movie not in Movie)
... };
{
{
data: {
default::Movie {
id: 6880d0ba-62ca-11ee-9608-635818746433,
release_year: 2021,
title: 'Eternals'
}
},
is_new: false
}
}
Suppressing failures
^^^^^^^^^^^^^^^^^^^^
.. index:: unless conflict
The ``else`` clause is optional; when omitted, the ``insert`` statement will
return an *empty set* if a conflict occurs. This is a common way to prevent
``insert`` queries from failing on constraint violations.
.. code-block:: edgeql-repl
db> insert Hero { name := "The Wasp" } # initial insert
... unless conflict;
{default::Hero {id: 35b97a92-3e9b-11ec-8e39-6b9695d671ba}}
db> insert Hero { name := "The Wasp" } # The Wasp now exists
... unless conflict;
{}
.. _ref_eql_insert_bulk:
Bulk inserts
------------
.. index:: bulk inserts
Bulk inserts are performed by passing in a JSON array as a :ref:`query
parameter <ref_eql_params>`, :eql:func:`unpacking <json_array_unpack>` it, and
using a :ref:`for loop <ref_eql_for>` to insert the objects.
.. code-block:: edgeql-repl
db> with
... raw_data := <json>$data,
... for item in json_array_unpack(raw_data) union (
... insert Hero { name := <str>item['name'] }
... );
Parameter <json>$data: [{"name":"Sersi"},{"name":"Ikaris"},{"name":"Thena"}]
{
default::Hero {id: 35b97a92-3e9b-11ec-8e39-6b9695d671ba},
default::Hero {id: 35b97a92-3e9b-11ec-8e39-6b9695d671ba},
default::Hero {id: 35b97a92-3e9b-11ec-8e39-6b9695d671ba},
...
}
.. list-table::
:class: seealso
* - **See also**
* - :ref:`Reference > Commands > Insert <ref_eql_statements_insert>`
* - :ref:`Cheatsheets > Inserting data <ref_cheatsheet_insert>`
================================================================================
.. File: literals.rst
.. _ref_eql_literals:
Literals
========
.. index:: primitive types
EdgeQL is *inextricably tied* to Gel's rigorous type system. Below is an
overview of how to declare a literal value of each *primitive type*. Click a
link in the left column to jump to the associated section.
.. list-table::
* - :ref:`String <ref_eql_literal_strings>`
- ``str``
* - :ref:`Boolean <ref_eql_literal_boolean>`
- ``bool``
* - :ref:`Numbers <ref_eql_literal_numbers>`
- ``int16`` ``int32`` ``int64``
``float32`` ``float64`` ``bigint``
``decimal``
* - :ref:`UUID <ref_eql_literal_uuid>`
- ``uuid``
* - :ref:`Enums <ref_eql_literal_enum>`
- ``enum<X, Y, Z>``
* - :ref:`Dates and times <ref_eql_literal_dates>`
- ``datetime`` ``duration``
``cal::local_datetime`` ``cal::local_date``
``cal::local_time`` ``cal::relative_duration``
* - :ref:`Durations <ref_eql_literal_durations>`
- ``duration`` ``cal::relative_duration`` ``cal::date_duration``
* - :ref:`Ranges <ref_eql_ranges>`
- ``range<x>``
* - :ref:`Bytes <ref_eql_literal_bytes>`
- ``bytes``
* - :ref:`Arrays <ref_eql_literal_array>`
- ``array<x>``
* - :ref:`Tuples <ref_eql_literal_tuple>`
- ``tuple<x, y, ...>`` or
``tuple<foo: x, bar: y, ...>``
* - :ref:`JSON <ref_eql_literal_json>`
- ``json``
.. _ref_eql_literal_strings:
Strings
-------
.. index:: str, unicode, quotes, raw strings, escape character
The :eql:type:`str` type is a variable-length string of Unicode characters. A
string can be declared with either single or double quotes.
.. code-block:: edgeql-repl
db> select 'I ❤️ EdgeQL';
{'I ❤️ EdgeQL'}
db> select "hello there!";
{'hello there!'}
db> select 'hello\nthere!';
{'hello
there!'}
db> select 'hello
... there!';
{'hello
there!'}
db> select r'hello
... there!'; # multiline
{'hello
there!'}
There is a special syntax for declaring "raw strings". Raw strings treat the
backslash ``\`` as a literal character instead of an escape character.
.. code-block:: edgeql-repl
db> select r'hello\nthere'; # raw string
{r'hello\\nthere'}
db> select $$one
... two
... three$$; # multiline raw string
{'one
two
three'}
db> select $label$You can add an interstitial label
... if you need to use "$$" in your string.$label$;
{
'You can add an interstital label
if you need to use "$$" in your string.',
}
EdgeQL contains a set of built-in functions and operators for searching,
comparing, and manipulating strings.
.. code-block:: edgeql-repl
db> select 'hellothere'[5:10];
{'there'}
db> select 'hello' ++ 'there';
{'hellothere'}
db> select len('hellothere');
{10}
db> select str_trim(' hello there ');
{'hello there'}
db> select str_split('hello there', ' ');
{['hello', 'there']}
For a complete reference on strings, see :ref:`Standard Library > String
<ref_std_string>` or click an item below.
.. list-table::
* - Indexing and slicing
- :eql:op:`str[i] <stridx>` :eql:op:`str[from:to] <strslice>`
* - Concatenation
- :eql:op:`str ++ str <strplus>`
* - Utilities
- :eql:func:`len`
* - Transformation functions
- :eql:func:`str_split` :eql:func:`str_lower` :eql:func:`str_upper`
:eql:func:`str_title` :eql:func:`str_pad_start` :eql:func:`str_pad_end`
:eql:func:`str_trim` :eql:func:`str_trim_start` :eql:func:`str_trim_end`
:eql:func:`str_repeat`
* - Comparison operators
- :eql:op:`= <eq>` :eql:op:`\!= <neq>` :eql:op:`?= <coaleq>`
:eql:op:`?!= <coalneq>` :eql:op:`\< <lt>` :eql:op:`\> <gt>`
:eql:op:`\<= <lteq>` :eql:op:`\>= <gteq>`
* - Search
- :eql:func:`contains` :eql:func:`find`
* - Pattern matching and regexes
- :eql:op:`str like pattern <like>` :eql:op:`str ilike pattern <ilike>`
:eql:func:`re_match` :eql:func:`re_match_all` :eql:func:`re_replace`
:eql:func:`re_test`
.. _ref_eql_literal_boolean:
Booleans
--------
.. index:: bool
The :eql:type:`bool` type represents a true/false value.
.. code-block:: edgeql-repl
db> select true;
{true}
db> select false;
{false}
|Gel| provides a set of operators that operate on boolean values.
.. list-table::
* - Comparison operators
- :eql:op:`= <eq>` :eql:op:`\!= <neq>` :eql:op:`?= <coaleq>`
:eql:op:`?!= <coalneq>` :eql:op:`\< <lt>` :eql:op:`\> <gt>`
:eql:op:`\<= <lteq>` :eql:op:`\>= <gteq>`
* - Logical operators
- :eql:op:`or` :eql:op:`and` :eql:op:`not`
* - Aggregation
- :eql:func:`all` :eql:func:`any`
.. _ref_eql_literal_numbers:
Numbers
-------
There are several numerical types in Gel's type system.
.. list-table::
* - :eql:type:`int16`
- 16-bit integer
* - :eql:type:`int32`
- 32-bit integer
* - :eql:type:`int64`
- 64-bit integer
* - :eql:type:`float32`
- 32-bit floating point number
* - :eql:type:`float64`
- 64-bit floating point number
* - :eql:type:`bigint`
- Arbitrary precision integer.
* - :eql:type:`decimal`
- Arbitrary precision number.
Number literals that *do not* contain a decimal are interpreted as ``int64``.
Numbers containing decimals are interpreted as ``float64``. The ``n`` suffix
designates a number with *arbitrary precision*: either ``bigint`` or
``decimal``.
====================================== =============================
Syntax Inferred type
====================================== =============================
:eql:code:`select 3;` :eql:type:`int64`
:eql:code:`select 3.14;` :eql:type:`float64`
:eql:code:`select 314e-2;` :eql:type:`float64`
:eql:code:`select 42n;` :eql:type:`bigint`
:eql:code:`select 42.0n;` :eql:type:`decimal`
:eql:code:`select 42e+100n;` :eql:type:`decimal`
====================================== =============================
To declare an ``int16``, ``int32``, or ``float32``, you must provide an
explicit type cast. For details on type casting, see :ref:`Casting
<ref_eql_types>`.
====================================== =============================
Syntax Type
====================================== =============================
:eql:code:`select <int16>1234;` :eql:type:`int16`
:eql:code:`select <int32>123456;` :eql:type:`int32`
:eql:code:`select <float32>123.456;` :eql:type:`float32`
====================================== =============================
EdgeQL includes a full set of arithmetic and comparison operators. Parentheses
can be used to indicate the order-of-operations or visually group
subexpressions; this is true across all EdgeQL queries.
.. code-block:: edgeql-repl
db> select 5 > 2;
{true}
db> select 2 + 2;
{4}
db> select 2 ^ 10;
{1024}
db> select (1 + 1) * 2 / (3 + 8);
{0.36363636363636365}
EdgeQL provides a comprehensive set of built-in functions and operators on
numerical data.
.. list-table::
* - Comparison operators
- :eql:op:`= <eq>` :eql:op:`\!= <neq>` :eql:op:`?= <coaleq>`
:eql:op:`?!= <coalneq>` :eql:op:`\< <lt>` :eql:op:`\> <gt>`
:eql:op:`\<= <lteq>` :eql:op:`\>= <gteq>`
* - Arithmetic
- :eql:op:`+ <plus>` :eql:op:`- <minus>` :eql:op:`- <uminus>`
:eql:op:`* <mult>` :eql:op:`/ <div>` :eql:op:`// <floordiv>`
:eql:op:`% <mod>` :eql:op:`^ <pow>`
* - Statistics
- :eql:func:`sum` :eql:func:`min` :eql:func:`max` :eql:func:`math::mean`
:eql:func:`math::stddev` :eql:func:`math::stddev_pop`
:eql:func:`math::var` :eql:func:`math::var_pop`
* - Math
- :eql:func:`round` :eql:func:`math::abs` :eql:func:`math::ceil`
:eql:func:`math::floor` :eql:func:`math::ln` :eql:func:`math::lg`
:eql:func:`math::log`
* - Random number
- :eql:func:`random`
.. _ref_eql_literal_uuid:
UUID
----
The :eql:type:`uuid` type is commonly used to represent object identifiers.
UUID literal must be explicitly cast from a string value matching the UUID
specification.
.. code-block:: edgeql-repl
db> select <uuid>'a5ea6360-75bd-4c20-b69c-8f317b0d2857';
{a5ea6360-75bd-4c20-b69c-8f317b0d2857}
Generate a random UUID.
.. code-blocK:: edgeql-repl
db> select uuid_generate_v1mc();
{b4d94e6c-3845-11ec-b0f4-93e867a589e7}
.. _ref_eql_literal_enum:
Enums
-----
.. index:: enums
Enum types must be :ref:`declared in your schema <ref_datamodel_enums>`.
.. code-block:: sdl
scalar type Color extending enum<Red, Green, Blue>;
Once declared, an enum literal can be declared with dot notation, or by
casting an appropriate string literal:
.. code-block:: edgeql-repl
db> select Color.Red;
{Red}
db> select <Color>"Red";
{Red}
.. _ref_eql_literal_dates:
Dates and times
---------------
.. index:: temporal
|Gel's| typesystem contains several temporal types.
.. list-table::
* - :eql:type:`datetime`
- Timezone-aware point in time
* - :eql:type:`cal::local_datetime`
- Date and time w/o timezone
* - :eql:type:`cal::local_date`
- Date type
* - :eql:type:`cal::local_time`
- Time type
All temporal literals are declared by casting an appropriately formatted
string.
.. code-block:: edgeql-repl
db> select <datetime>'1999-03-31T15:17:00Z';
{<datetime>'1999-03-31T15:17:00Z'}
db> select <datetime>'1999-03-31T17:17:00+02';
{<datetime>'1999-03-31T15:17:00Z'}
db> select <cal::local_datetime>'1999-03-31T15:17:00';
{<cal::local_datetime>'1999-03-31T15:17:00'}
db> select <cal::local_date>'1999-03-31';
{<cal::local_date>'1999-03-31'}
db> select <cal::local_time>'15:17:00';
{<cal::local_time>'15:17:00'}
EdgeQL supports a set of functions and operators on datetime types.
.. list-table::
* - Comparison operators
- :eql:op:`= <eq>` :eql:op:`\!= <neq>` :eql:op:`?= <coaleq>`
:eql:op:`?!= <coalneq>` :eql:op:`\< <lt>` :eql:op:`\> <gt>`
:eql:op:`\<= <lteq>` :eql:op:`\>= <gteq>`
* - Arithmetic
- :eql:op:`dt + dt <dtplus>` :eql:op:`dt - dt <dtminus>`
* - String parsing
- :eql:func:`to_datetime` :eql:func:`cal::to_local_datetime`
:eql:func:`cal::to_local_date` :eql:func:`cal::to_local_time`
* - Component extraction
- :eql:func:`datetime_get` :eql:func:`cal::time_get`
:eql:func:`cal::date_get`
* - Truncation
- :eql:func:`datetime_truncate`
* - System timestamps
- :eql:func:`datetime_current` :eql:func:`datetime_of_transaction`
:eql:func:`datetime_of_statement`
.. _ref_eql_literal_durations:
Durations
---------
|Gel's| type system contains three duration types.
.. list-table::
* - :eql:type:`duration`
- Exact duration
* - :eql:type:`cal::relative_duration`
- Duration in relative units
* - :eql:type:`cal::date_duration`
- Duration in months and days only
Exact durations
^^^^^^^^^^^^^^^
The :eql:type:`duration` type represents *exact* durations that can be
represented by some fixed number of microseconds. It can be negative and it
supports units of ``microseconds``, ``milliseconds``, ``seconds``, ``minutes``,
and ``hours``.
.. code-block:: edgeql-repl
db> select <duration>'45.6 seconds';
{<duration>'0:00:45.6'}
db> select <duration>'-15 microseconds';
{<duration>'-0:00:00.000015'}
db> select <duration>'5 hours 4 minutes 3 seconds';
{<duration>'5:04:03'}
db> select <duration>'8760 hours'; # about a year
{<duration>'8760:00:00'}
All temporal units beyond ``hour`` no longer correspond to a fixed duration of
time; the length of a day/month/year/etc changes based on daylight savings
time, the month in question, leap years, etc.
Relative durations
^^^^^^^^^^^^^^^^^^
By contrast, the :eql:type:`cal::relative_duration` type represents a
"calendar" duration, like ``1 month``. Because months have different number of
days, ``1 month`` doesn't correspond to a fixed number of milliseconds, but
it's often a useful quantity to represent recurring events, postponements, etc.
.. note::
The ``cal::relative_duration`` type supports the same units as ``duration``,
plus ``days``, ``weeks``, ``months``, ``years``, ``decades``, ``centuries``,
and ``millennia``.
To declare relative duration literals:
.. code-block:: edgeql-repl
db> select <cal::relative_duration>'15 milliseconds';
{<cal::relative_duration>'PT.015S'}
db> select <cal::relative_duration>'2 months 3 weeks 45 minutes';
{<cal::relative_duration>'P2M21DT45M'}
db> select <cal::relative_duration>'-7 millennia';
{<cal::relative_duration>'P-7000Y'}
Date durations
^^^^^^^^^^^^^^
The :eql:type:`cal::date_duration` represents spans consisting of some number
of *months* and *days*. This type is primarily intended to simplify logic
involving :eql:type:`cal::local_date` values.
.. code-block:: edgeql-repl
db> select <cal::date_duration>'5 days';
{<cal::date_duration>'P5D'}
db> select <cal::local_date>'2022-06-25' + <cal::date_duration>'5 days';
{<cal::local_date>'2022-06-30'}
db> select <cal::local_date>'2022-06-30' - <cal::local_date>'2022-06-25';
{<cal::date_duration>'P5D'}
EdgeQL supports a set of functions and operators on duration types.
.. list-table::
* - Comparison operators
- :eql:op:`= <eq>` :eql:op:`\!= <neq>` :eql:op:`?= <coaleq>`
:eql:op:`?!= <coalneq>` :eql:op:`\< <lt>` :eql:op:`\> <gt>`
:eql:op:`\<= <lteq>` :eql:op:`\>= <gteq>`
* - Arithmetic
- :eql:op:`dt + dt <dtplus>` :eql:op:`dt - dt <dtminus>`
* - Duration string parsing
- :eql:func:`to_duration` :eql:func:`cal::to_relative_duration`
:eql:func:`cal::to_date_duration`
* - Component extraction
- :eql:func:`duration_get`
* - Conversion
- :eql:func:`duration_truncate` :eql:func:`cal::duration_normalize_hours`
:eql:func:`cal::duration_normalize_days`
.. _ref_eql_ranges:
Ranges
------
.. index:: ranges, lower bound, upper bound, inclusive, inc_lower, inc_upper,
empty
Ranges represent a range of orderable scalar values. A range comprises a lower
bound, upper bound, and two boolean flags indicating whether each bound is
inclusive.
Create a range literal with the ``range`` constructor function.
.. code-block:: edgeql-repl
db> select range(1, 10);
{range(1, 10, inc_lower := true, inc_upper := false)}
db> select range(2.2, 3.3);
{range(2.2, 3.3, inc_lower := true, inc_upper := false)}
Ranges can be *empty*, when the upper and lower bounds are equal.
.. code-block:: edgeql-repl
db> select range(1, 1);
{range({}, empty := true)}
Ranges can be *unbounded*. An empty set is used to indicate the
lack of a particular upper or lower bound.
.. code-block:: edgeql-repl
db> select range(4, <int64>{});
{range(4, {})}
db> select range(<int64>{}, 4);
{range({}, 4)}
db> select range(<int64>{}, <int64>{});
{range({}, {})}
To compute the set of concrete values defined by a range literal, use
``range_unpack``. An empty range will unpack to the empty set. Unbounded
ranges cannot be unpacked.
.. code-block:: edgeql-repl
db> select range_unpack(range(0, 10));
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
db> select range_unpack(range(1, 1));
{}
db> select range_unpack(range(0, <int64>{}));
gel error: InvalidValueError: cannot unpack an unbounded range
.. _ref_eql_literal_bytes:
Bytes
-----
.. index:: binary, raw byte strings
The ``bytes`` type represents raw binary data.
.. code-block:: edgeql-repl
db> select b'bina\\x01ry';
{b'bina\\x01ry'}
There is a special syntax for declaring "raw byte strings". Raw byte strings
treat the backslash ``\`` as a literal character instead of an escape
character.
.. code-block:: edgeql-repl
db> select rb'hello\nthere';
{b'hello\\nthere'}
db> select br'\';
{b'\\'}
.. _ref_eql_literal_array:
Arrays
------
.. index:: collection, lists, ordered
An array is an *ordered* collection of values of the *same type*. For example:
.. code-block:: edgeql-repl
db> select [1, 2, 3];
{[1, 2, 3]}
db> select ['hello', 'world'];
{['hello', 'world']}
db> select [(1, 2), (100, 200)];
{[(1, 2), (100, 200)]}
EdgeQL provides a set of functions and operators on arrays.
.. list-table::
* - Indexing and slicing
- :eql:op:`array[i] <arrayidx>` :eql:op:`array[from:to] <arrayslice>`
:eql:func:`array_get`
* - Concatenation
- :eql:op:`array ++ array <arrayplus>`
* - Comparison operators
- :eql:op:`= <eq>` :eql:op:`\!= <neq>` :eql:op:`?= <coaleq>`
:eql:op:`?!= <coalneq>` :eql:op:`\< <lt>` :eql:op:`\> <gt>`
:eql:op:`\<= <lteq>` :eql:op:`\>= <gteq>`
* - Utilities
- :eql:func:`len` :eql:func:`array_join`
* - Search
- :eql:func:`contains` :eql:func:`find`
* - Conversion to/from sets
- :eql:func:`array_agg` :eql:func:`array_unpack`
See :ref:`Standard Library > Array <ref_std_array>` for a complete
reference on array data types.
.. _ref_eql_literal_tuple:
Tuples
------
.. index:: fixed length ordered collection, named tuples
A tuple is *fixed-length*, *ordered* collection of values, each of which may
have a *different type*. The elements of a tuple can be of any type, including
scalars, arrays, other tuples, and object types.
.. code-block:: edgeql-repl
db> select ('Apple', 7, true);
{('Apple', 7, true)}
Optionally, you can assign a key to each element of a tuple. These are known
as *named tuples*. You must assign keys to all or none of the elements; you
can't mix-and-match.
.. code-block:: edgeql-repl
db> select (fruit := 'Apple', quantity := 3.14, fresh := true);
{(fruit := 'Apple', quantity := 3.14, fresh := true)}
Indexing tuples
^^^^^^^^^^^^^^^
Tuple elements can be accessed with dot notation. Under the hood, there's no
difference between named and unnamed tuples. Named tuples support key-based
and numerical indexing.
.. code-block:: edgeql-repl
db> select (1, 3.14, 'red').0;
{1}
db> select (1, 3.14, 'red').2;
{'red'}
db> select (name := 'george', age := 12).name;
{('george')}
db> select (name := 'george', age := 12).0;
{('george')}
.. important::
When you query an *unnamed* tuple using one of EdgeQL's :ref:`client
libraries <ref_clients_index>`, its value is converted to a list/array. When
you fetch a *named tuple*, it is converted to an object/dictionary/hashmap.
For a full reference on tuples, see :ref:`Standard Library > Tuple
<ref_std_tuple>`.
.. _ref_eql_literal_json:
JSON
----
.. index:: json
The :eql:type:`json` scalar type is a stringified representation of structured
data. JSON literals are declared by explicitly casting other values or passing
a properly formatted JSON string into :eql:func:`to_json`. Any type can be
converted into JSON except :eql:type:`bytes`.
.. code-block:: edgeql-repl
db> select <json>5;
{'5'}
db> select <json>"a string";
{'"a string"'}
db> select <json>["this", "is", "an", "array"];
{'["this", "is", "an", "array"]'}
db> select <json>("unnamed tuple", 2);
{'["unnamed tuple", 2]'}
db> select <json>(name := "named tuple", count := 2);
{'{
"name": "named tuple",
"count": 2
}'}
db> select to_json('{"a": 2, "b": 5}');
{'{"a": 2, "b": 5}'}
JSON values support indexing operators. The resulting value is also of type
``json``.
.. code-block:: edgeql-repl
db> select to_json('{"a": 2, "b": 5}')['a'];
{2}
db> select to_json('["a", "b", "c"]')[2];
{'"c"'}
EdgeQL supports a set of functions and operators on ``json`` values. Refer to
the :ref:`Standard Library > JSON <ref_std_json>` or click an item below for
detailed documentation.
.. list-table::
* - Indexing
- :eql:op:`json[i] <jsonidx>` :eql:op:`json[from:to] <jsonslice>`
:eql:op:`json[name] <jsonobjdest>` :eql:func:`json_get`
* - Merging
- :eql:op:`json ++ json <jsonplus>`
* - Comparison operators
- :eql:op:`= <eq>` :eql:op:`\!= <neq>` :eql:op:`?= <coaleq>`
:eql:op:`?!= <coalneq>` :eql:op:`\< <lt>` :eql:op:`\> <gt>`
:eql:op:`\<= <lteq>` :eql:op:`\>= <gteq>`
* - Conversion to/from strings
- :eql:func:`to_json` :eql:func:`to_str`
* - Conversion to/from sets
- :eql:func:`json_array_unpack` :eql:func:`json_object_unpack`
* - Introspection
- :eql:func:`json_typeof`
================================================================================
.. File: parameters.rst
.. _ref_eql_params:
Parameters
==========
.. index:: query params, query arguments, query args, $, < >$, input
:edb-alt-title: Query Parameters
EdgeQL queries can reference parameters with ``$`` notation. The value of these
parameters are supplied externally.
.. code-block:: edgeql
select <str>$var;
select <int64>$a + <int64>$b;
select BlogPost filter .id = <uuid>$blog_id;
Note that we provided an explicit type cast before the parameter. This is
required, as it enables Gel to enforce the provided types at runtime.
Parameters can be named or unnamed tuples.
.. code-block:: edgeql
select <tuple<str, bool>>$var;
select <optional tuple<str, bool>>$var;
select <tuple<name: str, flag: bool>>$var;
select <optional tuple<name: str, flag: bool>>$var;
Usage with clients
------------------
REPL
^^^^
When you include a parameter reference in an Gel REPL, you'll be prompted
interactively to provide a value or values.
.. code-block:: edgeql-repl
db> select 'I ❤️ ' ++ <str>$var ++ '!';
Parameter <str>$var: Gel
{'I ❤️ Gel!'}
Python
^^^^^^
.. code-block:: python
await client.query(
"select 'I ❤️ ' ++ <str>$var ++ '!';",
var="lamp")
await client.query(
"select <datetime>$date;",
date=datetime.today())
JavaScript
^^^^^^^^^^
.. code-block:: javascript
await client.query("select 'I ❤️ ' ++ <str>$name ++ '!';", {
name: "rock and roll"
});
await client.query("select <datetime>$date;", {
date: new Date()
});
Go
^^
.. code-block:: go
var result string
err = db.QuerySingle(ctx,
`select 'I ❤️ ' ++ <str>$var ++ '!';"`,
&result, "Golang")
var date time.Time
err = db.QuerySingle(ctx,
`select <datetime>$date;`,
&date, time.Now())
Refer to the Datatypes page of your preferred :ref:`client library
<ref_clients_index>` to learn more about mapping between Gel types and
language-native types.
.. _ref_eql_params_types:
Parameter types and JSON
------------------------
.. index:: complex parameters
In Gel, parameters can also be tuples. If you need to pass complex structures
as parameters, use Gel's built-in :ref:`JSON <ref_std_json>` functionality.
.. code-block:: edgeql-repl
db> with data := <json>$data
... insert Movie {
... title := <str>data['title'],
... release_year := <int64>data['release_year'],
... };
Parameter <json>$data: {"title": "The Marvels", "release_year": 2023}
{default::Movie {id: 8d286cfe-3c0a-11ec-aa68-3f3076ebd97f}}
Arrays can be "unpacked" into sets and assigned to ``multi`` links or
properties.
.. code-block:: edgeql
with friends := (
select User filter .id in array_unpack(<array<uuid>>$friend_ids)
)
insert User {
name := <str>$name,
friends := friends,
};
.. _ref_eql_params_optional:
Optional parameters
-------------------
.. index:: <optional >$
By default, query parameters are ``required``; the query will fail if the
parameter value is an empty set. You can use an ``optional`` modifier inside
the type cast if the parameter is optional.
.. code-block:: edgeql-repl
db> select <optional str>$name;
Parameter <str>$name (Ctrl+D for empty set `{}`):
{}
.. note::
The ``<required foo>`` type cast is also valid (though redundant) syntax.
.. code-block:: edgeql
select <required str>$name;
Default parameter values
------------------------
.. index:: ??
When using optional parameters, you may want to provide a default value to use
in case the parameter is not passed. You can do this by using the
:eql:op:`?? (coalesce) <coalesce>` operator.
.. code-block:: edgeql-repl
db> select 'Hello ' ++ <optional str>$name ?? 'there';
Parameter <str>$name (Ctrl+D for empty set `{}`): Gel
{'Hello Gel'}
db> select 'Hello ' ++ <optional str>$name ?? 'there';
Parameter <str>$name (Ctrl+D for empty set `{}`):
{'Hello there'}
What can be parameterized?
--------------------------
.. index:: order by parameters
Any data manipulation language (DML) statement can be
parameterized: ``select``, ``insert``, ``update``, and ``delete``. Since
parameters can only be scalars, arrays of scalars, and
tuples of scalars, only parts of the query that would be one of those types can
be parameterized. This excludes parts of the query like the type being queried
and the property to order by.
.. note::
You can parameterize ``order by`` for a limited number of options by using
:eql:op:`if..else`:
.. code-block:: edgeql
select Movie {*}
order by
(.title if <str>$order_by = 'title'
else <str>{})
then
(.release_year if <str>$order_by = 'release_year'
else <int64>{});
If a user running this query enters ``title`` as the parameter value,
``Movie`` objects will be sorted by their ``title`` property. If they enter
``release_year``, they will be sorted by the ``release_year`` property.
Since the ``if`` and ``else`` result clauses need to be of compatible
types, your ``else`` expressions should be an empty set of the same type as
the property.
Schema definition language (SDL) and :ref:`configure
<ref_eql_statements_configure>` statements **cannot** be parameterized. Data
definition language (DDL) has limited support for parameters, but it's not a
recommended pattern. Some of the limitations might be lifted in future
versions.
================================================================================
.. File: path_resolution.rst
.. _ref_eql_path_resolution:
============
Path scoping
============
.. index:: using future simple_scoping, using future warn_old_scoping
Beginning with Gel 6.0, we are phasing out our historical (and
somewhat notorious)
:ref:`"path scoping" algorithm <ref_eql_old_path_resolution>`
in favor of a much simpler algorithm that nevertheless behaves
identically on *most* idiomatic EdgeQL queries.
Gel 6.0 will contain features to support migration to and testing
of the new semantics. We expect the migration to be relatively
painless for most users.
Discussion of rationale for this change is available in
`the RFC <rfc_>`_.
New path scoping
----------------
.. versionadded:: 6.0
When applying a shape to a path (or to a path that has shapes applied
to it already), the path will be be bound inside computed
pointers in that shape:
.. code-block:: edgeql-repl
db> select User {
... name := User.first_name ++ ' ' ++ User.last_name
... }
{User {name: 'Peter Parker'}, User {name: 'Tony Stark'}}
When doing ``SELECT``, ``UPDATE``, or ``DELETE``, if the subject is a
path, optionally with shapes applied to it, the path will be
bound in ``FILTER`` and ``ORDER BY`` clauses:
.. code-block:: edgeql-repl
db> select User {
... name := User.first_name ++ ' ' ++ User.last_name
... }
... filter User.first_name = 'Peter'
{User {name: 'Peter Parker'}}
However, when a path is used multiple times in "sibling" contexts,
a cross-product will be computed:
.. code-block:: edgeql-repl
db> select User.first_name ++ ' ' ++ User.last_name;
{'Peter Parker', 'Peter Stark', 'Tony Parker', 'Tony Stark'}
If you want to produce one value per ``User``, you can rewrite the query
with a ``FOR`` to make the intention explicit:
.. code-block:: edgeql-repl
db> for u in User
... select u.first_name ++ ' ' ++ u.last_name;
{'Peter Parker', 'Tony Stark'}
The most idiomatic way to fetch such data in EdgeQL, however,
remains:
.. code-block:: edgeql-repl
db> select User { name := .first_name ++ ' ' ++ .last_name }
{User {name: 'Peter Parker'}, User {name: 'Tony Stark'}}
(And, of course, you probably `shouldn't have first_name and last_name
properties anyway
<https://www.kalzumeus.com/2010/06/17/falsehoods-programmers-believe-about-names/>`_)
Path scoping configuration
--------------------------
.. versionadded:: 6.0
Gel 6.0 introduces a new
:ref:`future feature <ref_datamodel_future>`
named ``simple_scoping`` alongside a
configuration setting also named ``simple_scoping``. The future
feature presence will determine which behavior is used inside
expressions within the schema, as well as serve as the default value
if the configuration value is not set. The configuration setting will
allow overriding the presence or absence of the feature.
For concreteness, here are all of the posible combinations of whether
``using future simple_scoping`` is set and the value of the
configuration value ``simple_scoping``:
.. list-table::
:widths: 25 25 25 25
:header-rows: 1
* - Future exists?
- Config value
- Query is simply scoped
- Schema is simply scoped
* - No
- ``{}``
- No
- No
* - No
- ``true``
- Yes
- No
* - No
- ``false``
- No
- No
* - Yes
- ``{}``
- Yes
- Yes
* - Yes
- ``true``
- Yes
- Yes
* - Yes
- ``false``
- No
- Yes
.. _ref_warn_old_scoping:
Warning on old scoping
----------------------
.. versionadded:: 6.0
To make the migration process safer, we have also introduced a
``warn_old_scoping`` :ref:`future feature <ref_datamodel_future>` and
config setting.
When active, the server will emit a warning to the client when a query
is detected to depend on the old scoping behavior. The behavior of
warnings can be configured in client bindings, but by default they are
logged.
The check is known to sometimes produce false positives, on queries
that will not actually have changed behavior, but is intended to not
have false negatives.
Recommended upgrade plan
------------------------
.. versionadded:: 6.0
The safest approach is to first get your entire schema and application
working with ``warn_old_scoping`` without producing any warnings. Once
that is done, it should be safe to switch to ``simple_scoping``
without changes in behavior.
If you are very confident in your test coverage, though, you can try
skipping dealing with ``warn_old_scoping`` and go straight to
``simple_scoping``.
There are many different potential migration strategies. One that
should work well:
1. Run ``CONFIGURE CURRENT DATABASE SET warn_old_scoping := true``
2. Try running all of your queries against the database.
3. Fix any that produce warnings.
4. Adjust your schema until setting ``using future warn_old_scoping`` works
without producing warnings.
If you wish to proceed incrementally with steps 2 and 3, you can
configure ``warn_old_scoping`` in your clients, having it enabled for
queries that you have verified work with it and disabled for queries
that have not yet been verified or updated.
.. _ref_eql_old_path_resolution:
===================
Legacy path scoping
===================
This section describes the path scoping algorithm used exclusively
until |EdgeDB| 5.0 and by default in |Gel| 6.0.
It will be removed in Gel 7.0.
Element-wise operations with multiple arguments in Gel are generally applied
to the :ref:`cartesian product <ref_reference_cardinality_cartesian>` of all
the input sets.
.. code-block:: edgeql-repl
db> select {'aaa', 'bbb'} ++ {'ccc', 'ddd'};
{'aaaccc', 'aaaddd', 'bbbccc', 'bbbddd'}
However, in cases where multiple element-wise arguments share a common path
(``User.`` in this example), Gel factors out the common path rather than
using cartesian multiplication.
.. code-block:: edgeql-repl
db> select User.first_name ++ ' ' ++ User.last_name;
{'Mina Murray', 'Jonathan Harker', 'Lucy Westenra', 'John Seward'}
We assume this is what you want, but if your goal is to get the cartesian
product, you can accomplish it one of three ways. You could use
:eql:op:`detached`.
.. code-block:: edgeql-repl
gel> select User.first_name ++ ' ' ++ detached User.last_name;
{
'Mina Murray',
'Mina Harker',
'Mina Westenra',
'Mina Seward',
'Jonathan Murray',
'Jonathan Harker',
'Jonathan Westenra',
'Jonathan Seward',
'Lucy Murray',
'Lucy Harker',
'Lucy Westenra',
'Lucy Seward',
'John Murray',
'John Harker',
'John Westenra',
'John Seward',
}
You could use :ref:`with <ref_eql_with>` to attach a different symbol to
your set of ``User`` objects.
.. code-block:: edgeql-repl
gel> with U := User
.... select U.first_name ++ ' ' ++ User.last_name;
{
'Mina Murray',
'Mina Harker',
'Mina Westenra',
'Mina Seward',
'Jonathan Murray',
'Jonathan Harker',
'Jonathan Westenra',
'Jonathan Seward',
'Lucy Murray',
'Lucy Harker',
'Lucy Westenra',
'Lucy Seward',
'John Murray',
'John Harker',
'John Westenra',
'John Seward',
}
Or you could leverage the effect scopes have on path resolution. More on that
:ref:`in the Scopes section <ref_eql_path_resolution_scopes>`.
The reason ``with`` works here even though the alias ``U`` refers to the exact
same set is that we only assume you want the path factored in this way when you
use the same *symbol* to refer to a set. This means operations with
``User.first_name`` and ``User.last_name`` *do* get the common path factored
while ``U.first_name`` and ``User.last_name`` *do not* and are resolved with
cartesian multiplication.
That may leave you still wondering why ``U`` and ``User`` did not get a common
path factored. ``U`` is just an alias of ``select User`` and ``User`` is the
same symbol that we use in our name query. That's true, but |Gel| doesn't
factor in this case because of the queries' scopes.
.. _ref_eql_path_resolution_scopes:
Scopes
------
Scopes change the way path resolution works. Two sibling select queries — that
is, queries at the same level — do not have their paths factored even when they
use a common symbol.
.. code-block:: edgeql-repl
gel> select ((select User.first_name), (select User.last_name));
{
('Mina', 'Murray'),
('Mina', 'Harker'),
('Mina', 'Westenra'),
('Mina', 'Seward'),
('Jonathan', 'Murray'),
('Jonathan', 'Harker'),
('Jonathan', 'Westenra'),
('Jonathan', 'Seward'),
('Lucy', 'Murray'),
('Lucy', 'Harker'),
('Lucy', 'Westenra'),
('Lucy', 'Seward'),
('John', 'Murray'),
('John', 'Harker'),
('John', 'Westenra'),
('John', 'Seward'),
}
Common symbols in nested scopes *are* factored when they use the same symbol.
In this example, the nested queries both use the same ``User`` symbol as the
top-level query. As a result, the ``User`` in those queries refers to a single
object because it has been factored.
.. code-block:: edgeql-repl
gel> select User {
.... name:= (select User.first_name) ++ ' ' ++ (select User.last_name)
.... };
{
default::User {name: 'Mina Murray'},
default::User {name: 'Jonathan Harker'},
default::User {name: 'Lucy Westenra'},
default::User {name: 'John Seward'},
}
If you have two common scopes and only *one* of them is in a nested scope, the
paths are still factored.
.. code-block:: edgeql-repl
gel> select (Person.name, count(Person.friends));
{('Fran', 3), ('Bam', 2), ('Emma', 3), ('Geoff', 1), ('Tyra', 1)}
In this example, ``count``, like all aggregate function, creates a nested
scope, but this doesn't prevent the paths from being factored as you can see
from the results. If the paths were *not* factored, the friend count would be
the same for all the result tuples and it would reflect the total number of
``Person`` objects that are in *all* ``friends`` links rather than the number
of ``Person`` objects that are in the named ``Person`` object's ``friends``
link.
If you have two aggregate functions creating *sibling* nested scopes, the paths
are *not* factored.
.. code-block:: edgeql-repl
gel> select (array_agg(distinct Person.name), count(Person.friends));
{(['Fran', 'Bam', 'Emma', 'Geoff'], 3)}
This query selects a tuple containing two nested scopes. Here, |Gel| assumes
you want an array of all unique names and a count of the total number of people
who are anyone's friend.
Clauses & Nesting
^^^^^^^^^^^^^^^^^
Most clauses are nested and are subjected to the same rules described above:
common symbols are factored and assumed to refer to the same object as the
outer query. This is because clauses like :ref:`filter
<ref_eql_select_filter>` and :ref:`order by <ref_eql_select_order>` need to
be applied to each value in the result.
The :ref:`offset <ref_eql_select_pagination>` and
:ref:`limit <ref_eql_select_pagination>` clauses are not nested in the scope
because they need to be applied globally to the entire result set of your
query.
.. _rfc: https://github.com/geldata/rfcs/blob/master/text/1027-no-factoring.rst
================================================================================
.. File: paths.rst
.. _ref_eql_paths:
=====
Paths
=====
.. index:: links, relations
A *path expression* (or simply a *path*) represents a set of values that are
reachable by traversing a given sequence of links or properties from some
source set of objects.
Consider the following schema:
.. code-block:: sdl
type User {
required email: str;
multi friends: User;
}
type BlogPost {
required title: str;
required author: User;
}
type Comment {
required text: str;
required author: User;
}
A few simple inserts will allow some experimentation with paths.
Start with a first user:
.. code-block:: edgeql-repl
db> insert User {
... email := "user1@me.com",
... };
Along comes another user who adds the first user as a friend:
.. code-block:: edgeql-repl
db> insert User {
... email := "user2@me.com",
... friends := (select detached User filter .email = "user1@me.com")
... };
The first user reciprocates, adding the new user as a friend:
.. code-block:: edgeql-repl
db> update User filter .email = "user1@me.com"
... set {
... friends += (select detached User filter .email = "user2@me.com")
... };
The second user writes a blog post about how nice Gel is:
.. code-block:: edgeql-repl
db> insert BlogPost {
... title := "Gel is awesome",
... author := assert_single((select User filter .email = "user2@me.com"))
... };
And the first user follows it up with a comment below the post:
.. code-block:: edgeql-repl
db> insert Comment {
... text := "Nice post, user2!",
... author := assert_single((select User filter .email = "user1@me.com"))
... };
The simplest path is simply ``User``. This is a :ref:`set reference
<ref_eql_set_references>` that refers to all ``User`` objects in the database.
.. code-block:: edgeql
select User;
Paths can traverse links. The path below refers to *all Users who are the
friend of another User*.
.. code-block:: edgeql
select User.friends;
Paths can traverse to an arbitrary depth in a series of nested links.
Both ``select`` queries below end up showing the author of the ``BlogPost``.
The second query returns the friends of the friends of the author of the
``BlogPost``, which in this case is just the author.
.. code-block:: edgeql
select BlogPost.author; # The author
select BlogPost.author.friends.friends; # The author again
Paths can terminate with a property reference.
.. code-block:: edgeql
select BlogPost.title; # all blog post titles
select BlogPost.author.email; # all author emails
select User.friends.email; # all friends' emails
.. _ref_eql_paths_backlinks:
Backlinks
---------
.. index:: .<
All examples thus far have traversed links in the *forward direction*, however
it's also possible to traverse links *backwards* with ``.<`` notation. These
are called **backlinks**.
Starting from each user, the path below traverses all *incoming* links labeled
``author`` and returns the union of their sources.
.. code-block:: edgeql
select User.<author;
This query works, showing both the ``BlogPost`` and the ``Comment`` in the
database. However, we can't impose a shape on it:
.. code-block:: edgeql
select User.<author { text };
As written, Gel infers the *type* of this expression to be
:eql:type:`BaseObject`. Why? Because in theory, there may be
several links named ``author`` from different object types
that point to ``User``. And there is no guarantee that each
of these types will have a property called ``text``.
.. note::
``BaseObject`` is the root ancestor of all object types and it only contains
a single property, ``id``.
As such, commonly you'll want to narrow the results to a particular type.
To do so, use the :eql:op:`type intersection <isintersect>` operator:
``[is Foo]``:
.. code-block:: edgeql
# BlogPost objects that link to the user via a link named author
select User.<author[is BlogPost];
# Comment objects that link to the user via a link named author
select User.<author[is Comment];
# All objects that link to the user via a link named author
select User.<author;
Or parsed one step at a time, the above queries can be read as follows:
================================ ===================================
Syntax Meaning
================================ ===================================
``User.<`` Objects that link to the user
``author`` via a link named author
================================ ===================================
================================ ===================================
Syntax Meaning
================================ ===================================
``User.<`` Objects that link to the user
``author`` via a link named author
``[is BlogPost]`` that are ``BlogPost`` objects
================================ ===================================
================================ ===================================
Syntax Meaning
================================ ===================================
``User.<`` Objects that link to the user
``author`` via a link named author
``[is Comment]`` that are ``Comment`` objects
================================ ===================================
Backlinks can be inserted into a schema with the same format, except
that the type name (in this case ``User``) doesn't need to be specified.
.. code-block:: sdl-diff
type User {
required email: str;
multi friends: User;
+ all_links := .<author;
+ blog_links := .<author[is BlogPost];
+ comment_links := .<author[is Comment];
}
type BlogPost {
required title: str;
required author: User;
}
type Comment {
required text: str;
required author: User;
}
.. _ref_eql_paths_link_props:
Link properties
---------------
.. index:: linkprops, @
Paths can also reference :ref:`link properties <ref_datamodel_link_properties>`
with ``@`` notation. To demonstrate this, let's add a property to the ``User.
friends`` link:
.. code-block:: sdl-diff
type User {
required email: str;
- multi friends: User;
+ multi friends: User {
+ since: cal::local_date;
+ }
}
The following represents a set of all dates on which friendships were formed.
.. code-block:: edgeql
select User.friends@since;
Path roots
----------
For simplicity, all examples above use set references like ``User`` as the root
of the path; however, the root can be *any expression* returning object types.
Below, the root of the path is a *subquery*.
.. code-block:: edgeql-repl
db> with gel_lovers := (
... select BlogPost filter .title ilike "Gel is awesome"
... )
... select gel_lovers.author;
This expression returns a set of all ``Users`` who have written a blog post
titled "Gel is awesome".
For a full syntax definition, see the :ref:`Reference > Paths
<ref_reference_paths>`.
================================================================================
.. File: select.rst
.. _ref_eql_select:
Select
======
.. index:: select
The ``select`` command retrieves or computes a set of values. We've already
seen simple queries that select primitive values.
.. code-block:: edgeql-repl
db> select 'hello world';
{'hello world'}
db> select [1, 2, 3];
{[1, 2, 3]}
db> select {1, 2, 3};
{1, 2, 3}
With the help of a ``with`` block, we can add filters, ordering, and
pagination clauses.
.. code-block:: edgeql-repl
db> with x := {1, 2, 3, 4, 5}
... select x
... filter x >= 3;
{3, 4, 5}
db> with x := {1, 2, 3, 4, 5}
... select x
... order by x desc;
{5, 4, 3, 2, 1}
db> with x := {1, 2, 3, 4, 5}
... select x
... offset 1 limit 3;
{2, 3, 4}
These queries can also be rewritten to use inline aliases, like so:
.. code-block:: edgeql-repl
db> select x := {1, 2, 3, 4, 5}
... filter x >= 3;
.. _ref_eql_select_objects:
Selecting objects
-----------------
However most queries are selecting *objects* that live in the database. For
demonstration purposes, the queries below assume the following schema:
.. code-block:: sdl
module default {
abstract type Person {
required name: str { constraint exclusive };
}
type Hero extending Person {
secret_identity: str;
multi villains := .<nemesis[is Villain];
}
type Villain extending Person {
nemesis: Hero;
}
type Movie {
required title: str { constraint exclusive };
required release_year: int64;
multi characters: Person;
}
}
And the following inserts:
.. code-block:: edgeql-repl
db> insert Hero {
... name := "Spider-Man",
... secret_identity := "Peter Parker"
... };
{default::Hero {id: 6be1c9c6...}}
db> insert Hero {
... name := "Iron Man",
... secret_identity := "Tony Stark"
... };
{default::Hero {id: 6bf7115a... }}
db> for n in { "Sandman", "Electro", "Green Goblin", "Doc Ock" }
... union (
... insert Villain {
... name := n,
... nemesis := (select Hero filter .name = "Spider-Man")
... });
{
default::Villain {id: 6c22bdf0...},
default::Villain {id: 6c22c3d6...},
default::Villain {id: 6c22c46c...},
default::Villain {id: 6c22c502...},
}
db> insert Villain {
... name := "Obadiah Stane",
... nemesis := (select Hero filter .name = "Iron Man")
... };
{default::Villain {id: 6c42c4ec...}}
db> insert Movie {
... title := "Spider-Man: No Way Home",
... release_year := 2021,
... characters := (select Person filter .name in
... { "Spider-Man", "Sandman", "Electro", "Green Goblin", "Doc Ock" })
... };
{default::Movie {id: 6c60c28a...}}
db> insert Movie {
... title := "Iron Man",
... release_year := 2008,
... characters := (select Person filter .name in
... { "Iron Man", "Obadiah Stane" })
... };
{default::Movie {id: 6d1f430e...}}
Let's start by selecting all ``Villain`` objects in the database. In this
example, there are only five. Remember, ``Villain`` is a :ref:`reference
<ref_eql_set_references>` to the set of all Villain objects.
.. code-block:: edgeql-repl
db> select Villain;
{
default::Villain {id: 6c22bdf0...},
default::Villain {id: 6c22c3d6...},
default::Villain {id: 6c22c46c...},
default::Villain {id: 6c22c502...},
default::Villain {id: 6c42c4ec...},
}
.. note::
For the sake of readability, the ``id`` values have been truncated.
By default, this only returns the ``id`` of each object. If serialized to JSON,
this result would look like this:
.. code-block::
[
{"id": "6c22bdf0-5c03-11ee-99ff-dfaea4d947ce"},
{"id": "6c22c3d6-5c03-11ee-99ff-734255881e5d"},
{"id": "6c22c46c-5c03-11ee-99ff-c79f24cf638b"},
{"id": "6c22c502-5c03-11ee-99ff-cbacc3918129"},
{"id": "6c42c4ec-5c03-11ee-99ff-872c9906a467"}
]
.. _ref_eql_shapes:
Shapes
------
.. index:: select, shapes, { }
To specify which properties to select, we attach a **shape** to ``Villain``. A
shape can be attached to any object type expression in EdgeQL.
.. code-block:: edgeql-repl
db> select Villain { id, name };
{
default::Villain {id: 6c22bdf0..., name: 'Sandman'},
default::Villain {id: 6c22c3d6..., name: 'Electro'},
default::Villain {id: 6c22c46c..., name: 'Green Goblin'},
default::Villain {id: 6c22c502..., name: 'Doc Ock'},
default::Villain {id: 6c42c4ec..., name: 'Obadiah Stane'},
}
Nested shapes
^^^^^^^^^^^^^
.. index:: select, nested shapes
Nested shapes can be used to fetch linked objects and their properties. Here we
fetch all ``Villain`` objects and their nemeses.
.. code-block:: edgeql-repl
db> select Villain {
... name,
... nemesis: { name }
... };
{
default::Villain {
name: 'Sandman',
nemesis: default::Hero {name: 'Spider-Man'},
},
...
}
In the context of EdgeQL, computed links like ``Hero.villains`` are treated
identically to concrete/non-computed links like ``Villain.nemesis``.
.. code-block:: edgeql-repl
db> select Hero {
... name,
... villains: { name }
... };
{
default::Hero {
name: 'Spider-Man',
villains: {
default::Villain {name: 'Sandman'},
default::Villain {name: 'Electro'},
default::Villain {name: 'Green Goblin'},
default::Villain {name: 'Doc Ock'},
},
},
...
}
.. _ref_eql_select_splats:
Splats
^^^^^^
.. index:: select, splats, *, **, select *, select all, [is ].*, [is ].**
Splats allow you to select all properties of a type using the asterisk (``*``)
or all properties of the type and a single level of linked types with a double
asterisk (``**``).
.. edb:youtube-embed:: 9-I1qjIp3KI
Splats will help you more easily select all properties when using the REPL.
You can select all of an object's properties using the single splat:
.. code-block:: edgeql-repl
db> select Movie {*};
{
default::Movie {
id: 6c60c28a-5c03-11ee-99ff-dfa425012a05,
release_year: 2021,
title: 'Spider-Man: No Way Home',
},
default::Movie {
id: 6d1f430e-5c03-11ee-99ff-e731e8da06d9,
release_year: 2008,
title: 'Iron Man'
},
}
or you can select all of an object's properties and the properties of a single
level of nested objects with the double splat:
.. code-block:: edgeql-repl
db> select Movie {**};
{
default::Movie {
id: 6c60c28a-5c03-11ee-99ff-dfa425012a05,
release_year: 2021,
title: 'Spider-Man: No Way Home',
characters: {
default::Hero {
id: 6be1c9c6-5c03-11ee-99ff-63b1127d75f2,
name: 'Spider-Man'
},
default::Villain {
id: 6c22bdf0-5c03-11ee-99ff-dfaea4d947ce,
name: 'Sandman'
},
default::Villain {
id: 6c22c3d6-5c03-11ee-99ff-734255881e5d,
name: 'Electro'
},
default::Villain {
id: 6c22c46c-5c03-11ee-99ff-c79f24cf638b,,
name: 'Green Goblin'
},
default::Villain {
id: 6c22c502-5c03-11ee-99ff-cbacc3918129,
name: 'Doc Ock'
},
},
},
default::Movie {
id: 6d1f430e-5c03-11ee-99ff-e731e8da06d9,
release_year: 2008,
title: 'Iron Man',
characters: {
default::Hero {
id: 6bf7115a-5c03-11ee-99ff-c79c07f0e2db,
name: 'Iron Man'
},
default::Villain {
id: 6c42c4ec-5c03-11ee-99ff-872c9906a467,
name: 'Obadiah Stane'
},
},
},
}
.. note::
Splats are not yet supported in function bodies.
The splat expands all properties defined on the type as well as inherited
properties:
.. code-block:: edgeql-repl
db> select Hero {*};
{
default::Hero {
id: 6be1c9c6-5c03-11ee-99ff-63b1127d75f2,
name: 'Spider-Man',
secret_identity: 'Peter Parker'
},
default::Hero {
id: 6bf7115a-5c03-11ee-99ff-c79c07f0e2db,
name: 'Iron Man',
secret_identity: 'Tony Stark'
},
}
The splat here expands the heroes' names even though the ``name`` property is
not defined on the ``Hero`` type but on the ``Person`` type it extends. If we
want to select heroes but get only properties defined on the ``Person`` type,
we can do this instead:
.. code-block:: edgeql-repl
db> select Hero {Person.*};
{
default::Hero {
id: 6be1c9c6-5c03-11ee-99ff-63b1127d75f2,
name: 'Spider-Man'
},
default::Hero {
id: 6bf7115a-5c03-11ee-99ff-c79c07f0e2db,
name: 'Iron Man'
},
}
If there are links on our ``Person`` type, we can use ``Person.**`` in a
similar fashion to get all properties and one level of linked object
properties, but only for links and properties that are defined on the
``Person`` type.
You can use the splat to expand properties using a :ref:`type intersection
<ref_eql_types_intersection>`. Maybe we want to select all ``Person`` objects
with their names but also get any properties defined on the ``Hero`` for those
``Person`` objects which are also ``Hero`` objects:
.. code-block:: edgeql-repl
db> select Person {
... name,
... [is Hero].*
... };
{
default::Hero {
name: 'Spider-Man',
id: 6be1c9c6-5c03-11ee-99ff-63b1127d75f2,
secret_identity: 'Peter Parker'
},
default::Hero {
name: 'Iron Man'
id: 6bf7115a-5c03-11ee-99ff-c79c07f0e2db,
secret_identity: 'Tony Stark'
},
default::Villain {
name: 'Sandman',
id: 6c22bdf0-5c03-11ee-99ff-dfaea4d947ce,
secret_identity: {}
},
default::Villain {
name: 'Electro',
id: 6c22c3d6-5c03-11ee-99ff-734255881e5d,
secret_identity: {}
},
default::Villain {
name: 'Green Goblin',
id: 6c22c46c-5c03-11ee-99ff-c79f24cf638b,
secret_identity: {}
},
default::Villain {
name: 'Doc Ock',
id: 6c22c502-5c03-11ee-99ff-cbacc3918129,
secret_identity: {}
},
default::Villain {
name: 'Obadiah Stane',
id: 6c42c4ec-5c03-11ee-99ff-872c9906a467,
secret_identity: {}
},
}
The double splat also works with type intersection expansion to expand both
properties and links on the specified type.
.. code-block:: edgeql-repl
db> select Person {
... name,
... [is Hero].**
... };
{
default::Villain {
name: 'Sandman',
id: 6c22bdf0-5c03-11ee-99ff-dfaea4d947ce,
secret_identity: {},
villains: {}
},
default::Villain {
name: 'Electro',
id: 6c22c3d6-5c03-11ee-99ff-734255881e5d,
secret_identity: {},
villains: {}
},
default::Villain {
name: 'Green Goblin',
id: 6c22c46c-5c03-11ee-99ff-c79f24cf638b,
secret_identity: {},
villains: {}
},
default::Villain {
name: 'Doc Ock',
id: 6c22c502-5c03-11ee-99ff-cbacc3918129,
secret_identity: {},
villains: {}
},
default::Villain {
name: 'Obadiah Stane',
id: 6c42c4ec-5c03-11ee-99ff-872c9906a467,
secret_identity: {},
villains: {}
},
default::Hero {
name: 'Spider-Man',
id: 6be1c9c6-5c03-11ee-99ff-63b1127d75f2,
secret_identity: 'Peter Parker',
villains: {
default::Villain {
name: 'Electro',
id: 6c22c3d6-5c03-11ee-99ff-734255881e5d
},
default::Villain {
name: 'Sandman',
id: 6c22bdf0-5c03-11ee-99ff-dfaea4d947ce
},
default::Villain {
name: 'Doc Ock',
id: 6c22c502-5c03-11ee-99ff-cbacc3918129
},
default::Villain {
name: 'Green Goblin',
id: 6c22c46c-5c03-11ee-99ff-c79f24cf638b
},
},
},
}
With this query, we get ``name`` for each ``Person`` and all the properties and
one level of links on the ``Hero`` objects. We don't get ``Villain`` objects'
nemeses because that link is not covered by our double splat which only
expands ``Hero`` links. If the ``Villain`` type had properties defined on it,
we wouldn't get those with this query either.
.. _ref_eql_select_filter:
Filtering
---------
.. index:: select, filter, where
To filter the set of selected objects, use a ``filter <expr>`` clause. The
``<expr>`` that follows the ``filter`` keyword can be *any boolean expression*.
To reference the ``name`` property of the ``Villain`` objects being selected,
we use ``Villain.name``.
.. code-block:: edgeql-repl
db> select Villain {id, name}
... filter Villain.name = "Doc Ock";
{default::Villain {id: 6c22c502..., name: 'Doc Ock'}}
.. note::
This query contains two occurrences of ``Villain``. The first
(outer) is passed as the argument to ``select`` and refers to the set of all
``Villain`` objects. However the *inner* occurrence is inside the *scope* of
the ``select`` statement and refers to the *object being
selected*.
However, this looks a little clunky, so EdgeQL provides a shorthand: just drop
``Villain`` entirely and simply use ``.name``. Since we are selecting a set of
Villains, it's clear from context that ``.name`` must refer to a link/property
of the ``Villain`` type. In other words, we are in the **scope** of the
``Villain`` type.
.. code-block:: edgeql-repl
db> select Villain {name}
... filter .name = "Doc Ock";
{default::Villain {name: 'Doc Ock'}}
.. warning::
When using comparison operators like ``=`` or ``!=``, or boolean operators
``and``, ``or``, and ``not``, keep in mind that these operators will
produce an empty set if an operand is an empty set. Check out :ref:`our
boolean cheatsheet <ref_cheatsheet_boolean>` for more info and help on how
to mitigate this if you know your operands may be an empty set.
Filtering by ID
^^^^^^^^^^^^^^^
To filter by ``id``, remember to cast the desired ID to :ref:`uuid
<ref_std_uuid>`:
.. code-block:: edgeql-repl
db> select Villain {id, name}
... filter .id = <uuid>"6c22c502-5c03-11ee-99ff-cbacc3918129";
{
default::Villain {
id: '6c22c502-5c03-11ee-99ff-cbacc3918129',
name: 'Doc Ock'
}
}
Nested filters
^^^^^^^^^^^^^^
Filters can be added at every level of shape nesting. The query below applies a
filter to both the selected ``Hero`` objects and their linked ``villains``.
.. code-block:: edgeql-repl
db> select Hero {
... name,
... villains: {
... name
... } filter .name like "%O%"
... } filter .name ilike "%man";
{
default::Hero {
name: 'Spider-Man',
villains: {
default::Villain {
name: 'Doc Ock'
}
}
},
default::Hero {
name: 'Iron Man',
villains: {
default::Villain {
name: 'Obadiah Stane'
}
}
},
}
Note that the *scope* changes inside nested shapes. When we use ``.name`` in
the outer ``filter``, it refers to the name of the hero. But when we use
``.name`` in the nested ``villains`` shape, the scope has changed to
``Villain``.
Filtering on a known backlink
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Another handy use for backlinks is using them to filter and find items
when doing a ``select`` (or an ``update`` or other operation, of course).
This can work as a nice shortcut when you have the ID of one object that
links to a second object without a link back to the first.
Spider-Man's villains always have a grudging respect for him, and their names
can be displayed to reflect that if we know the ID of a movie that they
starred in. Note the ability to :ref:`cast from a uuid <ref_uuid_casting>`
to an object type.
.. code-block:: edgeql-repl
db> select Villain filter .<characters =
... <Movie><uuid>'6c60c28a-5c03-11ee-99ff-dfa425012a05' {
... name := .name ++ ', who got to see Spider-Man!'
... };
{
'Obadiah Stane',
'Sandman, who got to see Spider-Man!',
'Electro, who got to see Spider-Man!',
'Green Goblin, who got to see Spider-Man!',
'Doc Ock, who got to see Spider-Man!',
}
In other words, "select every ``Villain`` object that the ``Movie`` object
of this ID links to via a link called ``characters``".
A backlink is naturally not required, however. The same operation without
traversing a backlink would look like this:
.. code-block:: edgeql-repl
db> with movie :=
... <Movie><uuid>'6c60c28a-5c03-11ee-99ff-dfa425012a05',
... select movie.characters[is Villain] {
... name := .name ++ ', who got to see Spider-Man!'
... };
.. _ref_eql_select_order:
Filtering, ordering, and limiting of links
==========================================
Clauses like ``filter``, ``order by``, and ``limit`` can be used on links.
If no properties of a link are selected, you can place the clauses directly
inside the shape:
.. code-block:: edgeql
select User {
likes order by .title desc limit 10
};
If properties are selected, place the clauses after the link's shape:
.. code-block:: edgeql
select User {
likes: {
id,
title
} order by .title desc limit 10
};
Ordering
--------
.. index:: order by, sorting, asc, desc, then, empty first, empty last
Order the result of a query with an ``order by`` clause.
.. code-block:: edgeql-repl
db> select Villain { name }
... order by .name;
{
default::Villain {name: 'Doc Ock'},
default::Villain {name: 'Electro'},
default::Villain {name: 'Green Goblin'},
default::Villain {name: 'Obadiah Stane'},
default::Villain {name: 'Sandman'},
}
The expression provided to ``order by`` may be *any* singleton
expression, primitive or otherwise.
.. note::
In Gel all values are orderable. Objects are compared using their ``id``;
tuples and arrays are compared element-by-element from left to right. By
extension, the generic comparison operators :eql:op:`= <eq>`,
:eql:op:`\< <lt>`, :eql:op:`\> <gt>`, etc. can be used with any two
expressions of the same type.
You can also order by multiple
expressions and specify the *direction* with an ``asc`` (default) or ``desc``
modifier.
.. note::
When ordering by multiple expressions, arrays, or tuples, the leftmost
expression/element is compared. If these elements are the same, the next
element is used to "break the tie", and so on. If all elements are the same,
the order is not well defined.
.. code-block:: edgeql-repl
db> select Movie { title, release_year }
... order by
... .release_year desc then
... str_trim(.title) desc;
{
default::Movie {title: 'Spider-Man: No Way Home', release_year: 2021},
...
default::Movie {title: 'Iron Man', release_year: 2008},
}
When ordering by multiple expressions, each expression is separated with the
``then`` keyword. For a full reference on ordering, including how empty values
are handled, see :ref:`Reference > Commands > Select
<ref_reference_select_order>`.
.. _ref_eql_select_pagination:
Pagination
----------
.. index:: limit, offset
|Gel| supports ``limit`` and ``offset`` clauses. These are
typically used in conjunction with ``order by`` to maintain a consistent
ordering across pagination queries.
.. code-block:: edgeql-repl
db> select Villain { name }
... order by .name
... offset 2
... limit 2;
{
default::Villain {name: 'Obadiah Stane'},
default::Villain {name: 'Sandman'},
}
The expressions passed to ``limit`` and ``offset`` can be any singleton
``int64`` expression. This query fetches all Villains except the last (sorted
by name).
.. code-block:: edgeql-repl
db> select Villain {name}
... order by .name
... limit count(Villain) - 1;
{
default::Villain {name: 'Doc Ock'},
default::Villain {name: 'Electro'},
default::Villain {name: 'Green Goblin'},
default::Villain {name: 'Obadiah Stane'}, # no Sandman
}
You may pass the empty set to ``limit`` or ``offset``. Passing the empty set is
effectively the same as excluding ``limit`` or ``offset`` from your query
(i.e., no limit or no offset). This is useful if you need to parameterize
``limit`` and/or ``offset`` but may still need to execute your query without
providing one or the other.
.. code-block:: edgeql-repl
db> select Villain {name}
... order by .name
... offset <optional int64>$offset
... limit <optional int64>$limit;
Parameter <int64>$offset (Ctrl+D for empty set `{}`):
Parameter <int64>$limit (Ctrl+D for empty set `{}`):
{
default::Villain {name: 'Doc Ock'},
default::Villain {name: 'Electro'},
...
}
.. note::
If you parameterize ``limit`` and ``offset`` and want to reserve the option
to pass the empty set, make sure those parameters are ``optional`` as shown
in the example above.
.. _ref_eql_select_computeds:
Computed fields
---------------
.. index:: computeds, :=
Shapes can contain *computed fields*. These are EdgeQL expressions that are
computed on the fly during the execution of the query. As with other clauses,
we can use :ref:`leading dot notation <ref_dot_notation>` (e.g. ``.name``) to
refer to the properties and links of the object type currently *in scope*.
.. code-block:: edgeql-repl
db> select Villain {
... name,
... name_upper := str_upper(.name)
... };
{
default::Villain {
id: 6c22bdf0...,
name: 'Sandman',
name_upper: 'SANDMAN',
},
...
}
As with nested filters, the *current scope* changes inside nested shapes.
.. code-block:: edgeql-repl
db> select Villain {
... id,
... name,
... name_upper := str_upper(.name),
... nemesis: {
... secret_identity,
... real_name_upper := str_upper(.secret_identity)
... }
... };
{
default::Villain {
id: 6c22bdf0...,
name: 'Sandman',
name_upper: 'SANDMAN',
nemesis: default::Hero {
secret_identity: 'Peter Parker',
real_name_upper: 'PETER PARKER',
},
},
...
}
.. _ref_eql_select_backlinks:
Backlinks
---------
.. index:: .<
Fetching backlinks is a common use case for computed fields. To demonstrate
this, let's fetch a list of all movies starring a particular Hero.
.. code-block:: edgeql-repl
db> select Hero {
... name,
... movies := .<characters[is Movie] { title }
... } filter .name = "Iron Man";
{
default::Hero {
name: 'Iron Man',
movies: {
default::Movie {title: 'Iron Man'}
},
},
}
.. note::
The computed backlink ``movies`` is a combination of the *backlink
operator* ``.<`` and a type intersection ``[is Movie]``. For a full
reference on backlink syntax, see :ref:`EdgeQL > Paths
<ref_eql_paths_backlinks>`.
Instead of re-declaring backlinks inside every query where they're needed, it's
common to add them directly into your schema as computed links.
.. code-block:: sdl-diff
abstract type Person {
required name: str {
constraint exclusive;
};
+ multi movies := .<characters[is Movie]
}
.. note::
In the example above, the ``Person.movies`` is a ``multi`` link. Including
these keywords is optional, since Gel can infer this from the assigned
expression ``.<characters[is Movie]``. However, it's a good practice to
include the explicit keywords to make the schema more readable and "sanity
check" the cardinality.
This simplifies future queries; ``Person.movies`` can now be traversed in
shapes just like a non-computed link.
.. code-block:: edgeql
select Hero {
name,
movies: { title }
} filter .name = "Iron Man";
.. _ref_eql_select_subqueries:
Subqueries
----------
.. index:: nested queries, composition, composing queries, composable,
embedded queries, embedding queries
There's no limit to the complexity of computed expressions. EdgeQL is designed
to be fully composable; entire queries can be embedded inside each other.
Below, we use a subquery to select all movies containing a villain's nemesis.
.. code-block:: edgeql-repl
db> select Villain {
... name,
... nemesis_name := .nemesis.name,
... movies_with_nemesis := (
... select Movie { title }
... filter Villain.nemesis in .characters
... )
... };
{
default::Villain {
name: 'Sandman',
nemesis_name: 'Spider-Man',
movies_with_nemesis: {
default::Movie {title: 'Spider-Man: No Way Home'}
}
},
...
}
.. _ref_eql_select_polymorphic:
Polymorphic queries
-------------------
.. index:: polymorphism
All queries thus far have referenced concrete object types: ``Hero`` and
``Villain``. However, both of these types extend the abstract type ``Person``,
from which they inherit the ``name`` property.
Polymorphic sets
^^^^^^^^^^^^^^^^
It's possible to directly query all ``Person`` objects; the resulting set will
be a mix of ``Hero`` and ``Villain`` objects (and possibly other subtypes of
``Person``, should they be declared).
.. code-block:: edgeql-repl
db> select Person { name };
{
default::Hero {name: 'Spider-Man'},
default::Hero {name: 'Iron Man'},
default::Villain {name: 'Doc Ock'},
default::Villain {name: 'Obadiah Stane'},
...
}
You may also encounter such "mixed sets" when querying a link that points to an
abstract type (such as ``Movie.characters``) or a :eql:op:`union type
<typeor>`.
.. code-block:: edgeql-repl
db> select Movie {
... title,
... characters: {
... name
... }
... }
... filter .title = "Iron Man 2";
{
default::Movie {
title: 'Iron Man',
characters: {
default::Villain {name: 'Obadiah Stane'},
default::Hero {name: 'Iron Man'}
}
}
}
Polymorphic fields
^^^^^^^^^^^^^^^^^^
.. index:: [is ].
We can fetch different properties *conditional* on the subtype of each object
by prefixing property/link references with ``[is <type>]``. This is known as a
**polymorphic query**.
.. code-block:: edgeql-repl
db> select Person {
... name,
... secret_identity := [is Hero].secret_identity,
... number_of_villains := count([is Hero].villains),
... nemesis := [is Villain].nemesis {
... name
... }
... };
{
...
default::Villain {
name: 'Obadiah Stane',
secret_identity: {},
number_of_villains: 0,
nemesis: default::Hero {
name: 'Iron Man'
}
},
default::Hero {
name: 'Spider-Man',
secret_identity: 'Peter Parker',
number_of_villains: 4,
nemesis: {}
},
...
}
This syntax might look familiar; it's the :ref:`type intersection
<ref_eql_types_intersection>` again. In effect, this operator conditionally
returns the value of the referenced field only if the object matches a
particular type. If the match fails, an empty set is returned.
The line ``secret_identity := [is Hero].secret_identity`` is a bit redundant,
since the computed property has the same name as the polymorphic field. In
these cases, EdgeQL supports a shorthand.
.. code-block:: edgeql-repl
db> select Person {
... name,
... [is Hero].secret_identity,
... [is Villain].nemesis: {
... name
... }
... };
{
...
default::Villain {
name: 'Obadiah Stane',
secret_identity: {},
nemesis: default::Hero {name: 'Iron Man'}
},
default::Hero {
name: 'Spider-Man',
secret_identity: 'Peter Parker',
nemesis: {}
},
...
}
Filtering polymorphic links
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Relatedly, it's possible to filter polymorphic links by subtype. Below, we
exclusively fetch the ``Movie.characters`` of type ``Hero``.
.. code-block:: edgeql-repl
db> select Movie {
... title,
... characters[is Hero]: {
... secret_identity
... },
... };
{
default::Movie {
title: 'Spider-Man: No Way Home',
characters: {default::Hero {secret_identity: 'Peter Parker'}},
},
default::Movie {
title: 'Iron Man',
characters: {default::Hero {secret_identity: 'Tony Stark'}},
},
...
}
Accessing types in polymorphic queries
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
While the type of an object is displayed alongside the results of polymorphic
queries run in the REPL, this is simply a convenience of the REPL and not a
property that can be accessed. This is particularly noticeable if you cast an
object to ``json``, making it impossible to determine the type if the query is
polymorphic. First, the result of a query as the REPL presents it with type
annotations displayed:
.. code-block:: edgeql-repl
db> select Person limit 1;
{default::Villain {id: 6c22bdf0-5c03-11ee-99ff-dfaea4d947ce}}
Note the type ``default::Villain``, which is displayed for the user's
convenience but is not actually part of the data returned. This is the same
query with the result cast as ``json`` to show only the data returned:
.. code-block:: edgeql-repl
db> select <json>Person limit 1;
{Json("{\"id\": \"6c22bdf0-5c03-11ee-99ff-dfaea4d947ce\"}")}
.. note::
We will continue to cast subesequent examples in this section to ``json``,
not because this is required for any of the functionality being
demonstrated, but to remove the convenience type annotations provided by
the REPL and make it easier to see what data is actually being returned by
the query.
The type of an object is found inside ``__type__`` which is a link that
carries various information about the object's type, including its ``name``.
.. code-block:: edgeql-repl
db> select <json>Person {
... __type__: {
... name
... }
... } limit 1;
{Json("{\"__type__\": {\"name\": \"default::Villain\"}}")}
This information can be pulled into the top level by assigning a name to
the ``name`` property inside ``__type__``:
.. code-block:: edgeql-repl
db> select <json>Person { type := .__type__.name } limit 1;
{Json("{\"type\": \"default::Villain\"}")}
There is nothing magical about ``__type__``; it is a simple link to an object
of the type ``ObjectType`` which contains all of the possible information to
know about the type of the current object. The splat operator can be used to
see this object's makeup, while the double splat operator produces too much
output to show on this page. Playing around with the splat and double splat
operator inside ``__type__`` is a quick way to get some insight into the
internals of Gel.
.. code-block:: edgeql-repl
db> select Person.__type__ {*} limit 1;
{
schema::ObjectType {
id: 48be3a94-5bf3-11ee-bd60-0b44b607e31d,
name: 'default::Hero',
internal: false,
builtin: false,
computed_fields: [],
final: false,
is_final: false,
abstract: false,
is_abstract: false,
inherited_fields: [],
from_alias: false,
is_from_alias: false,
expr: {},
compound_type: false,
is_compound_type: false,
},
}
.. _ref_eql_select_free_objects:
Free objects
------------
.. index:: ad hoc type
To select several values simultaneously, you can "bundle" them into a "free
object". Free objects are a set of key-value pairs that can contain any
expression. Here, the term "free" is used to indicate that the object in
question is not an instance of a particular *object type*; instead, it's
constructed ad hoc inside the query.
.. code-block:: edgeql-repl
db> select {
... my_string := "This is a string",
... my_number := 42,
... several_numbers := {1, 2, 3},
... all_heroes := Hero { name }
... };
{
{
my_string: 'This is a string',
my_number: 42,
several_numbers: {1, 2, 3},
all_heroes: {
default::Hero {name: 'Spider-Man'},
default::Hero {name: 'Iron Man'},
},
},
}
Note that the result is a *singleton* but each key corresponds to a set of
values, which may have any cardinality.
.. _ref_eql_select_with:
With block
----------
All top-level EdgeQL statements (``select``, ``insert``, ``update``, and
``delete``) can be prefixed with a ``with`` block. These blocks let you declare
standalone expressions that can be used in your query.
.. code-block:: edgeql-repl
db> with hero_name := "Iron Man"
... select Hero { secret_identity }
... filter .name = hero_name;
{default::Hero {secret_identity: 'Tony Stark'}}
For full documentation on ``with``, see :ref:`EdgeQL > With <ref_eql_with>`.
.. list-table::
:class: seealso
* - **See also**
* - :ref:`Reference > Commands > Select <ref_eql_statements_select>`
* - :ref:`Cheatsheets > Selecting data <ref_cheatsheet_select>`
================================================================================
.. File: sets.rst
.. _ref_eql_sets:
Sets
====
.. _ref_eql_everything_is_a_set:
Everything is a set
-------------------
.. index:: set, multiset, cardinality, empty set, singleton
All values in EdgeQL are actually **sets**: a collection of values of a given
**type**. All elements of a set must have the same type. The number of items in
a set is known as its **cardinality**. A set with a cardinality of zero is
referred to as an **empty set**. A set with a cardinality of one is known as a
**singleton**.
.. _ref_eql_set_constructor:
Constructing sets
-----------------
.. index:: constructor, { }, union
Set literals are declared with *set constructor* syntax: a comma-separated
list of values inside a set of ``{curly braces}``.
.. code-block:: edgeql-repl
db> select {"set", "of", "strings"};
{"set", "of", "strings"}
db> select {1, 2, 3};
{1, 2, 3}
In actuality, curly braces are a syntactic sugar for the
:eql:op:`union` operator. The previous examples are perfectly
equivalent to the following:
.. code-block:: edgeql-repl
db> select "set" union "of" union "strings";
{"set", "of", "strings"}
db> select 1 union 2 union 3;
{1, 2, 3}
A consequence of this is that nested sets are *flattened*.
.. code-block:: edgeql-repl
db> select {1, {2, {3, 4}}};
{1, 2, 3, 4}
db> select 1 union (2 union (3 union 4));
{1, 2, 3, 4}
All values in a set must have the same type. For convenience, Gel will
*implicitly cast* values to other types, as long as there is no loss of
information (e.g. converting a ``int16`` to an ``int64``). For a full
reference, see the casting table in :ref:`Standard Library > Casts
<ref_eql_casts_table>`.
.. code-block:: edgeql-repl
db> select {1, 1.5};
{1.0, 1.5}
db> select {1, 1234.5678n};
{1.0n, 1234.5678n}
Attempting to declare a set containing elements of *incompatible* types is not
permitted.
.. code-block:: edgeql-repl
db> select {"apple", 3.14};
error: QueryError: set constructor has arguments of incompatible types
'std::str' and 'std::float64'
.. note::
Types are considered *compatible* if one can be implicitly cast into the
other. For reference on implicit castability, see :ref:`Standard Library >
Casts <ref_eql_casts_table>`.
.. _ref_eql_set_literals_are_singletons:
Literals are singletons
-----------------------
Literal syntax like ``6`` or ``"hello world"`` is just a shorthand for
declaring a *singleton* of a given type. This is why the literals we created in
the previous section were printed inside braces: to indicate that these values
are *actually sets*.
.. code-block:: edgeql-repl
db> select 6;
{6}
db> select "hello world";
{"hello world"}
Wrapping a literal in curly braces does not change the meaning of the
expression. For instance, ``"hello world"`` is *exactly equivalent* to
``{"hello world"}``.
.. code-block:: edgeql-repl
db> select {"hello world"};
{"hello world"}
db> select "hello world" = {"hello world"};
{true}
You can retrieve the cardinality of a set with the :eql:func:`count` function.
.. code-block:: edgeql-repl
db> select count('aaa');
{1}
db> select count({'aaa', 'bbb'});
{2}
.. _ref_eql_empty_sets:
Empty sets
----------
.. index:: null, exists
The reason EdgeQL introduced the concept of *sets* is to eliminate the concept
of ``null``. In SQL databases ``null`` is a special value denoting the absence
of data; in Gel the absence of data is just an empty set.
.. note::
Why is the existence of NULL a problem? Put simply, it's an edge case that
permeates all of SQL and is often handled inconsistently in different
circumstances. A number of specific inconsistencies are documented in detail
in the `We Can Do Better Than SQL
<https://www.geldata.com/blog/we-can-do-better-than-sql#null-a-bag-of-surprises>`_
post on the Gel blog. For broader context, see Tony Hoare's talk
`"The Billion Dollar Mistake" <https://bit.ly/3H238oG>`_.
Declaring empty sets isn't as simple as ``{}``; in EdgeQL, all expressions are
*strongly typed*, including empty sets. With nonempty sets (like ``{1, 2, 3}``)
, the type is inferred from the set's contents (``int64``). But with empty sets
this isn't possible, so an *explicit cast* is required.
.. code-block:: edgeql-repl
db> select {};
error: QueryError: expression returns value of indeterminate type
┌─ query:1:8
│
1 │ select {};
│ ^^ Consider using an explicit type cast.
db> select <int64>{};
{}
db> select <str>{};
{}
db> select count(<str>{});
{0}
You can check whether or not a set is *empty* with the :eql:op:`exists`
operator.
.. code-block:: edgeql-repl
db> select exists <str>{};
{false}
db> select exists {'not', 'empty'};
{true}
.. _ref_eql_set_references:
Set references
--------------
.. index:: pointer, alias, with
A set reference is a *pointer* to a set of values. Most commonly, this is the
name of an :ref:`object type <ref_datamodel_object_types>` you've declared in
your schema.
.. code-block:: edgeql-repl
db> select User;
{
default::User {id: 9d2ce01c-35e8-11ec-acc3-83b1377efea0},
default::User {id: b0e0dd0c-35e8-11ec-acc3-abf1752973be},
}
db> select count(User);
{2}
It may also be an *alias*, which can be defined in a :ref:`with block
<ref_eql_with>` or as an :ref:`alias declaration <ref_eql_sdl_aliases>` in your
schema.
.. note::
In the example above, the ``User`` object type was declared inside the
``default`` module. If it was in a non-``default`` module (say,
``my_module``, we would need to use its *fully-qualified* name.
.. code-block:: edgeql-repl
db> select my_module::User;
.. _ref_eql_set_distinct:
Multisets
---------
.. index:: multisets, distinct, duplicates
Technically sets in Gel are actually *multisets*, because they can contain
duplicates of the same element. To eliminate duplicates, use the
:eql:op:`distinct` set operator.
.. code-block:: edgeql-repl
db> select {'aaa', 'aaa', 'aaa'};
{'aaa', 'aaa', 'aaa'}
db> select distinct {'aaa', 'aaa', 'aaa'};
{'aaa'}
.. _ref_eql_set_in:
Checking membership
-------------------
.. index:: in
Use the :eql:op:`in` operator to check whether a set contains a particular
element.
.. code-block:: edgeql-repl
db> select 'aaa' in {'aaa', 'bbb', 'ccc'};
{true}
db> select 'ddd' in {'aaa', 'bbb', 'ccc'};
{false}
.. _ref_eql_set_union:
Merging sets
------------
.. index:: union, merge
Use the :eql:op:`union` operator to merge two sets.
.. code-block:: edgeql-repl
db> select 'aaa' union 'bbb' union 'ccc';
{'aaa', 'bbb', 'ccc'}
db> select {1, 2} union {3.1, 4.4};
{1.0, 2.0, 3.1, 4.4}
Finding common members
----------------------
.. index:: intersect
Use the :eql:op:`intersect` operator to find common members between two sets.
.. code-block:: edgeql-repl
db> select {1, 2, 3, 4, 5} intersect {3, 4, 5, 6, 7};
{3, 5, 4}
db> select {'a', 'b', 'c', 'd', 'e'} intersect {'c', 'd', 'e', 'f', 'g'};
{'e', 'd', 'c'}
If set members are repeated in both sets, they will be repeated in the set
produced by :eql:op:`intersect` the same number of times they are repeated in
both of the operand sets.
.. code-block:: edgeql-repl
db> select {0, 1, 1, 1, 2, 3, 3} intersect {1, 3, 3, 3, 3, 3};
{1, 3, 3}
In this example, ``1`` appears three times in the first set but only once in
the second, so it appears only once in the result. ``3`` appears twice in the
first set and five times in the second. Both ``3`` appearances in the first set
are overlapped by ``3`` appearances in the second, so they both end up in the
resulting set.
Removing common members
-----------------------
.. index:: except
Use the :eql:op:`except` operator to leave only the members in the first set
that do not appear in the second set.
.. code-block:: edgeql-repl
db> select {1, 2, 3, 4, 5} except {3, 4, 5, 6, 7};
{1, 2}
db> select {'a', 'b', 'c', 'd', 'e'} except {'c', 'd', 'e', 'f', 'g'};
{'b', 'a'}
When :eql:op:`except` eliminates a common member that is repeated, it never
eliminates more than the number of instances of that member appearing in the
second set.
.. code-block:: edgeql-repl
db> select {0, 1, 1, 1, 2, 3, 3} except {1, 3, 3, 3, 3, 3};
{0, 1, 1, 2}
In this example, both sets share the member ``1``. The first set contains three
of them while the second contains only one. The result retains two ``1``
members from the first set since the sets shared only a single ``1`` in common.
The second set has five ``3`` members to the first set's two, so both of the
first set's ``3`` members are eliminated from the resulting set.
.. _ref_eql_set_coalesce:
Coalescing
----------
.. index:: empty set, ??, default values, optional
Occasionally in queries, you need to handle the case where a set is empty. This
can be achieved with a coalescing operator :eql:op:`?? <coalesce>`. This is
commonly used to provide default values for optional :ref:`query parameters
<ref_eql_params>`.
.. code-block:: edgeql-repl
db> select 'value' ?? 'default';
{'value'}
db> select <str>{} ?? 'default';
{'default'}
.. note::
Coalescing is an example of a function/operator with :ref:`optional inputs
<ref_sdl_function_typequal>`. By default, passing an empty set into a
function/operator will "short circuit" the operation and return an empty set.
However it's possible to mark inputs as *optional*, in which case the
operation will be defined over empty sets. Another example is
:eql:func:`count`, which returns ``{0}`` when an empty set is passed as
input.
.. _ref_eql_set_type_filter:
Inheritance
-----------
.. index:: type intersection, backlinks, [is ]
|Gel| schemas support :ref:`inheritance <ref_datamodel_objects_inheritance>`;
types (usually object types) can extend one or more other types. For instance
you may declare an abstract object type ``Media`` that is extended by ``Movie``
and ``TVShow``.
.. code-block:: sdl
abstract type Media {
required title: str;
}
type Movie extending Media {
release_year: int64;
}
type TVShow extending Media {
num_seasons: int64;
}
A set of type ``Media`` may contain both ``Movie`` and ``TVShow``
objects.
.. code-block:: edgeql-repl
db> select Media;
{
default::Movie {id: 9d2ce01c-35e8-11ec-acc3-83b1377efea0},
default::Movie {id: 3bfe4900-3743-11ec-90ee-cb73d2740820},
default::TVShow {id: b0e0dd0c-35e8-11ec-acc3-abf1752973be},
}
We can use the *type intersection* operator ``[is <type>]`` to restrict the
elements of a set by subtype.
.. code-block:: edgeql-repl
db> select Media[is Movie];
{
default::Movie {id: 9d2ce01c-35e8-11ec-acc3-83b1377efea0},
default::Movie {id: 3bfe4900-3743-11ec-90ee-cb73d2740820},
}
db> select Media[is TVShow];
{
default::TVShow {id: b0e0dd0c-35e8-11ec-acc3-abf1752973be}
}
Type filters are commonly used in conjunction with :ref:`backlinks
<ref_eql_select_backlinks>`.
.. _ref_eql_set_aggregate:
Aggregate vs element-wise operations
------------------------------------
.. index:: cartesian product
EdgeQL provides a large library of built-in functions and operators for
handling data structures. It's useful to consider functions/operators as either
*aggregate* or *element-wise*.
.. note::
This is an over-simplification, but it's a useful mental model when just
starting out with Gel. For a more complete guide, see :ref:`Reference >
Cardinality <ref_reference_cardinality>`.
*Aggregate* operations are applied to the set *as a whole*; they
accept a set with arbitrary cardinality and return a *singleton* (or perhaps an
empty set if the input was also empty).
.. code-block:: edgeql-repl
db> select count({'aaa', 'bbb'});
{2}
db> select sum({1, 2, 3});
{6}
db> select min({1, 2, 3});
{1}
Element-wise operations are applied on *each element* of a set.
.. code-block:: edgeql-repl
db> select str_upper({'aaa', 'bbb'});
{'AAA', 'BBB'}
db> select {1, 2, 3} ^ 2;
{1, 4, 9}
db> select str_split({"hello world", "hi again"}, " ");
{["hello", "world"], ["hi", "again"]}
When an *element-wise* operation accepts two or more inputs, the operation is
applied to all possible combinations of inputs; in other words, the operation
is applied to the *Cartesian product* of the inputs.
.. code-block:: edgeql-repl
db> select {'aaa', 'bbb'} ++ {'ccc', 'ddd'};
{'aaaccc', 'aaaddd', 'bbbccc', 'bbbddd'}
Accordingly, operations involving an empty set typically return an empty set.
In constrast, aggregate operations like :eql:func:`count` are able to operate
on empty sets.
.. code-block:: edgeql-repl
db> select <str>{} ++ 'ccc';
{}
db> select count(<str>{});
{0}
For a more complete discussion of cardinality, see :ref:`Reference >
Cardinality <ref_reference_cardinality>`.
.. _ref_eql_set_array_conversion:
Conversion to/from arrays
-------------------------
.. index:: array_unpack, array_agg, converting sets
Both arrays and sets are collections of values that share a type. EdgeQL
provides ways to convert one into the other.
.. note::
Remember that *all values* in EdgeQL are sets; an array literal is just a
singleton set of arrays. So here, "converting" a set into an array means
converting a set of type ``x`` into another set with cardinality
``1`` (a singleton) and type ``array<x>``.
.. code-block:: edgeql-repl
db> select array_unpack([1,2,3]);
{1, 2, 3}
db> select array_agg({1,2,3});
{[1, 2, 3]}
Arrays are an *ordered collection*, whereas sets are generally unordered
(unless explicitly sorted with an ``order by`` clause in a :ref:`select
<ref_eql_select_order>` statement).
Element-wise scalar operations in the standard library cannot be applied to
arrays, so sets of scalars are typically easier to manipulate, search, and
transform than arrays.
.. code-block:: edgeql-repl
db> select str_trim({' hello', 'world '});
{'hello', 'world'}
db> select str_trim([' hello', 'world ']);
error: QueryError: function "str_trim(arg0: array<std::str>)" does not exist
Some :ref:`aggregate <ref_reference_cardinality_aggregate>` operations have
analogs that operate on arrays. For instance, the set function
:eql:func:`count` is analogous to the array function :eql:func:`len`.
Reference
---------
.. list-table::
* - Set operators
- :eql:op:`distinct` :eql:op:`in` :eql:op:`union`
:eql:op:`exists` :eql:op:`if..else`
:eql:op:`?? <coalesce>` :eql:op:`detached`
:eql:op:`[is type] <isintersect>`
* - Utility functions
- :eql:func:`count` :eql:func:`enumerate`
* - Cardinality assertion
- :eql:func:`assert_distinct` :eql:func:`assert_single`
:eql:func:`assert_exists`
================================================================================
.. File: transactions.rst
.. _ref_eql_transactions:
Transactions
============
.. index:: start transaction, declare savepoint, release savepoint,
rollback to savepoint, rollback, commit
EdgeQL supports atomic transactions. The transaction API consists
of several commands:
:eql:stmt:`start transaction`
Start a transaction, specifying the isolation level, access mode (``read
only`` vs ``read write``), and deferrability.
:eql:stmt:`declare savepoint`
Establish a new savepoint within the current transaction. A savepoint is a
intermediate point in a transaction flow that provides the ability to
partially rollback a transaction.
:eql:stmt:`release savepoint`
Destroys a savepoint previously defined in the current transaction.
:eql:stmt:`rollback to savepoint`
Rollback to the named savepoint. All changes made after the savepoint
are discarded. The savepoint remains valid and can be rolled back
to again later, if needed.
:eql:stmt:`rollback`
Rollback the entire transaction. All updates made within the transaction are
discarded.
:eql:stmt:`commit`
Commit the transaction. All changes made by the transaction become visible
to others and will persist if a crash occurs.
Client libraries
----------------
There is rarely a reason to use these commands directly. All Gel client
libraries provide dedicated transaction APIs that handle transaction creation
under the hood.
Examples below show a transaction that sends 10 cents from the account
of a ``BankCustomer`` called ``'Customer1'`` to ``BankCustomer`` called
``'Customer2'``. The equivalent Gel schema and queries are:
.. code-block::
module default {
type BankCustomer {
required name: str;
required balance: int64;
}
}
update BankCustomer
filter .name = 'Customer1'
set { bank_balance := .bank_balance -10 };
update BankCustomer
filter .name = 'Customer2'
set { bank_balance := .bank_balance +10 }
TypeScript/JS
^^^^^^^^^^^^^
Using an EdgeQL query string:
.. code-block:: typescript
client.transaction(async tx => {
await tx.execute(`update BankCustomer
filter .name = 'Customer1'
set { bank_balance := .bank_balance -10 }`);
await tx.execute(`update BankCustomer
filter .name = 'Customer2'
set { bank_balance := .bank_balance +10 }`);
});
Using the querybuilder:
.. code-block:: typescript
const query1 = e.update(e.BankCustomer, () => ({
filter_single: { name: "Customer1" },
set: {
bank_balance: { "-=": 10 }
},
}));
const query2 = e.update(e.BankCustomer, () => ({
filter_single: { name: "Customer2" },
set: {
bank_balance: { "+=": 10 }
},
}));
client.transaction(async (tx) => {
await query1.run(tx);
await query2.run(tx);
});
Full documentation at :ref:`Client Libraries > TypeScript/JS <gel-js-intro>`;
Python
^^^^^^
.. code-block:: python
async for tx in client.transaction():
async with tx:
await tx.execute("""update BankCustomer
filter .name = 'Customer1'
set { bank_balance := .bank_balance -10 };""")
await tx.execute("""update BankCustomer
filter .name = 'Customer2'
set { bank_balance := .bank_balance +10 };""")
Full documentation at :ref:`Client Libraries > Python <gel-python-intro>`;
Golang
^^^^^^
.. code-block:: go
err = client.Tx(ctx, func(ctx context.Context, tx *gel.Tx) error {
query1 := `update BankCustomer
filter .name = 'Customer1'
set { bank_balance := .bank_balance -10 };`
if e := tx.Execute(ctx, query1); e != nil {
return e
}
query2 := `update BankCustomer
filter .name = 'Customer2'
set { bank_balance := .bank_balance +10 };`
if e := tx.Execute(ctx, query2); e != nil {
return e
}
return nil
})
if err != nil {
log.Fatal(err)
}
Full documentation at :ref:`Client Libraries > Go <gel-go-intro>`.
Rust
^^^^
.. code-block:: rust
let balance_change_query = "update BankCustomer
filter .name = <str>$0
set { bank_balance := .bank_balance + <int32>$1 }";
client
.transaction(|mut conn| async move {
conn.execute(balance_change_query, &("Customer1", -10))
.await
.expect("Execute should have worked");
conn.execute(balance_change_query, &("Customer2", 10))
.await
.expect("Execute should have worked");
Ok(())
})
.await
.expect("Transaction should have worked");
.. XXX: Add Rust docs
.. Full documentation at :ref:`Client Libraries > Rust <ref_rust_index>`.
================================================================================
.. File: types.rst
.. _ref_eql_types:
=====
Types
=====
The foundation of EdgeQL is Gel's rigorous type system. There is a set of
EdgeQL operators and functions for changing, introspecting, and filtering by
types.
.. _ref_eql_types_names:
Type expressions
----------------
.. index:: array< >, tuple< >
Type expressions are exactly what they sound like: EdgeQL expressions that
refer to a type. Most commonly, these are simply the *names* of established
types: ``str``, ``int64``, ``BlogPost``, etc. Arrays and tuples have a
dedicated type syntax.
.. list-table::
* - **Type**
- **Syntax**
* - Array
- ``array<x>``
* - Tuple (unnamed)
- ``tuple<x, y, z>``
* - Tuple (named)
- ``tuple<foo: x, bar: y>``
For additional details on type syntax, see :ref:`Schema > Primitive Types
<ref_datamodel_primitives>`.
.. _ref_eql_types_typecast:
Type casting
------------
.. index:: casts, < >, find object by id
Type casting is used to convert primitive values into another type. Casts are
indicated with angle brackets containing a type expression.
.. code-block:: edgeql-repl
db> select <str>10;
{"10"}
db> select <bigint>10;
{10n}
db> select <array<str>>[1, 2, 3];
{['1', '2', '3']}
db> select <tuple<str, float64, bigint>>(1, 2, 3);
{('1', 2, 3n)}
Type casts are useful for declaring literals for types like ``datetime``,
``uuid``, and ``int16`` that don't have a dedicated syntax.
.. code-block:: edgeql-repl
db> select <datetime>'1999-03-31T15:17:00Z';
{<datetime>'1999-03-31T15:17:00Z'}
db> select <int16>42;
{42}
db> select <uuid>'89381587-705d-458f-b837-860822e1b219';
{89381587-705d-458f-b837-860822e1b219}
There are limits to what values can be cast to a certain type. In some cases
two types are entirely incompatible, like ``bool`` and ``int64``; in other
cases, the source data must be in a particular format, like casting ``str`` to
``datetime``. For a comprehensive table of castability, see :ref:`Standard
Library > Casts <ref_eql_casts_table>`.
Type casts can only be used on primitive expressions, not object type
expressions. Every object stored in the database is strongly and immutably
typed; you can't simply convert an object to an object of a different type.
.. code-block:: edgeql-repl
db> select <BlogPost>10;
QueryError: cannot cast 'std::int64' to 'default::BlogPost'
db> select <int64>'asdf';
InvalidValueError: invalid input syntax for type std::int64: "asdf"
db> select <int16>100000000000000n;
NumericOutOfRangeError: std::int16 out of range
.. lint-off
You can cast a UUID into an object:
.. code-block:: edgeql-repl
db> select <Hero><uuid>'01d9cc22-b776-11ed-8bef-73f84c7e91e7';
{default::Hero {id: 01d9cc22-b776-11ed-8bef-73f84c7e91e7}}
If you try to cast a UUID that no object of the type has as its ``id``
property, you'll get an error:
.. code-block:: edgeql-repl
db> select <Hero><uuid>'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa';
gel error: CardinalityViolationError: 'default::Hero' with id 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' does not exist
.. lint-on
.. _ref_eql_types_intersection:
Type intersections
------------------
.. index:: [is ]
All elements of a given set have the same type; however, in the context of
*sets of objects*, this type might be ``abstract`` and contain elements of
multiple concrete subtypes. For instance, a set of ``Media`` objects may
contain both ``Movie`` and ``TVShow`` objects.
.. code-block:: edgeql-repl
db> select Media;
{
default::Movie {id: 9d2ce01c-35e8-11ec-acc3-83b1377efea0},
default::Movie {id: 3bfe4900-3743-11ec-90ee-cb73d2740820},
default::TVShow {id: b0e0dd0c-35e8-11ec-acc3-abf1752973be},
}
We can use the *type intersection* operator to restrict the elements of a set
by subtype.
.. code-block:: edgeql-repl
db> select Media[is Movie];
{
default::Movie {id: 9d2ce01c-35e8-11ec-acc3-83b1377efea0},
default::Movie {id: 3bfe4900-3743-11ec-90ee-cb73d2740820},
}
Logically, this computes the intersection of the ``Media`` and ``Movie`` sets;
since only ``Movie`` objects occur in both sets, this can be conceptualized as
a "filter" that removes all elements that aren't of type ``Movie``.
.. Type unions
.. -----------
.. You can create a type union with the pipe operator: :eql:op:`type | type
.. <typeor>`. This is mostly commonly used for object types.
.. .. code-block:: edgeql-repl
.. db> select 5 is int32 | int64;
.. {true}
.. db> select Media is Movie | TVShow;
.. {true, true, true}
Type checking
-------------
.. index:: is
The ``[is foo]`` "type intersection" syntax should not be confused with the
*type checking* operator :eql:op:`is`.
.. code-block:: edgeql-repl
db> select 5 is int64;
{true}
db> select {3.14, 2.718} is not int64;
{true, true}
db> select Media is Movie;
{true, true, false}
The ``typeof`` operator
-----------------------
.. index:: typeof
The type of any expression can be extracted with the :eql:op:`typeof`
operator. This can be used in any expression that expects a type.
.. code-block:: edgeql-repl
db> select <typeof 5>'100';
{100}
db> select "tuna" is typeof "trout";
{true}
Introspection
-------------
The entire type system of Gel is *stored inside Gel*. All types are
introspectable as instances of the ``schema::Type`` type. For a set of
introspection examples, see :ref:`Guides > Introspection
<ref_datamodel_introspection>`.
================================================================================
.. File: update.rst
.. _ref_eql_update:
Update
======
.. index:: update, filter, set
The ``update`` command is used to update existing objects.
.. code-block:: edgeql-repl
db> update Hero
... filter .name = "Hawkeye"
... set { name := "Ronin" };
{default::Hero {id: d476b12e-3e7b-11ec-af13-2717f3dc1d8a}}
If you omit the ``filter`` clause, all objects will be updated. This is useful
for updating values across all objects of a given type. The example below
cleans up all ``Hero.name`` values by trimming whitespace and converting them
to title case.
.. code-block:: edgeql-repl
db> update Hero
... set { name := str_trim(str_title(.name)) };
{default::Hero {id: d476b12e-3e7b-11ec-af13-2717f3dc1d8a}}
Syntax
^^^^^^
The structure of the ``update`` statement (``update...filter...set``) is an
intentional inversion of SQL's ``UPDATE...SET...WHERE`` syntax. Curiously, in
SQL, the ``where`` clauses typically occur *last* despite being applied before
the ``set`` statement. EdgeQL is structured to reflect this; first, a target
set is specified, then filters are applied, then the data is updated.
Updating properties
-------------------
.. index:: unset
To explicitly unset a property that is not required, set it to an empty set.
.. code-block:: edgeql
update Person filter .id = <uuid>$id set { middle_name := {} };
Updating links
--------------
.. index:: :=, +=, -=
When updating links, the ``:=`` operator will *replace* the set of linked
values.
.. code-block:: edgeql-repl
db> update movie
... filter .title = "Black Widow"
... set {
... characters := (
... select Person
... filter .name in { "Black Widow", "Yelena", "Dreykov" }
... )
... };
{default::Title {id: af706c7c-3e98-11ec-abb3-4bbf3f18a61a}}
db> select Movie { num_characters := count(.characters) }
... filter .title = "Black Widow";
{default::Movie {num_characters: 3}}
To add additional linked items, use the ``+=`` operator.
.. code-block:: edgeql-repl
db> update Movie
... filter .title = "Black Widow"
... set {
... characters += (insert Villain {name := "Taskmaster"})
... };
{default::Title {id: af706c7c-3e98-11ec-abb3-4bbf3f18a61a}}
db> select Movie { num_characters := count(.characters) }
... filter .title = "Black Widow";
{default::Movie {num_characters: 4}}
To remove items, use ``-=``.
.. code-block:: edgeql-repl
db> update Movie
... filter .title = "Black Widow"
... set {
... characters -= Villain # remove all villains
... };
{default::Title {id: af706c7c-3e98-11ec-abb3-4bbf3f18a61a}}
db> select Movie { num_characters := count(.characters) }
... filter .title = "Black Widow";
{default::Movie {num_characters: 2}}
Returning data on update
------------------------
.. index:: update, returning
By default, ``update`` returns only the inserted object's ``id`` as seen in the
examples above. If you want to get additional data back, you may wrap your
``update`` with a ``select`` and apply a shape specifying any properties and
links you want returned:
.. code-block:: edgeql-repl
db> select (update Hero
... filter .name = "Hawkeye"
... set { name := "Ronin" }
... ) {id, name};
{
default::Hero {
id: d476b12e-3e7b-11ec-af13-2717f3dc1d8a,
name: "Ronin"
}
}
With blocks
-----------
.. index:: with update
All top-level EdgeQL statements (``select``, ``insert``, ``update``, and
``delete``) can be prefixed with a ``with`` block. This is useful for updating
the results of a complex query.
.. code-block:: edgeql-repl
db> with people := (
... select Person
... order by .name
... offset 3
... limit 3
... )
... update people
... set { name := str_trim(.name) };
{
default::Hero {id: d4764c66-3e7b-11ec-af13-df1ba5b91187},
default::Hero {id: d7d7e0f6-40ae-11ec-87b1-3f06bed494b9},
default::Villain {id: d477a836-3e7b-11ec-af13-4fea611d1c31},
}
.. note::
You can pass any object-type expression into ``update``, including
polymorphic ones (as above).
You can also use ``with`` to make returning additional data from an update more
readable:
.. code-block:: edgeql-repl
db> with UpdatedHero := (update Hero
... filter .name = "Hawkeye"
... set { name := "Ronin" }
... )
... select UpdatedHero {
... id,
... name
... };
{
default::Hero {
id: d476b12e-3e7b-11ec-af13-2717f3dc1d8a,
name: "Ronin"
}
}
See also
--------
For documentation on performing *upsert* operations, see :ref:`EdgeQL > Insert
> Upserts <ref_eql_upsert>`.
.. list-table::
* - :ref:`Reference > Commands > Update <ref_eql_statements_update>`
* - :ref:`Cheatsheets > Updating data <ref_cheatsheet_update>`
================================================================================
.. File: with.rst
.. _ref_eql_with:
With
====
.. index:: composition, composing queries, composable, CTE,
common table expressions, subquery, subqueries
All top-level EdgeQL statements (``select``, ``insert``, ``update``, and
``delete``) can be prefixed by a ``with`` block. These blocks contain
declarations of standalone expressions that can be used in your query.
.. code-block:: edgeql-repl
db> with my_str := "hello world"
... select str_title(my_str);
{'Hello World'}
The ``with`` clause can contain more than one variable. Earlier variables can
be referenced by later ones. Taken together, it becomes possible to write
"script-like" queries that execute several statements in sequence.
.. code-block:: edgeql-repl
db> with a := 5,
... b := 2,
... c := a ^ b
... select c;
{25}
Subqueries
^^^^^^^^^^
There's no limit to the complexity of computed expressions. EdgeQL is fully
composable; queries can simply be embedded inside each other. The following
query fetches a list of all movies featuring at least one of the original six
Avengers.
.. code-block:: edgeql-repl
db> with avengers := (select Hero filter .name in {
... 'Iron Man',
... 'Black Widow',
... 'Captain America',
... 'Thor',
... 'Hawkeye',
... 'The Hulk'
... })
... select Movie {title}
... filter avengers in .characters;
{
default::Movie {title: 'Iron Man'},
default::Movie {title: 'The Incredible Hulk'},
default::Movie {title: 'Iron Man 2'},
default::Movie {title: 'Thor'},
default::Movie {title: 'Captain America: The First Avenger'},
...
}
.. _ref_eql_with_params:
Query parameters
^^^^^^^^^^^^^^^^
.. index:: with
A common use case for ``with`` clauses is the initialization of :ref:`query
parameters <ref_eql_params>`.
.. code-block:: edgeql
with user_id := <uuid>$user_id
select User { name }
filter .id = user_id;
For a full reference on using query parameters, see :ref:`EdgeQL > Parameters
<ref_eql_params>`.
Module alias
^^^^^^^^^^^^
.. index:: with, as module
Another use of ``with`` is to provide aliases for modules. This can be useful
for long queries which reuse many objects or functions from the same module.
.. code-block:: edgeql
with http as module std::net::http
select http::ScheduledRequest
filter .method = http::Method.POST;
If the aliased module does not exist at the top level, but does exists as a
part of the ``std`` module, that will be used automatically.
.. code-block:: edgeql
with http as module net::http # <- omitting std
select http::ScheduledRequest
filter .method = http::Method.POST;
Module selection
^^^^^^^^^^^^^^^^
.. index:: with module, fully-qualified names
By default, the *active module* is ``default``, so all schema objects inside
this module can be referenced by their *short name*, e.g. ``User``,
``BlogPost``, etc. To reference objects in other modules, we must use
fully-qualified names (``default::Hero``).
However, ``with`` clauses also provide a mechanism for changing the *active
module* on a per-query basis.
.. code-block:: edgeql-repl
db> with module schema
... select ObjectType;
This ``with module`` clause changes the default module to schema, so we can
refer to ``schema::ObjectType`` (a built-in Gel type) as simply
``ObjectType``.
As with module aliases, if the active module does not exist at the top level,
but does exist as part of the ``std`` module, that will be used automatically.
.. code-block:: edgeql-repl
db> with module math select abs(-1);
{1}
.. list-table::
:class: seealso
* - **See also**
* - :ref:`Reference > Commands > With <ref_eql_statements_with>`
================================================================================
.. File: abstract.rst
.. _ref_std_abstract_types:
==============
Abstract Types
==============
Abstract types are used to describe polymorphic functions, otherwise known as
"generic functions," which can be called on a broad range of types.
----------
.. eql:type:: anytype
:index: any anytype
A generic type.
It is a placeholder used in cases where no specific type
requirements are needed, such as defining polymorphic parameters
in functions and operators.
----------
.. eql:type:: std::anyobject
:index: any anytype object
A generic object.
Similarly to :eql:type:`anytype`, this type is used to denote a generic
object. This is useful when defining polymorphic parameters in functions
and operators as it conforms to whatever type is actually passed. This is
different friom :eql:type:`BaseObject` which although is the parent type
of any object also only has an ``id`` property, making access to other
properties and links harder.
----------
.. eql:type:: std::anyscalar
:index: any anytype scalar
An abstract base scalar type.
All scalar types are derived from this type.
----------
.. eql:type:: std::anyenum
:index: any anytype enum
An abstract base enumerated type.
All :eql:type:`enum` types are derived from this type.
----------
.. eql:type:: anytuple
:index: any anytype anytuple
A generic tuple.
Similarly to :eql:type:`anytype`, this type is used to denote a generic
tuple without detailing its component types. This is useful when defining
polymorphic parameters in functions and operators.
Abstract Numeric Types
======================
These abstract numeric types extend :eql:type:`anyscalar`.
.. eql:type:: std::anyint
:index: any anytype int
An abstract base scalar type for
:eql:type:`int16`, :eql:type:`int32`, and :eql:type:`int64`.
----------
.. eql:type:: std::anyfloat
:index: any anytype float
An abstract base scalar type for
:eql:type:`float32` and :eql:type:`float64`.
----------
.. eql:type:: std::anyreal
:index: any anytype
An abstract base scalar type for
:eql:type:`anyint`, :eql:type:`anyfloat`, and :eql:type:`decimal`.
Abstract Range Types
====================
There are some types that can be used to construct :ref:`ranges
<ref_std_range>`. These scalar types are distinguished by the following
abstract types:
.. eql:type:: std::anypoint
:index: any anypoint anyrange
Abstract base type for all valid ranges.
Abstract base scalar type for :eql:type:`int32`, :eql:type:`int64`,
:eql:type:`float32`, :eql:type:`float64`, :eql:type:`decimal`,
:eql:type:`datetime`, :eql:type:`cal::local_datetime`, and
:eql:type:`cal::local_date`.
----------
.. eql:type:: std::anydiscrete
:index: any anydiscrete anyrange discrete
An abstract base type for all valid *discrete* ranges.
This is an abstract base scalar type for :eql:type:`int32`,
:eql:type:`int64`, and :eql:type:`cal::local_date`.
----------
.. eql:type:: std::anycontiguous
:index: any anycontiguous anyrange
An abstract base type for all valid *contiguous* ranges.
This is an abstract base scalar type for :eql:type:`float32`,
:eql:type:`float64`, :eql:type:`decimal`, :eql:type:`datetime`, and
:eql:type:`cal::local_datetime`.
================================================================================
.. File: array.rst
.. _ref_std_array:
======
Arrays
======
:edb-alt-title: Array Functions and Operators
.. list-table::
:class: funcoptable
* - :eql:op:`array[i] <arrayidx>`
- :eql:op-desc:`arrayidx`
* - :eql:op:`array[from:to] <arrayslice>`
- :eql:op-desc:`arrayslice`
* - :eql:op:`array ++ array <arrayplus>`
- :eql:op-desc:`arrayplus`
* - :eql:op:`= <eq>` :eql:op:`\!= <neq>` :eql:op:`?= <coaleq>`
:eql:op:`?!= <coalneq>` :eql:op:`\< <lt>` :eql:op:`\> <gt>`
:eql:op:`\<= <lteq>` :eql:op:`\>= <gteq>`
- Comparison operators
* - :eql:func:`len`
- Returns the number of elements in the array.
* - :eql:func:`contains`
- Checks if an element is in the array.
* - :eql:func:`find`
- Finds the index of an element in the array.
* - :eql:func:`array_join`
- Renders an array to a string or byte-string.
* - :eql:func:`array_fill`
- :eql:func-desc:`array_fill`
* - :eql:func:`array_replace`
- :eql:func-desc:`array_replace`
* - :eql:func:`array_set`
- :eql:func-desc:`array_set`
* - :eql:func:`array_insert`
- :eql:func-desc:`array_insert`
* - :eql:func:`array_agg`
- :eql:func-desc:`array_agg`
* - :eql:func:`array_get`
- :eql:func-desc:`array_get`
* - :eql:func:`array_unpack`
- :eql:func-desc:`array_unpack`
Arrays store expressions of the *same type* in an ordered list.
.. _ref_std_array_constructor:
Constructing arrays
^^^^^^^^^^^^^^^^^^^
An array constructor is an expression that consists of a sequence of
comma-separated expressions *of the same type* enclosed in square brackets.
It produces an array value:
.. eql:synopsis::
"[" <expr> [, ...] "]"
For example:
.. code-block:: edgeql-repl
db> select [1, 2, 3];
{[1, 2, 3]}
db> select [('a', 1), ('b', 2), ('c', 3)];
{[('a', 1), ('b', 2), ('c', 3)]}
Empty arrays
^^^^^^^^^^^^
You can also create an empty array, but it must be done by providing the type
information using type casting. Gel cannot infer the type of an empty array
created otherwise. For example:
.. code-block:: edgeql-repl
db> select [];
QueryError: expression returns value of indeterminate type
Hint: Consider using an explicit type cast.
### select [];
### ^
db> select <array<int64>>[];
{[]}
Reference
^^^^^^^^^
.. eql:type:: std::array
:index: array
An ordered list of values of the same type.
Array indexing starts at zero.
An array can contain any type except another array. In Gel, arrays are
always one-dimensional.
An array type is created implicitly when an :ref:`array
constructor <ref_std_array_constructor>` is used:
.. code-block:: edgeql-repl
db> select [1, 2];
{[1, 2]}
The array types themselves are denoted by ``array`` followed by their
sub-type in angle brackets. These may appear in cast operations:
.. code-block:: edgeql-repl
db> select <array<str>>[1, 4, 7];
{['1', '4', '7']}
db> select <array<bigint>>[1, 4, 7];
{[1n, 4n, 7n]}
Array types may also appear in schema declarations:
.. code-block:: sdl
type Person {
str_array: array<str>;
json_array: array<json>;
}
See also the list of standard :ref:`array functions <ref_std_array>`, as
well as :ref:`generic functions <ref_std_generic>` such as
:eql:func:`len`.
----------
.. eql:operator:: arrayidx: array<anytype> [ int64 ] -> anytype
:index: [int], index access
Accesses the array element at a given index.
Example:
.. code-block:: edgeql-repl
db> select [1, 2, 3][0];
{1}
db> select [(x := 1, y := 1), (x := 2, y := 3.3)][1];
{(x := 2, y := 3.3)}
This operator also allows accessing elements from the end of the array
using a negative index:
.. code-block:: edgeql-repl
db> select [1, 2, 3][-1];
{3}
Referencing a non-existent array element will result in an error:
.. code-block:: edgeql-repl
db> select [1, 2, 3][4];
InvalidValueError: array index 4 is out of bounds
----------
.. eql:operator:: arrayslice: array<anytype> [ int64 : int64 ] -> anytype
:index: [int:int]
Produces a sub-array from an existing array.
Omitting the lower bound of an array slice will default to a lower bound
of zero.
Omitting the upper bound will default the upper bound to the length of the
array.
The lower bound of an array slice is inclusive while the upper bound is
not.
Examples:
.. code-block:: edgeql-repl
db> select [1, 2, 3][0:2];
{[1, 2]}
db> select [1, 2, 3][2:];
{[3]}
db> select [1, 2, 3][:1];
{[1]}
db> select [1, 2, 3][:-2];
{[1]}
Referencing an array slice beyond the array boundaries will result in an
empty array (unlike a direct reference to a specific index). Slicing with
a lower bound less than the minimum index or a upper bound greater than
the maximum index are functionally equivalent to not specifying those
bounds for your slice:
.. code-block:: edgeql-repl
db> select [1, 2, 3][1:20];
{[2, 3]}
db> select [1, 2, 3][10:20];
{[]}
---------
.. eql:operator:: arrayplus: array<anytype> ++ array<anytype> -> array<anytype>
:index: ++, concatenate, join, add
Concatenates two arrays of the same type into one.
.. code-block:: edgeql-repl
db> select [1, 2, 3] ++ [99, 98];
{[1, 2, 3, 99, 98]}
----------
.. eql:function:: std::array_agg(s: set of anytype) -> array<anytype>
:index: aggregate array set
Returns an array made from all of the input set elements.
The ordering of the input set will be preserved if specified:
.. code-block:: edgeql-repl
db> select array_agg({2, 3, 5});
{[2, 3, 5]}
db> select array_agg(User.name order by User.name);
{['Alice', 'Bob', 'Joe', 'Sam']}
----------
.. eql:function:: std::array_get(array: array<anytype>, \
index: int64, \
named only default: anytype = {} \
) -> optional anytype
:index: array access get
Returns the element of a given *array* at the specified *index*.
If the index is out of the array's bounds, the *default* argument or
``{}`` (empty set) will be returned.
This works the same as the :eql:op:`array indexing operator <arrayidx>`,
except that if the index is out of bounds, an empty set
of the array element's type is returned instead of raising an exception:
.. code-block:: edgeql-repl
db> select array_get([2, 3, 5], 1);
{3}
db> select array_get([2, 3, 5], 100);
{}
db> select array_get([2, 3, 5], 100, default := 42);
{42}
----------
.. eql:function:: std::array_unpack(array: array<anytype>) -> set of anytype
:index: set array unpack
Returns the elements of an array as a set.
.. note::
The ordering of the returned set is not guaranteed.
However, if it is wrapped in a call to :eql:func:`enumerate`,
the assigned indexes are guaranteed to match the array.
.. code-block:: edgeql-repl
db> select array_unpack([2, 3, 5]);
{3, 2, 5}
db> select enumerate(array_unpack([2, 3, 5]));
{(1, 3), (0, 2), (2, 5)}
----------
.. eql:function:: std::array_join(array: array<str>, delimiter: str) -> str
std::array_join(array: array<bytes>, \
delimiter: bytes) -> bytes
:index: join array_to_string implode
Renders an array to a string or byte-string.
Join a string array into a single string using a specified *delimiter*:
.. code-block:: edgeql-repl
db> select array_join(['one', 'two', 'three'], ', ');
{'one, two, three'}
Similarly, an array of :eql:type:`bytes` can be joined as a single value
using a specified *delimiter*:
.. code-block:: edgeql-repl
db> select array_join([b'\x01', b'\x02', b'\x03'], b'\xff');
{b'\x01\xff\x02\xff\x03'}
----------
.. eql:function:: std::array_fill(val: anytype, n: int64) -> array<anytype>
:index: fill
Returns an array of the specified size, filled with the provided value.
Create an array of size *n* where every element has the value *val*.
.. code-block:: edgeql-repl
db> select array_fill(0, 5);
{[0, 0, 0, 0, 0]}
db> select array_fill('n/a', 3);
{['n/a', 'n/a', 'n/a']}
----------
.. eql:function:: std::array_replace(array: array<anytype>, \
old: anytype, \
new: anytype) \
-> array<anytype>
Returns an array with all occurrences of one value replaced by another.
Return an array where every *old* value is replaced with *new*.
.. code-block:: edgeql-repl
db> select array_replace([1, 1, 2, 3, 5], 1, 99);
{[99, 99, 2, 3, 5]}
db> select array_replace(['h', 'e', 'l', 'l', 'o'], 'l', 'L');
{['h', 'e', 'L', 'L', 'o']}
----------
.. eql:function:: std::array_set(array: array<anytype>, \
idx: int64, \
val: anytype) \
-> array<anytype>
.. versionadded:: 6.0
Returns an array with an value at a specific index replaced by another.
Return an array where the value at the index indicated by *idx* is
replaced with *val*.
.. code-block:: edgeql-repl
db> select array_set(['hello', 'world'], 0, 'goodbye');
{['goodbye', 'world']}
db> select array_set([1, 1, 2, 3], 1, 99);
{[1, 99, 2, 3]}
----------
.. eql:function:: std::array_insert(array: array<anytype>, \
idx: int64, \
val: anytype) \
-> array<anytype>
.. versionadded:: 6.0
Returns an array with an value inserted at a specific.
Return an array where the value *val* is inserted at the index indicated by *idx*.
.. code-block:: edgeql-repl
db> select array_insert(['the', 'brown', 'fox'], 1, 'quick');
{['the', 'quick', 'brown', 'fox']}
db> select array_insert([1, 1, 2, 3], 1, 99);
{[1, 99, 1, 2, 3]}
================================================================================
.. File: bool.rst
.. _ref_std_logical:
========
Booleans
========
:edb-alt-title: Boolean Functions and Operators
.. list-table::
:class: funcoptable
* - :eql:type:`bool`
- Boolean type
* - :eql:op:`bool or bool <or>`
- :eql:op-desc:`or`
* - :eql:op:`bool and bool <and>`
- :eql:op-desc:`and`
* - :eql:op:`not bool <not>`
- :eql:op-desc:`not`
* - :eql:op:`= <eq>` :eql:op:`\!= <neq>` :eql:op:`?= <coaleq>`
:eql:op:`?!= <coalneq>` :eql:op:`\< <lt>` :eql:op:`\> <gt>`
:eql:op:`\<= <lteq>` :eql:op:`\>= <gteq>`
- Comparison operators
* - :eql:func:`all`
- :eql:func-desc:`all`
* - :eql:func:`any`
- :eql:func-desc:`any`
* - :eql:func:`assert`
- :eql:func-desc:`assert`
----------
.. eql:type:: std::bool
A boolean type of either ``true`` or ``false``.
EdgeQL has case-insensitive keywords and that includes the boolean
literals:
.. code-block:: edgeql-repl
db> select (True, true, TRUE);
{(true, true, true)}
db> select (False, false, FALSE);
{(false, false, false)}
These basic operators will always result in a boolean type value (although,
for some of them, that value may be the empty set if an operand is the
empty set):
- :eql:op:`= <eq>`
- :eql:op:`\!= <neq>`
- :eql:op:`?= <coaleq>`
- :eql:op:`?!= <coalneq>`
- :eql:op:`in`
- :eql:op:`not in <in>`
- :eql:op:`\< <lt>`
- :eql:op:`\> <gt>`
- :eql:op:`\<= <lteq>`
- :eql:op:`\>= <gteq>`
- :eql:op:`like`
- :eql:op:`ilike`
These operators will result in a boolean type value even if the right
operand is the empty set:
- :eql:op:`in`
- :eql:op:`not in <in>`
These operators will always result in a boolean ``true`` or ``false``
value, even if either operand is the empty set:
- :eql:op:`?= <coaleq>`
- :eql:op:`?!= <coalneq>`
These operators will produce the empty set if either operand is the empty
set:
- :eql:op:`= <eq>`
- :eql:op:`\!= <neq>`
- :eql:op:`\< <lt>`
- :eql:op:`\> <gt>`
- :eql:op:`\<= <lteq>`
- :eql:op:`\>= <gteq>`
- :eql:op:`like`
- :eql:op:`ilike`
If you need to use these operators and it's possible one or both operands
will be the empty set, you can ensure a ``bool`` product by
:eql:op:`coalescing <coalesce>`. With ``=`` and ``!=``, you can use their
respective dedicated coalescing operators, ``?=`` and ``?!=``. See each
individual operator for an example.
Some boolean operator examples:
.. code-block:: edgeql-repl
db> select true and 2 < 3;
{true}
db> select '!' IN {'hello', 'world'};
{false}
It's possible to get a boolean by casting a :eql:type:`str` or
:eql:type:`json` value into it:
.. code-block:: edgeql-repl
db> select <bool>('true');
{true}
db> select <bool>to_json('false');
{false}
:ref:`Filter clauses <ref_eql_statements_select_filter>` must
always evaluate to a boolean:
.. code-block:: edgeql
select User
filter .name ilike 'alice';
----------
.. eql:operator:: or: bool or bool -> bool
:index: or
Evaluates ``true`` if either boolean is ``true``.
.. code-block:: edgeql-repl
db> select false or true;
{true}
.. warning::
When either operand in an ``or`` is an empty set, the result will not
be a ``bool`` but instead an empty set.
.. code-block:: edgeql-repl
db> select true or <bool>{};
{}
If one of the operands in an ``or`` operation could be an empty set,
you may want to use the :eql:op:`coalesce` operator (``??``) on that
side to ensure you will still get a ``bool`` result.
.. code-block:: edgeql-repl
db> select true or (<bool>{} ?? false);
{true}
----------
.. eql:operator:: and: bool and bool -> bool
:index: and
Evaluates ``true`` if both booleans are ``true``.
.. code-block:: edgeql-repl
db> select false and true;
{false}
.. warning::
When either operand in an ``and`` is an empty set, the result will not
be a ``bool`` but instead an empty set.
.. code-block:: edgeql-repl
db> select true and <bool>{};
{}
If one of the operands in an ``and`` operation could be an empty set,
you may want to use the :eql:op:`coalesce` operator (``??``) on that
side to ensure you will still get a ``bool`` result.
.. code-block:: edgeql-repl
db> select true and (<bool>{} ?? false);
{false}
----------
.. eql:operator:: not: not bool -> bool
:index: not
Logically negates a given boolean value.
.. code-block:: edgeql-repl
db> select not false;
{true}
.. warning::
When the operand in a ``not`` is an empty set, the result will not be a
``bool`` but instead an empty set.
.. code-block:: edgeql-repl
db> select not <bool>{};
{}
If the operand in a ``not`` operation could be an empty set, you may
want to use the :eql:op:`coalesce` operator (``??``) on that side to
ensure you will still get a ``bool`` result.
.. code-block:: edgeql-repl
db> select not (<bool>{} ?? false);
{true}
----------
The ``and`` and ``or`` operators are commutative.
The truth tables are as follows:
+-------+-------+---------------+--------------+--------------+
| a | b | a ``and`` b | a ``or`` b | ``not`` a |
+=======+=======+===============+==============+==============+
| true | true | true | true | false |
+-------+-------+---------------+--------------+--------------+
| true | false | false | true | false |
+-------+-------+---------------+--------------+--------------+
| false | true | false | true | true |
+-------+-------+---------------+--------------+--------------+
| false | false | false | false | true |
+-------+-------+---------------+--------------+--------------+
----------
The operators ``and``/``or`` and the functions :eql:func:`all`/:eql:func:`any`
differ in the way they handle an empty set (``{}``). Both ``and`` and ``or``
operators apply to the cross-product of their operands. If either operand is
an empty set, the result will also be an empty set. For example:
.. code-block:: edgeql-repl
db> select {true, false} and <bool>{};
{}
db> select true and <bool>{};
{}
Operating on an empty set with :eql:func:`all`/:eql:func:`any` does *not*
return an empty set:
.. code-block:: edgeql-repl
db> select all(<bool>{});
{true}
db> select any(<bool>{});
{false}
:eql:func:`all` returns ``true`` because the empty set contains no false
values.
:eql:func:`any` returns ``false`` because the empty set contains no
true values.
The :eql:func:`all` and :eql:func:`any` functions are generalized to apply to
sets of values, including ``{}``. Thus they have the following truth
table:
+-------+-------+-----------------+-----------------+
| a | b | ``all({a, b})`` | ``any({a, b})`` |
+=======+=======+=================+=================+
| true | true | true | true |
+-------+-------+-----------------+-----------------+
| true | false | false | true |
+-------+-------+-----------------+-----------------+
| {} | true | true | true |
+-------+-------+-----------------+-----------------+
| {} | false | false | false |
+-------+-------+-----------------+-----------------+
| false | true | false | true |
+-------+-------+-----------------+-----------------+
| false | false | false | false |
+-------+-------+-----------------+-----------------+
| true | {} | true | true |
+-------+-------+-----------------+-----------------+
| false | {} | false | false |
+-------+-------+-----------------+-----------------+
| {} | {} | true | false |
+-------+-------+-----------------+-----------------+
Since :eql:func:`all` and :eql:func:`any` apply to sets as a whole,
missing values (represented by ``{}``) are just that - missing. They
don't affect the overall result.
To understand the last line in the above truth table it's useful to
remember that ``all({a, b}) = all(a) and all(b)`` and ``any({a, b}) =
any(a) or any(b)``.
For more customized handling of ``{}``, use the :eql:op:`?? <coalesce>`
operator.
----------
.. eql:function:: std::assert( \
input: bool, \
named only message: optional str = <str>{} \
) -> bool
Checks that the input bool is ``true``.
If the input bool is ``false``, ``assert`` raises a
``QueryAssertionError``. Otherwise, this function returns ``true``.
.. code-block:: edgeql-repl
db> select assert(true);
{true}
db> select assert(false);
gel error: QueryAssertionError: assertion failed
db> select assert(false, message := 'value is not true');
gel error: QueryAssertionError: value is not true
``assert`` can be used in triggers to create more powerful constraints. In
this schema, the ``Person`` type has both ``friends`` and ``enemies``
links. You may not want a ``Person`` to be both a friend and an enemy of
the same ``Person``. ``assert`` can be used inside a trigger to easily
prohibit this.
.. code-block:: sdl
type Person {
required name: str;
multi friends: Person;
multi enemies: Person;
trigger prohibit_frenemies after insert, update for each do (
assert(
not exists (__new__.friends intersect __new__.enemies),
message := "Invalid frenemies",
)
)
}
With this trigger in place, it is impossible to link the same ``Person`` as
both a friend and an enemy of any other person.
.. code-block:: edgeql-repl
db> insert Person {name := 'Quincey Morris'};
{default::Person {id: e4a55480-d2de-11ed-93bd-9f4224fc73af}}
db> insert Person {name := 'Dracula'};
{default::Person {id: e7f2cff0-d2de-11ed-93bd-279780478afb}}
db> update Person
... filter .name = 'Quincey Morris'
... set {
... enemies := (
... select detached Person filter .name = 'Dracula'
... )
... };
{default::Person {id: e4a55480-d2de-11ed-93bd-9f4224fc73af}}
db> update Person
... filter .name = 'Quincey Morris'
... set {
... friends := (
... select detached Person filter .name = 'Dracula'
... )
... };
gel error: GelError: Invalid frenemies
In the following examples, the ``size`` properties of the ``File`` objects
are ``1024``, ``1024``, and ``131,072``.
.. code-block:: edgeql-repl
db> for obj in (select File)
... union (assert(obj.size <= 128*1024, message := 'file too big'));
{true, true, true}
db> for obj in (select File)
... union (assert(obj.size <= 64*1024, message := 'file too big'));
gel error: QueryAssertionError: file too big
You may call ``assert`` in the ``order by`` clause of your ``select``
statement. This will ensure it is called only on objects that pass your
filter.
.. code-block:: edgeql-repl
db> select File { name, size }
... order by assert(.size <= 128*1024, message := "file too big");
{
default::File {name: 'File 2', size: 1024},
default::File {name: 'Asdf 3', size: 1024},
default::File {name: 'File 1', size: 131072},
}
db> select File { name, size }
... order by assert(.size <= 64*1024, message := "file too big");
gel error: QueryAssertionError: file too big
db> select File { name, size }
... filter .size <= 64*1024
... order by assert(.size <= 64*1024, message := "file too big");
{
default::File {name: 'File 2', size: 1024},
default::File {name: 'Asdf 3', size: 1024}
}
================================================================================
.. File: bytes.rst
.. _ref_std_bytes:
=====
Bytes
=====
:edb-alt-title: Bytes Functions and Operators
.. list-table::
:class: funcoptable
* - :eql:type:`bytes`
- Byte sequence
* - :eql:type:`Endian`
- An enum for indicating integer value encoding.
* - :eql:op:`bytes[i] <bytesidx>`
- :eql:op-desc:`bytesidx`
* - :eql:op:`bytes[from:to] <bytesslice>`
- :eql:op-desc:`bytesslice`
* - :eql:op:`bytes ++ bytes <bytesplus>`
- :eql:op-desc:`bytesplus`
* - :eql:op:`= <eq>` :eql:op:`\!= <neq>` :eql:op:`?= <coaleq>`
:eql:op:`?!= <coalneq>` :eql:op:`\< <lt>` :eql:op:`\> <gt>`
:eql:op:`\<= <lteq>` :eql:op:`\>= <gteq>`
- Comparison operators
* - :eql:func:`len`
- Returns the number of bytes.
* - :eql:func:`contains`
- Checks if the byte sequence contains a given subsequence.
* - :eql:func:`find`
- Finds the index of the first occurrence of a subsequence.
* - :eql:func:`to_bytes`
- :eql:func-desc:`to_bytes`
* - :eql:func:`to_str`
- :eql:func-desc:`to_str`
* - :eql:func:`to_int16`
- :eql:func-desc:`to_int16`
* - :eql:func:`to_int32`
- :eql:func-desc:`to_int32`
* - :eql:func:`to_int64`
- :eql:func-desc:`to_int64`
* - :eql:func:`to_uuid`
- :eql:func-desc:`to_uuid`
* - :eql:func:`bytes_get_bit`
- :eql:func-desc:`bytes_get_bit`
* - :eql:func:`bit_count`
- :eql:func-desc:`bit_count`
* - :eql:func:`enc::base64_encode`
- :eql:func-desc:`enc::base64_encode`
* - :eql:func:`enc::base64_decode`
- :eql:func-desc:`enc::base64_decode`
----------
.. eql:type:: std::bytes
A sequence of bytes representing raw data.
Bytes can be represented as a literal using this syntax: ``b''``.
.. code-block:: edgeql-repl
db> select b'Hello, world';
{b'Hello, world'}
db> select b'Hello,\x20world\x01';
{b'Hello, world\x01'}
There are also some :ref:`generic <ref_std_generic>`
functions that can operate on bytes:
.. code-block:: edgeql-repl
db> select contains(b'qwerty', b'42');
{false}
Bytes are rendered as base64-encoded strings in JSON. When you cast a
``bytes`` value into JSON, that's what you'll get. In order to
:eql:op:`cast <cast>` a :eql:type:`json` value into bytes, it must be a
base64-encoded string.
.. code-block:: edgeql-repl
db> select <json>b'Hello Gel!';
{"\"SGVsbG8gRWRnZURCIQ==\""}
db> select <bytes>to_json("\"SGVsbG8gRWRnZURCIQ==\"");
{b'Hello Gel!'}
----------
.. eql:type:: std::Endian
.. versionadded:: 5.0
An enum for indicating integer value encoding.
This enum is used by the :eql:func:`to_int16`, :eql:func:`to_int32`,
:eql:func:`to_int64` and the :eql:func:`to_bytes` converters working with
:eql:type:`bytes` and integers.
``Endian.Big`` stands for big-endian encoding going from most significant
byte to least. ``Endian.Little`` stands for little-endian encoding going
from least to most significant byte.
.. code-block:: edgeql-repl
db> select to_bytes(<int32>16908295, Endian.Big);
{b'\x01\x02\x00\x07'}
db> select to_int32(b'\x01\x02\x00\x07', Endian.Big);
{16908295}
db> select to_bytes(<int32>16908295, Endian.Little);
{b'\x07\x00\x02\x01'}
db> select to_int32(b'\x07\x00\x02\x01', Endian.Little);
{16908295}
----------
.. eql:operator:: bytesidx: bytes [ int64 ] -> bytes
:index: [int]
Accesses a byte at a given index.
Examples:
.. code-block:: edgeql-repl
db> select b'binary \x01\x02\x03\x04 ftw!'[2];
{b'n'}
db> select b'binary \x01\x02\x03\x04 ftw!'[8];
{b'\x02'}
----------
.. eql:operator:: bytesslice: bytes [ int64 : int64 ] -> bytes
:index: [int:int]
Produces a bytes sub-sequence from an existing bytes value.
Examples:
.. code-block:: edgeql-repl
db> select b'\x01\x02\x03\x04 ftw!'[2:-1];
{b'\x03\x04 ftw'}
db> select b'some bytes'[2:-3];
{b'me by'}
---------
.. eql:operator:: bytesplus: bytes ++ bytes ->
:index: ++, bytes, concatenate, join, add
Concatenates two bytes values into one.
.. code-block:: edgeql-repl
db> select b'\x01\x02' ++ b'\x03\x04';
{b'\x01\x02\x03\x04'}
---------
.. TODO: Function signatures except the first need to be revealed only for v5+
.. eql:function:: std::to_bytes(s: str) -> bytes
std::to_bytes(val: int16, endian: Endian) -> bytes
std::to_bytes(val: int32, endian: Endian) -> bytes
std::to_bytes(val: int64, endian: Endian) -> bytes
std::to_bytes(val: uuid) -> bytes
:index: encode stringencoder
.. versionadded:: 4.0
Converts a given value into binary representation as :eql:type:`bytes`.
The strings get converted using UTF-8 encoding:
.. code-block:: edgeql-repl
db> select to_bytes('テキスト');
{b'\xe3\x83\x86\xe3\x82\xad\xe3\x82\xb9\xe3\x83\x88'}
The integer values can be encoded as big-endian (most significant bit
comes first) byte strings:
.. code-block:: edgeql-repl
db> select to_bytes(<int16>31, Endian.Big);
{b'\x00\x1f'}
db> select to_bytes(<int32>31, Endian.Big);
{b'\x00\x00\x00\x1f'}
db> select to_bytes(123456789123456789, Endian.Big);
{b'\x01\xb6\x9bK\xac\xd0_\x15'}
.. note::
Due to underlying implementation details using big-endian encoding
results in slightly faster performance of ``to_bytes`` when converting
integers.
The UUID values are converted to the underlying string of 16 bytes:
.. code-block:: edgeql-repl
db> select to_bytes(<uuid>'1d70c86e-cc92-11ee-b4c7-a7aa0a34e2ae');
{b'\x1dp\xc8n\xcc\x92\x11\xee\xb4\xc7\xa7\xaa\n4\xe2\xae'}
To perform the reverse conversion there are corresponding functions:
:eql:func:`to_str`, :eql:func:`to_int16`, :eql:func:`to_int32`,
:eql:func:`to_int64`, :eql:func:`to_uuid`.
---------
.. eql:function:: std::bytes_get_bit(bytes: bytes, nth: int64) -> int64
Returns the specified bit of the :eql:type:`bytes` value.
When looking for the *nth* bit, this function will enumerate bits from
least to most significant in each byte.
.. code-block:: edgeql-repl
db> for n in {0, 1, 2, 3, 4, 5, 6, 7,
... 8, 9, 10, 11, 12, 13 ,14, 15}
... union bytes_get_bit(b'ab', n);
{1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0}
---------
.. eql:function:: enc::base64_encode(b: bytes) -> str
.. versionadded:: 4.0
Returns a Base64-encoded :eql:type:`str` of the :eql:type:`bytes` value.
.. code-block:: edgeql-repl
db> select enc::base64_encode(b'hello');
{'aGVsbG8='}
---------
.. eql:function:: enc::base64_decode(s: str) -> bytes
.. versionadded:: 4.0
Returns the :eql:type:`bytes` of a Base64-encoded :eql:type:`str`.
Returns an InvalidValueError if input is not valid Base64.
.. code-block:: edgeql-repl
db> select enc::base64_decode('aGVsbG8=');
{b'hello'}
db> select enc::base64_decode('aGVsbG8');
gel error: InvalidValueError: invalid base64 end sequence
================================================================================
.. File: cfg.rst
.. _ref_std_cfg:
======
Config
======
The ``cfg`` module contains a set of types and scalars used for configuring
|Gel|.
.. list-table::
:class: funcoptable
* - **Type**
- **Description**
* - :eql:type:`cfg::AbstractConfig`
- The abstract base type for all configuration objects. The properties
of this type define the set of configuruation settings supported by
Gel.
* - :eql:type:`cfg::Config`
- The main configuration object. The properties of this object reflect
the overall configuration setting from instance level all the way to
session level.
* - :eql:type:`cfg::DatabaseConfig`
- The database configuration object. It reflects all the applicable
configuration at the Gel database level.
* - :eql:type:`cfg::BranchConfig`
- The database branch configuration object. It reflects all the applicable
configuration at the Gel branch level.
* - :eql:type:`cfg::InstanceConfig`
- The instance configuration object.
* - :eql:type:`cfg::ExtensionConfig`
- The abstract base type for all extension configuration objects. Each
extension can define the necessary configuration settings by extending
this type and adding the extension-specific properties.
* - :eql:type:`cfg::Auth`
- An object type representing an authentication profile.
* - :eql:type:`cfg::ConnectionTransport`
- An enum type representing the different protocols that Gel speaks.
* - :eql:type:`cfg::AuthMethod`
- An abstract object type representing a method of authentication
* - :eql:type:`cfg::Trust`
- A subclass of ``AuthMethod`` indicating an "always trust" policy (no
authentication).
* - :eql:type:`cfg::SCRAM`
- A subclass of ``AuthMethod`` indicating password-based authentication.
* - :eql:type:`cfg::Password`
- A subclass of ``AuthMethod`` indicating basic password-based
authentication.
* - :eql:type:`cfg::JWT`
- A subclass of ``AuthMethod`` indicating token-based authentication.
* - :eql:type:`cfg::memory`
- A scalar type for storing a quantity of memory storage.
Configuration Parameters
========================
:edb-alt-title: Available Configuration Parameters
.. _ref_admin_config_connection:
Connection settings
-------------------
.. index:: listen_addresses, listen_port
:eql:synopsis:`listen_addresses -> multi str`
Specifies the TCP/IP address(es) on which the server is to listen for
connections from client applications. If the list is empty, the server
does not listen on any IP interface at all.
:eql:synopsis:`listen_port -> int16`
The TCP port the server listens on; ``5656`` by default. Note that the
same port number is used for all IP addresses the server listens on.
Resource usage
--------------
.. index:: effective_io_concurrency, query_work_mem, shared_buffers
:eql:synopsis:`effective_io_concurrency -> int64`
Sets the number of concurrent disk I/O operations that can be
executed simultaneously. Corresponds to the PostgreSQL
configuration parameter of the same name.
:eql:synopsis:`query_work_mem -> cfg::memory`
The amount of memory used by internal query operations such as
sorting. Corresponds to the PostgreSQL ``work_mem`` configuration
parameter.
:eql:synopsis:`shared_buffers -> cfg::memory`
The amount of memory the database uses for shared memory buffers.
Corresponds to the PostgreSQL configuration parameter of the same
name. Changing this value requires server restart.
Query planning
--------------
.. index:: default_statistics_target, effective_cache_size
:eql:synopsis:`default_statistics_target -> int64`
Sets the default data statistics target for the planner.
Corresponds to the PostgreSQL configuration parameter of the same
name.
:eql:synopsis:`effective_cache_size -> cfg::memory`
Sets the planner's assumption about the effective size of the disk
cache that is available to a single query. Corresponds to the
PostgreSQL configuration parameter of the same name.
Query cache
-----------
.. versionadded:: 5.0
.. index:: auto_rebuild_query_cache, query_cache_mode, cfg::QueryCacheMode
:eql:synopsis:`auto_rebuild_query_cache -> bool`
Determines whether to recompile the existing query cache to SQL any time DDL
is executed.
:eql:synopsis:`query_cache_mode -> cfg::QueryCacheMode`
Allows the developer to set where the query cache is stored. Possible values:
* ``cfg::QueryCacheMode.InMemory``- All query cache is lost on server restart.
This mirrors pre-5.0 |EdgeDB| behavior.
* ``cfg::QueryCacheMode.RegInline``- The in-memory query cache is also stored in
the database as-is so it can be restored on restart.
* ``cfg::QueryCacheMode.Default``- Allow the server to select the best caching
option. Currently, it will select ``InMemory`` for arm64 Linux and
``RegInline`` for everything else.
.. TODO: toggle on once the PgFunc mode is available
* ``cfg::QueryCacheMode.PgFunc``- this is experimental and not quite ready as of
now. It wraps SQLs into stored functions in Postgres and reduces backend
request size and preparation time.
Query behavior
--------------
.. index:: allow_bare_ddl, cfg::AllowBareDDL, apply_access_policies,
apply_access_policies_pg, force_database_error
:eql:synopsis:`allow_bare_ddl -> cfg::AllowBareDDL`
Allows for running bare DDL outside a migration. Possible values are
``cfg::AllowBareDDL.AlwaysAllow`` and ``cfg::AllowBareDDL.NeverAllow``.
When you create an instance, this is set to ``cfg::AllowBareDDL.AlwaysAllow``
until you run a migration. At that point it is set to
``cfg::AllowBareDDL.NeverAllow`` because it's generally a bad idea to mix
migrations with bare DDL.
.. _ref_std_cfg_apply_access_policies:
:eql:synopsis:`apply_access_policies -> bool`
Determines whether access policies should be applied when running queries.
Setting this to ``false`` effectively puts you into super-user mode, ignoring
any access policies that might otherwise limit you on the instance.
.. note::
This setting can also be conveniently accessed via the "Config" dropdown
menu at the top of the Gel UI (accessible by running the CLI command
:gelcmd:`ui` from within a project). The setting will apply only to your
UI session, so you won't have to remember to re-enable it when you're
done.
:eql:synopsis:`apply_access_policies_pg -> bool`
Determines whether access policies should be applied when running queries over
SQL adapter. Defaults to ``false``.
:eql:synopsis:`force_database_error -> str`
A hook to force all queries to produce an error. Defaults to 'false'.
.. note::
This parameter takes a ``str`` instead of a ``bool`` to allow more
verbose messages when all queries are forced to fail. The database will
attempt to deserialize this ``str`` into a JSON object that must include
a ``type`` (which must be an Gel
:ref:`error type <ref_protocol_errors>` name), and may also include
``message``, ``hint``, and ``details`` which can be set ad-hoc by
the user.
For example, the following is valid input:
``'{ "type": "QueryError",
"message": "Did not work",
"hint": "Try doing something else",
"details": "Indeed, something went really wrong" }'``
As is this:
``'{ "type": "UnknownParameterError" }'``
.. _ref_std_cfg_client_connections:
Client connections
------------------
.. index:: allow_user_specified_id, session_idle_timeout,
session_idle_transaction_timeout, query_execution_timeout
:eql:synopsis:`allow_user_specified_id -> bool`
Makes it possible to set the ``.id`` property when inserting new objects.
.. warning::
Enabling this feature introduces some security vulnerabilities:
1. An unprivileged user can discover ids that already exist in the
database by trying to insert new values and noting when there is a
constraint violation on ``.id`` even if the user doesn't have access
to the relevant table.
2. It allows re-using object ids for a different object type, which the
application might not expect.
Additionally, enabling can have serious performance implications as, on
an ``insert``, every object type must be checked for collisions.
As a result, we don't recommend enabling this. If you need to preserve
UUIDs from an external source on your objects, it's best to create a new
property to store these UUIDs. If you will need to filter on this
external UUID property, you may add an :ref:`index
<ref_datamodel_indexes>` on it.
:eql:synopsis:`session_idle_timeout -> std::duration`
Sets the timeout for how long client connections can stay inactive
before being forcefully closed by the server.
Time spent on waiting for query results doesn't count as idling.
E.g. if the session idle timeout is set to 1 minute it would be OK
to run a query that takes 2 minutes to compute; to limit the query
execution time use the ``query_execution_timeout`` setting.
The default is 60 seconds. Setting it to ``<duration>'0'`` disables
the mechanism. Setting the timeout to less than ``2`` seconds is not
recommended.
Note that the actual time an idle connection can live can be up to
two times longer than the specified timeout.
This is a system-level config setting.
:eql:synopsis:`session_idle_transaction_timeout -> std::duration`
Sets the timeout for how long client connections can stay inactive
while in a transaction.
The default is 10 seconds. Setting it to ``<duration>'0'`` disables
the mechanism.
.. note::
For ``session_idle_transaction_timeout`` and ``query_execution_timeout``,
values under 1ms are rounded down to zero, which will disable the timeout.
In order to set a timeout, please set a duration of 1ms or greater.
``session_idle_timeout`` can take values below 1ms.
:eql:synopsis:`query_execution_timeout -> std::duration`
Sets a time limit on how long a query can be run.
Setting it to ``<duration>'0'`` disables the mechanism.
The timeout isn't enabled by default.
.. note::
For ``session_idle_transaction_timeout`` and ``query_execution_timeout``,
values under 1ms are rounded down to zero, which will disable the timeout.
In order to set a timeout, please set a duration of 1ms or greater.
``session_idle_timeout`` can take values below 1ms.
----------
.. eql:type:: cfg::AbstractConfig
An abstract type representing the configuration of an instance or database.
The properties of this object type represent the set of configuration
options supported by Gel (listed above).
----------
.. eql:type:: cfg::Config
The main configuration object type.
This type will have only one object instance. The ``cfg::Config`` object
represents the sum total of the current Gel configuration. It reflects
the result of applying instance, branch, and session level configuration.
Examining this object is the recommended way of determining the current
configuration.
Here's an example of checking and disabling :ref:`access policies
<ref_std_cfg_apply_access_policies>`:
.. code-block:: edgeql-repl
db> select cfg::Config.apply_access_policies;
{true}
db> configure session set apply_access_policies := false;
OK: CONFIGURE SESSION
db> select cfg::Config.apply_access_policies;
{false}
----------
.. eql:type:: cfg::BranchConfig
.. versionadded:: 5.0
The branch-level configuration object type.
This type will have only one object instance. The ``cfg::BranchConfig``
object represents the state of the branch and instance-level Gel
configuration.
For overall configuration state please refer to the :eql:type:`cfg::Config`
instead.
----------
.. eql:type:: cfg::InstanceConfig
The instance-level configuration object type.
This type will have only one object instance. The ``cfg::InstanceConfig``
object represents the state of only instance-level Gel configuration.
For overall configuraiton state please refer to the :eql:type:`cfg::Config`
instead.
----------
.. eql:type:: cfg::ExtensionConfig
.. versionadded:: 5.0
An abstract type representing extension configuration.
Every extension is expected to define its own extension-specific config
object type extending ``cfg::ExtensionConfig``. Any necessary extension
configuration setting should be represented as properties of this concrete
config type.
Up to three instances of the extension-specific config type will be created,
each of them with a ``required single link cfg`` to the
:eql:type:`cfg::Config`, :eql:type:`cfg::DatabaseConfig`, or
:eql:type:`cfg::InstanceConfig` object depending on the configuration level.
The :eql:type:`cfg::AbstractConfig` exposes a corresponding computed
multi-backlink called ``extensions``.
For example, :ref:`ext::pgvector <ref_ext_pgvector>` extension exposes
``probes`` as a configurable parameter via ``ext::pgvector::Config`` object:
.. code-block:: edgeql-repl
db> configure session
... set ext::pgvector::Config::probes := 5;
OK: CONFIGURE SESSION
db> select cfg::Config.extensions[is ext::pgvector::Config]{*};
{
ext::pgvector::Config {
id: 12b5c70f-0bb8-508a-845f-ca3d41103b6f,
probes: 5,
ef_search: 40,
},
}
----------
.. eql:type:: cfg::Auth
An object type designed to specify a client authentication profile.
.. code-block:: edgeql-repl
db> configure instance insert
... Auth {priority := 0, method := (insert Trust)};
OK: CONFIGURE INSTANCE
Below are the properties of the ``Auth`` class.
:eql:synopsis:`priority -> int64`
The priority of the authentication rule. The lower this number,
the higher the priority.
:eql:synopsis:`user -> multi str`
The name(s) of the database role(s) this rule applies to. If set to
``'*'``, then it applies to all roles.
:eql:synopsis:`method -> cfg::AuthMethod`
The name of the authentication method type. Expects an instance of
:eql:type:`cfg::AuthMethod`; Valid values are:
``Trust`` for no authentication and ``SCRAM`` for SCRAM-SHA-256
password authentication.
:eql:synopsis:`comment -> optional str`
An optional comment for the authentication rule.
---------
.. eql:type:: cfg::ConnectionTransport
An enum listing the various protocols that Gel can speak.
Possible values are:
.. list-table::
:class: funcoptable
* - **Value**
- **Description**
* - ``cfg::ConnectionTransport.TCP``
- Gel binary protocol
* - ``cfg::ConnectionTransport.TCP_PG``
- Postgres protocol for the
:ref:`SQL query mode <ref_sql_adapter>`
* - ``cfg::ConnectionTransport.HTTP``
- Gel binary protocol
:ref:`tunneled over HTTP <ref_http_tunnelling>`
* - ``cfg::ConnectionTransport.SIMPLE_HTTP``
- :ref:`EdgeQL over HTTP <ref_edgeql_http>`
and :ref:`GraphQL <ref_graphql_index>` endpoints
---------
.. eql:type:: cfg::AuthMethod
An abstract object class that represents an authentication method.
It currently has four concrete subclasses, each of which represent an
available authentication method: :eql:type:`cfg::SCRAM`,
:eql:type:`cfg::JWT`, :eql:type:`cfg::Password`, and
:eql:type:`cfg::Trust`.
:eql:synopsis:`transports -> multi cfg::ConnectionTransport`
Which connection transports this method applies to.
The subclasses have their own defaults for this.
-------
.. eql:type:: cfg::Trust
The ``cfg::Trust`` indicates an "always-trust" policy.
When active, it disables password-based authentication.
.. code-block:: edgeql-repl
db> configure instance insert
... Auth {priority := 0, method := (insert Trust)};
OK: CONFIGURE INSTANCE
-------
.. eql:type:: cfg::SCRAM
``cfg::SCRAM`` indicates password-based authentication.
It uses a challenge-response scheme to avoid transmitting the
password directly. This policy is implemented via ``SCRAM-SHA-256``
It is available for the ``TCP``, ``TCP_PG``, and ``HTTP`` transports
and is the default for ``TCP`` and ``TCP_PG``.
.. code-block:: edgeql-repl
db> configure instance insert
... Auth {priority := 0, method := (insert SCRAM)};
OK: CONFIGURE INSTANCE
-------
.. eql:type:: cfg::JWT
``cfg::JWT`` uses a JWT signed by the server to authenticate.
It is available for the ``TCP``, ``HTTP``, and ``HTTP_SIMPLE`` transports
and is the default for ``HTTP``.
-------
.. eql:type:: cfg::Password
``cfg::Password`` indicates simple password-based authentication.
Unlike :eql:type:`cfg::SCRAM`, this policy transmits the password
over the (encrypted) channel. It is implemened using HTTP Basic
Authentication over TLS.
This policy is available only for the ``SIMPLE_HTTP`` transport, where it is
the default.
-------
.. eql:type:: cfg::memory
A scalar type representing a quantity of memory storage.
As with ``uuid``, ``datetime``, and several other types, ``cfg::memory``
values are declared by casting from an appropriately formatted string.
.. code-block:: edgeql-repl
db> select <cfg::memory>'1B'; # 1 byte
{<cfg::memory>'1B'}
db> select <cfg::memory>'5KiB'; # 5 kibibytes
{<cfg::memory>'5KiB'}
db> select <cfg::memory>'128MiB'; # 128 mebibytes
{<cfg::memory>'128MiB'}
The numerical component of the value must be a non-negative integer; the
units must be one of ``B|KiB|MiB|GiB|TiB|PiB``. We're using the explicit
``KiB`` unit notation (1024 bytes) instead of ``kB`` (which is ambiguous,
and may mean 1000 or 1024 bytes).
================================================================================
.. File: constraint_table.rst
.. list-table::
* - :eql:constraint:`exclusive`
- Enforce uniqueness (disallow duplicate values)
* - :eql:constraint:`expression`
- Custom constraint expression (followed by keyword ``on``)
* - :eql:constraint:`one_of`
- A list of allowable values
* - :eql:constraint:`max_value`
- Maximum value numerically/lexicographically
* - :eql:constraint:`max_ex_value`
- Maximum value numerically/lexicographically (exclusive range)
* - :eql:constraint:`min_value`
- Minimum value numerically/lexicographically
* - :eql:constraint:`min_ex_value`
- Minimum value numerically/lexicographically (exclusive range)
* - :eql:constraint:`max_len_value`
- Maximum length (``str`` only)
* - :eql:constraint:`min_len_value`
- Minimum length (``str`` only)
* - :eql:constraint:`regexp`
- Regex constraint (``str`` only)
================================================================================
.. File: constraints.rst
.. _ref_std_constraints:
===========
Constraints
===========
.. include:: constraint_table.rst
.. eql:constraint:: std::expression on (expr)
A constraint based on an arbitrary expression returning a boolean.
The ``expression`` constraint may be used as in this example to create a
custom scalar type:
.. code-block:: sdl
scalar type StartsWithA extending str {
constraint expression on (__subject__[0] = 'A');
}
Example of using an ``expression`` constraint based on two
object properties to restrict maximum magnitude for a vector:
.. code-block:: sdl
type Vector {
required x: float64;
required y: float64;
constraint expression on (
__subject__.x^2 + __subject__.y^2 < 25
);
}
.. eql:constraint:: std::one_of(variadic members: anytype)
Specifies a list of allowed values.
Example:
.. code-block:: sdl
scalar type Status extending str {
constraint one_of ('Open', 'Closed', 'Merged');
}
.. eql:constraint:: std::max_value(max: anytype)
Specifies the maximum allowed value.
Example:
.. code-block:: sdl
scalar type Max100 extending int64 {
constraint max_value(100);
}
.. eql:constraint:: std::max_ex_value(max: anytype)
Specifies a non-inclusive upper bound for the value.
Example:
.. code-block:: sdl
scalar type Under100 extending int64 {
constraint max_ex_value(100);
}
In this example, in contrast to the ``max_value`` constraint, a value
of the ``Under100`` type cannot be ``100`` since the valid range of
``max_ex_value`` does not include the value specified in the constraint.
.. eql:constraint:: std::max_len_value(max: int64)
Specifies the maximum allowed length of a value.
Example:
.. code-block:: sdl
scalar type Username extending str {
constraint max_len_value(30);
}
.. eql:constraint:: std::min_value(min: anytype)
Specifies the minimum allowed value.
Example:
.. code-block:: sdl
scalar type NonNegative extending int64 {
constraint min_value(0);
}
.. eql:constraint:: std::min_ex_value(min: anytype)
Specifies a non-inclusive lower bound for the value.
Example:
.. code-block:: sdl
scalar type PositiveFloat extending float64 {
constraint min_ex_value(0);
}
In this example, in contrast to the ``min_value`` constraint, a value
of the ``PositiveFloat`` type cannot be ``0`` since the valid range of
``mix_ex_value`` does not include the value specified in the constraint.
.. eql:constraint:: std::min_len_value(min: int64)
Specifies the minimum allowed length of a value.
Example:
.. code-block:: sdl
scalar type EmailAddress extending str {
constraint min_len_value(3);
}
.. eql:constraint:: std::regexp(pattern: str)
:index: regex regexp regular
Limits to string values matching a regular expression.
Example:
.. code-block:: sdl
scalar type LettersOnly extending str {
constraint regexp(r'[A-Za-z]*');
}
See our documentation on :ref:`regular expression patterns
<string_regexp>` for more information on those.
.. eql:constraint:: std::exclusive
Specifies that the link or property value must be exclusive (unique).
When applied to a ``multi`` link or property, the exclusivity constraint
guarantees that for every object, the set of values held by a link or
property does not intersect with any other such set in any other object
of this type.
This constraint is only valid for concrete links and properties.
Scalar type definitions cannot include this constraint.
This constraint has an additional effect of creating an
implicit :ref:`index <ref_datamodel_indexes>` on a property.
This means that there's no need to add explicit indexes
for properties with this constraint.
Example:
.. code-block:: sdl
type User {
# Make sure user names are unique.
required name: str {
constraint exclusive;
}
# Already indexed, don't need to do this:
# index on (.name)
# Make sure none of the "owned" items belong
# to any other user.
multi owns: Item {
constraint exclusive;
}
}
Sometimes it may be necessary to create a type where each *combination*
of properties is unique. This can be achieved by defining an
``exclusive`` constraint for the combination, rather than on each
property:
.. code-block:: sdl
type UniqueCoordinates {
required x: int64;
required y: int64;
# Each combination of x and y must be unique.
constraint exclusive on ( (.x, .y) );
}
Any possible expression can appear in the ``on (<expr>)`` clause of
the ``exclusive`` constraint as long as it adheres to the following:
* The expression can only contain references to the immediate
properties or links of the type.
* No :ref:`backlinks <ref_datamodel_links>` or long paths are
allowed.
* Only ``Immutable`` functions are allowed in the constraint
expression.
.. list-table::
:class: seealso
* - **See also**
* - :ref:`Schema > Constraints <ref_datamodel_constraints>`
* - :ref:`SDL > Constraints <ref_eql_sdl_constraints>`
* - :ref:`DDL > Constraints <ref_eql_ddl_constraints>`
* - :ref:`Introspection > Constraints
<ref_datamodel_introspection_constraints>`
================================================================================
.. File: datetime.rst
.. _ref_std_datetime:
===============
Dates and Times
===============
:edb-alt-title: Types, Functions, and Operators for Dates and Times
.. list-table::
:class: funcoptable
* - :eql:type:`datetime`
- Timezone-aware point in time
* - :eql:type:`duration`
- Absolute time span
* - :eql:type:`cal::local_datetime`
- Date and time w/o timezone
* - :eql:type:`cal::local_date`
- Date type
* - :eql:type:`cal::local_time`
- Time type
* - :eql:type:`cal::relative_duration`
- Relative time span
* - :eql:type:`cal::date_duration`
- Relative time span in days
* - :eql:op:`dt + dt <dtplus>`
- :eql:op-desc:`dtplus`
* - :eql:op:`dt - dt <dtminus>`
- :eql:op-desc:`dtminus`
* - :eql:op:`= <eq>` :eql:op:`\!= <neq>` :eql:op:`?= <coaleq>`
:eql:op:`?!= <coalneq>` :eql:op:`\< <lt>` :eql:op:`\> <gt>`
:eql:op:`\<= <lteq>` :eql:op:`\>= <gteq>`
- Comparison operators
* - :eql:func:`to_str`
- Render a date/time value to a string.
* - :eql:func:`to_datetime`
- :eql:func-desc:`to_datetime`
* - :eql:func:`cal::to_local_datetime`
- :eql:func-desc:`cal::to_local_datetime`
* - :eql:func:`cal::to_local_date`
- :eql:func-desc:`cal::to_local_date`
* - :eql:func:`cal::to_local_time`
- :eql:func-desc:`cal::to_local_time`
* - :eql:func:`to_duration`
- :eql:func-desc:`to_duration`
* - :eql:func:`cal::to_relative_duration`
- :eql:func-desc:`cal::to_relative_duration`
* - :eql:func:`cal::to_date_duration`
- :eql:func-desc:`cal::to_date_duration`
* - :eql:func:`datetime_get`
- :eql:func-desc:`datetime_get`
* - :eql:func:`cal::time_get`
- :eql:func-desc:`cal::time_get`
* - :eql:func:`cal::date_get`
- :eql:func-desc:`cal::date_get`
* - :eql:func:`duration_get`
- :eql:func-desc:`duration_get`
* - :eql:func:`datetime_truncate`
- :eql:func-desc:`datetime_truncate`
* - :eql:func:`duration_truncate`
- :eql:func-desc:`duration_truncate`
* - :eql:func:`datetime_current`
- :eql:func-desc:`datetime_current`
* - :eql:func:`datetime_of_transaction`
- :eql:func-desc:`datetime_of_transaction`
* - :eql:func:`datetime_of_statement`
- :eql:func-desc:`datetime_of_statement`
* - :eql:func:`cal::duration_normalize_hours`
- :eql:func-desc:`cal::duration_normalize_hours`
* - :eql:func:`cal::duration_normalize_days`
- :eql:func-desc:`cal::duration_normalize_days`
.. _ref_std_datetime_intro:
|Gel| offers two ways of representing date/time values:
* a timezone-aware :eql:type:`std::datetime` type;
* a set of "local" date/time types, not attached to any particular
timezone: :eql:type:`cal::local_datetime`, :eql:type:`cal::local_date`,
and :eql:type:`cal::local_time`.
There are also two different ways of measuring duration:
* :eql:type:`duration` for using absolute and unambiguous units;
* :eql:type:`cal::relative_duration` for using fuzzy units like years,
months and days in addition to the absolute units.
All related operators, functions, and type casts are designed to maintain a
strict separation between timezone-aware and "local" date/time values.
|Gel| stores and outputs timezone-aware values in UTC format.
.. note::
All date/time types are restricted to years between 1 and 9999, including
the years 1 and 9999.
Although many systems support ISO 8601 date/time formatting in theory,
in practice the formatting before year 1 and after 9999 tends to
be inconsistent. As such, dates outside this range are not reliably
portable.
.. _ref_std_datetime_timezones:
Timezones
---------
For timezone string literals, you may specify timezones in one of two ways:
* IANA (Olson) timezone database name (e.g. ``America/New_York``)
* A time zone abbreviation (e.g. ``EDT`` for Eastern Daylight Time)
See the `relevant section from the PostgreSQL documentation
<https://www.postgresql.org/docs/current/datetime-timezones.html#TIMEZONE-TABLES>`_
for more detail about how time zones affect the behavior of date/time
functionality.
.. note::
The IANA timezone database is maintained by Paul Eggert for the IANA. You can
find a `GitHub repository with the latest timezone data here
<https://github.com/eggert/tz>`_, and the `list of timezone names here
<https://github.com/eggert/tz/blob/master/zone1970.tab>`_.
----------
.. eql:type:: std::datetime
Represents a timezone-aware moment in time.
All dates must correspond to dates that exist in the proleptic Gregorian
calendar.
:eql:op:`Casting <cast>` is a simple way to obtain a
:eql:type:`datetime` value in an expression:
.. code-block:: edgeql
select <datetime>'2018-05-07T15:01:22.306916+00';
select <datetime>'2018-05-07T15:01:22+00';
When casting ``datetime`` from strings, the string must follow
the ISO 8601 format with a timezone included.
.. code-block:: edgeql-repl
db> select <datetime>'January 01 2019 UTC';
InvalidValueError: invalid input syntax for type
std::datetime: 'January 01 2019 UTC'
Hint: Please use ISO8601 format. Alternatively "to_datetime"
function provides custom formatting options.
db> select <datetime>'2019-01-01T15:01:22';
InvalidValueError: invalid input syntax for type
std::datetime: '2019-01-01T15:01:22'
Hint: Please use ISO8601 format. Alternatively "to_datetime"
function provides custom formatting options.
All ``datetime`` values are restricted to the range from year 1 to 9999.
For more information regarding interacting with this type, see
:eql:func:`datetime_get`, :eql:func:`to_datetime`, and :eql:func:`to_str`.
----------
.. eql:type:: cal::local_datetime
A type for representing a date and time without a timezone.
:eql:op:`Casting <cast>` is a simple way to obtain a
:eql:type:`cal::local_datetime` value in an expression:
.. code-block:: edgeql
select <cal::local_datetime>'2018-05-07T15:01:22.306916';
select <cal::local_datetime>'2018-05-07T15:01:22';
When casting ``cal::local_datetime`` from strings, the string must follow
the ISO 8601 format without timezone:
.. code-block:: edgeql-repl
db> select <cal::local_datetime>'2019-01-01T15:01:22+00';
InvalidValueError: invalid input syntax for type
cal::local_datetime: '2019-01-01T15:01:22+00'
Hint: Please use ISO8601 format. Alternatively
"cal::to_local_datetime" function provides custom formatting
options.
db> select <cal::local_datetime>'January 01 2019';
InvalidValueError: invalid input syntax for type
cal::local_datetime: 'January 01 2019'
Hint: Please use ISO8601 format. Alternatively
"cal::to_local_datetime" function provides custom formatting
options.
All ``datetime`` values are restricted to the range from year 1 to 9999.
For more information regarding interacting with this type, see
:eql:func:`datetime_get`, :eql:func:`cal::to_local_datetime`, and
:eql:func:`to_str`.
----------
.. eql:type:: cal::local_date
A type for representing a date without a timezone.
:eql:op:`Casting <cast>` is a simple way to obtain a
:eql:type:`cal::local_date` value in an expression:
.. code-block:: edgeql
select <cal::local_date>'2018-05-07';
When casting ``cal::local_date`` from strings, the string must follow the
ISO 8601 date format.
For more information regarding interacting with this type, see
:eql:func:`cal::date_get`, :eql:func:`cal::to_local_date`, and
:eql:func:`to_str`.
----------
.. eql:type:: cal::local_time
A type for representing a time without a timezone.
:eql:op:`Casting <cast>` is a simple way to obtain a
:eql:type:`cal::local_time` value in an expression:
.. code-block:: edgeql
select <cal::local_time>'15:01:22.306916';
select <cal::local_time>'15:01:22';
When casting ``cal::local_time`` from strings, the string must follow the
ISO 8601 time format.
For more information regarding interacting with this type, see
:eql:func:`cal::time_get`, :eql:func:`cal::to_local_time`, and
:eql:func:`to_str`.
----------
.. _ref_datetime_duration:
.. eql:type:: std::duration
A type for representing a span of time.
A :eql:type:`duration` is a fixed number of seconds and microseconds and
isn't adjusted by timezone, length of month, or anything else in datetime
calculations.
When converting from a string, only units of ``'microseconds'``,
``'milliseconds'``, ``'seconds'``, ``'minutes'``, and ``'hours'`` are
valid:
.. code-block:: edgeql-repl
db> select <duration>'45.6 seconds';
{<duration>'0:00:45.6'}
db> select <duration>'15 milliseconds';
{<duration>'0:00:00.015'}
db> select <duration>'48 hours 45 minutes';
{<duration>'48:45:00'}
db> select <duration>'11 months';
gel error: InvalidValueError: invalid input syntax for type
std::duration: '11 months'
Hint: Units bigger than hours cannot be used for std::duration.
All date/time types support the ``+`` and ``-`` arithmetic operations
with durations:
.. code-block:: edgeql-repl
db> select <datetime>'2019-01-01T00:00:00Z' - <duration>'24 hours';
{<datetime>'2018-12-31T00:00:00+00:00'}
db> select <cal::local_time>'22:00' + <duration>'1 hour';
{<cal::local_time>'23:00:00'}
For more information regarding interacting with this type, see
:eql:func:`to_duration`, and :eql:func:`to_str` and date/time
:eql:op:`operators <dtplus>`.
----------
.. eql:type:: cal::relative_duration
A type for representing a relative span of time.
Unlike :eql:type:`std::duration`, ``cal::relative_duration`` is an
imprecise form of measurement. When months and days are used, the same
relative duration could have a different absolute duration depending on
the date you're measuring from.
For example 2020 was a leap year and had 366 days. Notice how the number
of hours in each year below is different:
.. code-block:: edgeql-repl
db> with
... first_day_of_2020 := <datetime>'2020-01-01T00:00:00Z',
... one_year := <cal::relative_duration>'1 year',
... first_day_of_next_year := first_day_of_2020 + one_year
... select first_day_of_next_year - first_day_of_2020;
{<duration>'8784:00:00'}
db> with
... first_day_of_2019 := <datetime>'2019-01-01T00:00:00Z',
... one_year := <cal::relative_duration>'1 year',
... first_day_of_next_year := first_day_of_2019 + one_year
... select first_day_of_next_year - first_day_of_2019;
{<duration>'8760:00:00'}
When converting from a string, only the following units are valid:
- ``'microseconds'``
- ``'milliseconds'``
- ``'seconds'``
- ``'minutes'``
- ``'hours'``
- ``'days'``
- ``'weeks'``
- ``'months'``
- ``'years'``
- ``'decades'``
- ``'centuries'``
- ``'millennia'``
Examples of units usage:
.. code-block:: edgeql
select <cal::relative_duration>'45.6 seconds';
select <cal::relative_duration>'15 milliseconds';
select <cal::relative_duration>'3 weeks 45 minutes';
select <cal::relative_duration>'-7 millennia';
All date/time types support the ``+`` and ``-`` arithmetic operations
with ``relative_duration``:
.. code-block:: edgeql-repl
db> select <datetime>'2019-01-01T00:00:00Z' -
... <cal::relative_duration>'3 years';
{<datetime>'2016-01-01T00:00:00+00:00'}
db> select <cal::local_time>'22:00' +
... <cal::relative_duration>'1 hour';
{<cal::local_time>'23:00:00'}
If an arithmetic operation results in a day that doesn't exist in the
given month, the last day of the month will be used instead:
.. code-block:: edgeql-repl
db> select <cal::local_datetime>"2021-01-31T15:00:00" +
... <cal::relative_duration>"1 month";
{<cal::local_datetime>'2021-02-28T15:00:00'}
For arithmetic operations involving a ``cal::relative_duration``
consisting of multiple components (units), higher-order components are
applied first followed by lower-order components.
.. code-block:: edgeql-repl
db> select <cal::local_datetime>"2021-04-30T15:00:00" +
... <cal::relative_duration>"1 month 1 day";
{<cal::local_datetime>'2021-05-31T15:00:00'}
If you add the same components split into separate durations, adding the
higher-order units first followed by the lower-order units, the
calculation produces the same result as in the previous example:
.. code-block:: edgeql-repl
db> select <cal::local_datetime>"2021-04-30T15:00:00" +
... <cal::relative_duration>"1 month" +
... <cal::relative_duration>"1 day";
{<cal::local_datetime>'2021-05-31T15:00:00'}
When the order of operations is reversed, the result may be different for
some corner cases:
.. code-block:: edgeql-repl
db> select <cal::local_datetime>"2021-04-30T15:00:00" +
... <cal::relative_duration>"1 day" +
... <cal::relative_duration>"1 month";
{<cal::local_datetime>'2021-06-01T15:00:00'}
.. rubric:: Gotchas
Due to the implementation of ``relative_duration`` logic, arithmetic
operations may behave counterintuitively.
**Non-associative**
.. code-block:: edgeql-repl
db> select <cal::local_datetime>'2021-01-31T00:00:00' +
... <cal::relative_duration>'1 month' +
... <cal::relative_duration>'1 month';
{<cal::local_datetime>'2021-03-28T00:00:00'}
db> select <cal::local_datetime>'2021-01-31T00:00:00' +
... (<cal::relative_duration>'1 month' +
... <cal::relative_duration>'1 month');
{<cal::local_datetime>'2021-03-31T00:00:00'}
**Lossy**
.. code-block:: edgeql-repl
db> with m := <cal::relative_duration>'1 month'
... select <cal::local_date>'2021-01-31' + m
... =
... <cal::local_date>'2021-01-30' + m;
{true}
**Asymmetric**
.. code-block:: edgeql-repl
db> with m := <cal::relative_duration>'1 month'
... select <cal::local_date>'2021-01-31' + m - m;
{<cal::local_date>'2021-01-28'}
**Non-monotonic**
.. code-block:: edgeql-repl
db> with m := <cal::relative_duration>'1 month'
... select <cal::local_datetime>'2021-01-31T01:00:00' + m
... <
... <cal::local_datetime>'2021-01-30T23:00:00' + m;
{true}
db> with m := <cal::relative_duration>'2 month'
... select <cal::local_datetime>'2021-01-31T01:00:00' + m
... <
... <cal::local_datetime>'2021-01-30T23:00:00' + m;
{false}
For more information regarding interacting with this type, see
:eql:func:`cal::to_relative_duration`, and :eql:func:`to_str` and
date/time :eql:op:`operators <dtplus>`.
----------
.. eql:type:: cal::date_duration
A type for representing a span of time in days.
This type is similar to :eql:type:`cal::relative_duration`, except it only
uses 2 units: months and days. It is the result of subtracting one
:eql:type:`cal::local_date` from another. The purpose of this type is to
allow performing ``+`` and ``-`` operations on a
:eql:type:`cal::local_date` and to produce a :eql:type:`cal::local_date`
as the result:
.. code-block:: edgeql-repl
db> select <cal::local_date>'2022-06-30' -
... <cal::local_date>'2022-06-25';
{<cal::date_duration>'P5D'}
db> select <cal::local_date>'2022-06-25' +
... <cal::date_duration>'5 days';
{<cal::local_date>'2022-06-30'}
db> select <cal::local_date>'2022-06-25' -
... <cal::date_duration>'5 days';
{<cal::local_date>'2022-06-20'}
When converting from a string, only the following units are valid:
- ``'days'``,
- ``'weeks'``,
- ``'months'``,
- ``'years'``,
- ``'decades'``,
- ``'centuries'``,
- ``'millennia'``.
.. code-block:: edgeql
select <cal::date_duration>'45 days';
select <cal::date_duration>'3 weeks 5 days';
select <cal::date_duration>'-7 millennia';
In most cases, ``date_duration`` is fully compatible with
:eql:type:`cal::relative_duration` and shares the same general behavior
and caveats. Gel will apply type coercion in the event it expects a
:eql:type:`cal::relative_duration` and finds a ``cal::date_duration``
instead.
For more information regarding interacting with this type, see
:eql:func:`cal::to_date_duration` and date/time :eql:op:`operators
<dtplus>`.
----------
.. eql:operator:: dtplus: datetime + duration -> datetime
datetime + cal::relative_duration \
-> cal::relative_duration
duration + duration -> duration
duration + cal::relative_duration \
-> cal::relative_duration
cal::relative_duration + cal::relative_duration \
-> cal::relative_duration
cal::local_datetime + cal::relative_duration \
-> cal::relative_duration
cal::local_datetime + duration \
-> cal::local_datetime
cal::local_time + cal::relative_duration \
-> cal::relative_duration
cal::local_time + duration -> cal::local_time
cal::local_date + cal::date_duration \
-> cal::local_date
cal::date_duration + cal::date_duration \
-> cal::date_duration
cal::local_date + cal::relative_duration \
-> cal::local_datetime
cal::local_date + duration -> cal::local_datetime
:index: +, duration, datetime, add
Adds a duration and any other datetime value.
This operator is commutative.
.. code-block:: edgeql-repl
db> select <cal::local_time>'22:00' + <duration>'1 hour';
{<cal::local_time>'23:00:00'}
db> select <duration>'1 hour' + <cal::local_time>'22:00';
{<cal::local_time>'23:00:00'}
db> select <duration>'1 hour' + <duration>'2 hours';
{10800s}
----------
.. eql:operator:: dtminus: duration - duration -> duration
datetime - datetime -> duration
datetime - duration -> datetime
datetime - cal::relative_duration -> datetime
cal::relative_duration - cal::relative_duration \
-> cal::relative_duration
cal::local_datetime - cal::local_datetime \
-> cal::relative_duration
cal::local_datetime - cal::relative_duration \
-> cal::local_datetime
cal::local_datetime - duration \
-> cal::local_datetime
cal::local_time - cal::local_time \
-> cal::relative_duration
cal::local_time - cal::relative_duration \
-> cal::local_time
cal::local_time - duration -> cal::local_time
cal::date_duration - cal::date_duration \
-> cal::date_duration
cal::local_date - cal::local_date \
-> cal::date_duration
cal::local_date - cal::date_duration \
-> cal::local_date
cal::local_date - cal::relative_duration \
-> cal::local_datetime
cal::local_date - duration -> cal::local_datetime
duration - cal::relative_duration \
-> cal::relative_duration
cal::relative_duration - duration\
-> cal::relative_duration
:index: -, duration, datetime, subtract
Subtracts two compatible datetime or duration values.
.. code-block:: edgeql-repl
db> select <datetime>'2019-01-01T01:02:03+00' -
... <duration>'24 hours';
{<datetime>'2018-12-31T01:02:03Z'}
db> select <datetime>'2019-01-01T01:02:03+00' -
... <datetime>'2019-02-01T01:02:03+00';
{-2678400s}
db> select <duration>'1 hour' -
... <duration>'2 hours';
{-3600s}
When subtracting a :eql:type:`cal::local_date` type from another, the
result is given as a whole number of days using the
:eql:type:`cal::date_duration` type:
.. code-block:: edgeql-repl
db> select <cal::local_date>'2022-06-25' -
... <cal::local_date>'2019-02-01';
{<cal::date_duration>'P1240D'}
.. note::
Subtraction doesn't make sense for some type combinations. You
couldn't subtract a point in time from a duration, so neither can
Gel (although the inverse — subtracting a duration from a point in
time — is perfectly fine). You also couldn't subtract a timezone-aware
datetime from a local one or vice versa. If you attempt any of these,
Gel will raise an exception as shown in these examples.
When subtracting a date/time object from a time interval, an exception
will be raised:
.. code-block:: edgeql-repl
db> select <duration>'1 day' -
... <datetime>'2019-01-01T01:02:03+00';
QueryError: operator '-' cannot be applied to operands ...
An exception will also be raised when trying to subtract a timezone-aware
:eql:type:`std::datetime` type from :eql:type:`cal::local_datetime` or
vice versa:
.. code-block:: edgeql-repl
db> select <datetime>'2019-01-01T01:02:03+00' -
... <cal::local_datetime>'2019-02-01T01:02:03';
QueryError: operator '-' cannot be applied to operands...
db> select <cal::local_datetime>'2019-02-01T01:02:03' -
... <datetime>'2019-01-01T01:02:03+00';
QueryError: operator '-' cannot be applied to operands...
----------
.. eql:function:: std::datetime_current() -> datetime
:index: now
Returns the server's current date and time.
.. code-block:: edgeql-repl
db> select datetime_current();
{<datetime>'2018-05-14T20:07:11.755827Z'}
This function is volatile since it always returns the current time when it
is called. As a result, it cannot be used in :ref:`computed properties
defined in schema <ref_datamodel_computed>`. This does *not* apply to
computed properties outside of schema.
----------
.. eql:function:: std::datetime_of_transaction() -> datetime
:index: now
Returns the date and time of the start of the current transaction.
This function is non-volatile since it returns the current time when the
transaction is started, not when the function is called. As a result, it
can be used in :ref:`computed properties <ref_datamodel_computed>` defined
in schema.
----------
.. eql:function:: std::datetime_of_statement() -> datetime
:index: now
Returns the date and time of the start of the current statement.
This function is non-volatile since it returns the current time when the
statement is started, not when the function is called. As a result, it
can be used in :ref:`computed properties <ref_datamodel_computed>` defined
in schema.
----------
.. eql:function:: std::datetime_get(dt: datetime, el: str) -> float64
std::datetime_get(dt: cal::local_datetime, \
el: str) -> float64
Returns the element of a date/time given a unit name.
You may pass any of these unit names for *el*:
- ``'epochseconds'`` - the number of seconds since 1970-01-01 00:00:00
UTC (Unix epoch) for :eql:type:`datetime` or local time for
:eql:type:`cal::local_datetime`. It can be negative.
- ``'century'`` - the century according to the Gregorian calendar
- ``'day'`` - the day of the month (1-31)
- ``'decade'`` - the decade (year divided by 10 and rounded down)
- ``'dow'`` - the day of the week from Sunday (0) to Saturday (6)
- ``'doy'`` - the day of the year (1-366)
- ``'hour'`` - the hour (0-23)
- ``'isodow'`` - the ISO day of the week from Monday (1) to Sunday (7)
- ``'isoyear'`` - the ISO 8601 week-numbering year that the date falls in.
See the ``'week'`` element for more details.
- ``'microseconds'`` - the seconds including fractional value expressed
as microseconds
- ``'millennium'`` - the millennium. The third millennium started
on Jan 1, 2001.
- ``'milliseconds'`` - the seconds including fractional value expressed
as milliseconds
- ``'minutes'`` - the minutes (0-59)
- ``'month'`` - the month of the year (1-12)
- ``'quarter'`` - the quarter of the year (1-4)
- ``'seconds'`` - the seconds, including fractional value from 0 up to and
not including 60
- ``'week'`` - the number of the ISO 8601 week-numbering week of
the year. ISO weeks are defined to start on Mondays and the
first week of a year must contain Jan 4 of that year.
- ``'year'`` - the year
.. code-block:: edgeql-repl
db> select datetime_get(
... <datetime>'2018-05-07T15:01:22.306916+00',
... 'epochseconds');
{1525705282.306916}
db> select datetime_get(
... <datetime>'2018-05-07T15:01:22.306916+00',
... 'year');
{2018}
db> select datetime_get(
... <datetime>'2018-05-07T15:01:22.306916+00',
... 'quarter');
{2}
db> select datetime_get(
... <datetime>'2018-05-07T15:01:22.306916+00',
... 'doy');
{127}
db> select datetime_get(
... <datetime>'2018-05-07T15:01:22.306916+00',
... 'hour');
{15}
----------
.. eql:function:: cal::time_get(dt: cal::local_time, el: str) -> float64
Returns the element of a time value given a unit name.
You may pass any of these unit names for *el*:
- ``'midnightseconds'``
- ``'hour'``
- ``'microseconds'``
- ``'milliseconds'``
- ``'minutes'``
- ``'seconds'``
For full description of what these elements extract see
:eql:func:`datetime_get`.
.. code-block:: edgeql-repl
db> select cal::time_get(
... <cal::local_time>'15:01:22.306916', 'minutes');
{1}
db> select cal::time_get(
... <cal::local_time>'15:01:22.306916', 'milliseconds');
{22306.916}
----------
.. eql:function:: cal::date_get(dt: local_date, el: str) -> float64
Returns the element of a date given a unit name.
The :eql:type:`cal::local_date` scalar has the following elements
available for extraction:
- ``'century'`` - the century according to the Gregorian calendar
- ``'day'`` - the day of the month (1-31)
- ``'decade'`` - the decade (year divided by 10 and rounded down)
- ``'dow'`` - the day of the week from Sunday (0) to Saturday (6)
- ``'doy'`` - the day of the year (1-366)
- ``'isodow'`` - the ISO day of the week from Monday (1) to Sunday (7)
- ``'isoyear'`` - the ISO 8601 week-numbering year that the date falls in.
See the ``'week'`` element for more details.
- ``'millennium'`` - the millennium. The third millennium started
on Jan 1, 2001.
- ``'month'`` - the month of the year (1-12)
- ``'quarter'`` - the quarter of the year (1-4)
not including 60
- ``'week'`` - the number of the ISO 8601 week-numbering week of
the year. ISO weeks are defined to start on Mondays and the
first week of a year must contain Jan 4 of that year.
- ``'year'`` - the year
.. code-block:: edgeql-repl
db> select cal::date_get(
... <cal::local_date>'2018-05-07', 'century');
{21}
db> select cal::date_get(
... <cal::local_date>'2018-05-07', 'year');
{2018}
db> select cal::date_get(
... <cal::local_date>'2018-05-07', 'month');
{5}
db> select cal::date_get(
... <cal::local_date>'2018-05-07', 'doy');
{127}
----------
.. eql:function:: std::duration_get(dt: duration, el: str) -> float64
std::duration_get(dt: cal::relative_duration, \
el: str) -> float64
std::duration_get(dt: cal::date_duration, \
el: str) -> float64
Returns the element of a duration given a unit name.
You may pass any of these unit names as ``el``:
- ``'millennium'`` - number of 1000-year chunks rounded down
- ``'century'`` - number of centuries rounded down
- ``'decade'`` - number of decades rounded down
- ``'year'`` - number of years rounded down
- ``'quarter'``- remaining quarters after whole years are accounted for
- ``'month'`` - number of months left over after whole years are
accounted for
- ``'day'`` - number of days recorded in the duration
- ``'hour'`` - number of hours
- ``'minutes'`` - remaining minutes after whole hours are accounted for
- ``'seconds'`` - remaining seconds, including fractional value after whole
minutes are accounted for
- ``'milliseconds'`` - remaining seconds including fractional value
expressed as milliseconds
- ``'microseconds'`` - remaining seconds including fractional value
expressed as microseconds
.. note ::
Only for units ``'month'`` or larger or for units ``'hour'`` or smaller
will you receive a total across multiple units expressed in the original
duration. See *Gotchas* below for details.
Additionally, it's possible to convert a given duration into seconds:
- ``'totalseconds'`` - the number of seconds represented by the duration.
It will be approximate for :eql:type:`cal::relative_duration` and
:eql:type:`cal::date_duration` for units ``'month'`` or larger because a
month is assumed to be 30 days exactly.
The :eql:type:`duration` scalar has only ``'hour'`` and smaller units
available for extraction.
The :eql:type:`cal::relative_duration` scalar has all of the units
available for extraction.
The :eql:type:`cal::date_duration` scalar only has ``'date'`` and larger
units available for extraction.
.. code-block:: edgeql-repl
db> select duration_get(
... <cal::relative_duration>'400 months', 'year');
{33}
db> select duration_get(
... <cal::date_duration>'400 months', 'month');
{4}
db> select duration_get(
... <cal::relative_duration>'1 month 20 days 30 hours',
... 'day');
{20}
db> select duration_get(
... <cal::relative_duration>'30 hours', 'hour');
{30}
db> select duration_get(
... <cal::relative_duration>'1 month 20 days 30 hours',
... 'hour');
{30}
db> select duration_get(<duration>'30 hours', 'hour');
{30}
db> select duration_get(
... <cal::relative_duration>'1 month 20 days 30 hours',
... 'totalseconds');
{4428000}
db> select duration_get(
... <duration>'30 hours', 'totalseconds');
{108000}
.. rubric:: Gotchas
This function will provide you with a calculated total for the unit passed
as ``el``, but only within the given "size class" of the unit. These size
classes exist because they are logical breakpoints that we can't reliably
convert values across. A month might be 30 days long, or it might be 28 or
29 or 31. A day is generally 24 hours, but with daylight savings, it might
be longer or shorter.
As a result, it's impossible to convert across these lines in a way that
works in every situation. For some use cases, assuming a 30 day month works
fine. For others, it might not. The size classes are as follows:
- ``'month'`` and larger
- ``'day'``
- ``'hour'`` and smaller
For example, if you specify ``'day'`` as your ``el`` argument, the function
will return only the number of days expressed as ``N days`` in your
duration. It will not add another day to the returned count for every 24
hours (defined as ``24 hours``) in the duration, nor will it consider the
months' constituent day counts in the returned value. Specifying
``'decade'`` for ``el`` will total up all decades represented in units
``'month'`` and larger, but it will not add a decade's worth of days to the
returned value as an additional decade.
In this example, the duration represents more than a day's time, but since
``'day'`` and ``'hour'`` are in different size classes, the extra day
stemming from the duration's hours is not added.
.. code-block:: edgeql-repl
db> select duration_get(
... <cal::relative_duration>'1 day 36 hours', 'day');
{1}
In this counter example, both the decades and months are pooled together
since they are in the same size class. The return value is 5: the 2
``'decades'`` and the 3 decades in ``'400 months'``.
.. code-block:: edgeql-repl
db> select duration_get(
... <cal::relative_duration>'2 decades 400 months', 'decade');
{5}
If a unit from a smaller size class would contribute to your desired unit's
total, it is not added.
.. code-block:: edgeql-repl
db> select duration_get(
... <cal::relative_duration>'1 year 400 days', 'year');
{1}
When you request a unit in the smallest size class, it will be pooled with
other durations in the same size class.
.. code-block:: edgeql-repl
db> select duration_get(
... <cal::relative_duration>'20 hours 3600 seconds', 'hour');
{21}
Seconds and smaller units always return remaining time in that unit after
accounting for the next larger unit.
.. code-block:: edgeql-repl
db> select duration_get(
... <cal::relative_duration>'20 hours 3600 seconds', 'seconds');
{0}
db> select duration_get(
... <cal::relative_duration>'20 hours 3630 seconds', 'seconds');
{30}
Normalization and truncation may help you deal with this. If your use case
allows for making assumptions about the duration of a month or a day, you
can make those conversions for yourself using the
:eql:func:`cal::duration_normalize_hours` or
:eql:func:`cal::duration_normalize_days` functions. If you got back a
duration as a result of a datetime calculation and don't need the level of
granularity you have, you can truncate the value with
:eql:func:`duration_truncate`.
----------
.. eql:function:: std::datetime_truncate(dt: datetime, unit: str) -> datetime
Truncates the input datetime to a particular precision.
The valid units in order or decreasing precision are:
- ``'microseconds'``
- ``'milliseconds'``
- ``'seconds'``
- ``'minutes'``
- ``'hours'``
- ``'days'``
- ``'weeks'``
- ``'months'``
- ``'quarters'``
- ``'years'``
- ``'decades'``
- ``'centuries'``
.. code-block:: edgeql-repl
db> select datetime_truncate(
... <datetime>'2018-05-07T15:01:22.306916+00', 'years');
{<datetime>'2018-01-01T00:00:00Z'}
db> select datetime_truncate(
... <datetime>'2018-05-07T15:01:22.306916+00', 'quarters');
{<datetime>'2018-04-01T00:00:00Z'}
db> select datetime_truncate(
... <datetime>'2018-05-07T15:01:22.306916+00', 'days');
{<datetime>'2018-05-07T00:00:00Z'}
db> select datetime_truncate(
... <datetime>'2018-05-07T15:01:22.306916+00', 'hours');
{<datetime>'2018-05-07T15:00:00Z'}
----------
.. eql:function:: std::duration_truncate(dt: duration, unit: str) -> duration
std::duration_truncate(dt: cal::relative_duration, \
unit: str) -> cal::relative_duration
Truncates the input duration to a particular precision.
The valid units for :eql:type:`duration` are:
- ``'microseconds'``
- ``'milliseconds'``
- ``'seconds'``
- ``'minutes'``
- ``'hours'``
In addition to the above the following are also valid for
:eql:type:`cal::relative_duration`:
- ``'days'``
- ``'weeks'``
- ``'months'``
- ``'years'``
- ``'decades'``
- ``'centuries'``
.. code-block:: edgeql-repl
db> select duration_truncate(
... <duration>'15:01:22', 'hours');
{<duration>'15:00:00'}
db> select duration_truncate(
... <duration>'15:01:22.306916', 'minutes');
{<duration>'15:01:00'}
db> select duration_truncate(
... <cal::relative_duration>'400 months', 'years');
{<cal::relative_duration>'P33Y'}
db> select duration_truncate(
... <cal::relative_duration>'400 months', 'decades');
{<cal::relative_duration>'P30Y'}
----------
.. eql:function:: std::to_datetime(s: str, fmt: optional str={}) -> datetime
std::to_datetime(local: cal::local_datetime, zone: str) \
-> datetime
std::to_datetime(year: int64, month: int64, day: int64, \
hour: int64, min: int64, sec: float64, zone: str) \
-> datetime
std::to_datetime(epochseconds: decimal) -> datetime
std::to_datetime(epochseconds: float64) -> datetime
std::to_datetime(epochseconds: int64) -> datetime
:index: parse datetime
Create a :eql:type:`datetime` value.
The :eql:type:`datetime` value can be parsed from the input
:eql:type:`str` *s*. By default, the input is expected to conform
to ISO 8601 format. However, the optional argument *fmt* can
be used to override the :ref:`input format
<ref_std_converters_datetime_fmt>` to other forms.
.. code-block:: edgeql-repl
db> select to_datetime('2018-05-07T15:01:22.306916+00');
{<datetime>'2018-05-07T15:01:22.306916Z'}
db> select to_datetime('2018-05-07T15:01:22+00');
{<datetime>'2018-05-07T15:01:22Z'}
db> select to_datetime('May 7th, 2018 15:01:22 +00',
... 'Mon DDth, YYYY HH24:MI:SS TZH');
{<datetime>'2018-05-07T15:01:22Z'}
Alternatively, the :eql:type:`datetime` value can be constructed
from a :eql:type:`cal::local_datetime` value:
.. code-block:: edgeql-repl
db> select to_datetime(
... <cal::local_datetime>'2019-01-01T01:02:03', 'HKT');
{<datetime>'2018-12-31T17:02:03Z'}
Another way to construct a the :eql:type:`datetime` value is to specify it
in terms of its component parts: year, month, day, hour, min, sec, and
:ref:`zone <ref_std_datetime_timezones>`.
.. code-block:: edgeql-repl
db> select to_datetime(
... 2018, 5, 7, 15, 1, 22.306916, 'UTC');
{<datetime>'2018-05-07T15:01:22.306916000Z'}
Finally, it is also possible to convert a Unix timestamp to a
:eql:type:`datetime`
.. code-block:: edgeql-repl
db> select to_datetime(1590595184.584);
{<datetime>'2020-05-27T15:59:44.584000000Z'}
------------
.. eql:function:: cal::to_local_datetime(s: str, fmt: optional str={}) \
-> local_datetime
cal::to_local_datetime(dt: datetime, zone: str) \
-> local_datetime
cal::to_local_datetime(year: int64, month: int64, \
day: int64, hour: int64, min: int64, sec: float64) \
-> local_datetime
:index: parse local_datetime
Create a :eql:type:`cal::local_datetime` value.
Similar to :eql:func:`to_datetime`, the :eql:type:`cal::local_datetime`
value can be parsed from the input :eql:type:`str` *s* with an
optional *fmt* argument or it can be given in terms of its
component parts: *year*, *month*, *day*, *hour*, *min*, *sec*.
For more details on formatting see :ref:`here
<ref_std_converters_datetime_fmt>`.
.. code-block:: edgeql-repl
db> select cal::to_local_datetime('2018-05-07T15:01:22.306916');
{<cal::local_datetime>'2018-05-07T15:01:22.306916'}
db> select cal::to_local_datetime('May 7th, 2018 15:01:22',
... 'Mon DDth, YYYY HH24:MI:SS');
{<cal::local_datetime>'2018-05-07T15:01:22'}
db> select cal::to_local_datetime(
... 2018, 5, 7, 15, 1, 22.306916);
{<cal::local_datetime>'2018-05-07T15:01:22.306916'}
A timezone-aware :eql:type:`datetime` type can be converted to local
datetime in the specified :ref:`timezone <ref_std_datetime_timezones>`:
.. code-block:: edgeql-repl
db> select cal::to_local_datetime(
... <datetime>'2018-12-31T22:00:00+08',
... 'America/Chicago');
{<cal::local_datetime>'2018-12-31T08:00:00'}
db> select cal::to_local_datetime(
... <datetime>'2018-12-31T22:00:00+08',
... 'CST');
{<cal::local_datetime>'2018-12-31T08:00:00'}
------------
.. eql:function:: cal::to_local_date(s: str, fmt: optional str={}) \
-> cal::local_date
cal::to_local_date(dt: datetime, zone: str) \
-> cal::local_date
cal::to_local_date(year: int64, month: int64, \
day: int64) -> cal::local_date
:index: parse local_date
Create a :eql:type:`cal::local_date` value.
Similar to :eql:func:`to_datetime`, the :eql:type:`cal::local_date`
value can be parsed from the input :eql:type:`str` *s* with an
optional *fmt* argument or it can be given in terms of its
component parts: *year*, *month*, *day*.
For more details on formatting see :ref:`here
<ref_std_converters_datetime_fmt>`.
.. code-block:: edgeql-repl
db> select cal::to_local_date('2018-05-07');
{<cal::local_date>'2018-05-07'}
db> select cal::to_local_date('May 7th, 2018', 'Mon DDth, YYYY');
{<cal::local_date>'2018-05-07'}
db> select cal::to_local_date(2018, 5, 7);
{<cal::local_date>'2018-05-07'}
A timezone-aware :eql:type:`datetime` type can be converted to local date
in the specified :ref:`timezone <ref_std_datetime_timezones>`:
.. code-block:: edgeql-repl
db> select cal::to_local_date(
... <datetime>'2018-12-31T22:00:00+08',
... 'America/Chicago');
{<cal::local_date>'2019-01-01'}
------------
.. eql:function:: cal::to_local_time(s: str, fmt: optional str={}) \
-> local_time
cal::to_local_time(dt: datetime, zone: str) \
-> local_time
cal::to_local_time(hour: int64, min: int64, sec: float64) \
-> local_time
:index: parse local_time
Create a :eql:type:`cal::local_time` value.
Similar to :eql:func:`to_datetime`, the :eql:type:`cal::local_time`
value can be parsed from the input :eql:type:`str` *s* with an
optional *fmt* argument or it can be given in terms of its
component parts: *hour*, *min*, *sec*.
For more details on formatting see :ref:`here
<ref_std_converters_datetime_fmt>`.
.. code-block:: edgeql-repl
db> select cal::to_local_time('15:01:22.306916');
{<cal::local_time>'15:01:22.306916'}
db> select cal::to_local_time('03:01:22pm', 'HH:MI:SSam');
{<cal::local_time>'15:01:22'}
db> select cal::to_local_time(15, 1, 22.306916);
{<cal::local_time>'15:01:22.306916'}
A timezone-aware :eql:type:`datetime` type can be converted to local date
in the specified :ref:`timezone <ref_std_datetime_timezones>`:
.. code-block:: edgeql-repl
db> select cal::to_local_time(
... <datetime>'2018-12-31T22:00:00+08',
... 'America/Los_Angeles');
{<cal::local_time>'06:00:00'}
------------
.. eql:function:: std::to_duration( \
named only hours: int64=0, \
named only minutes: int64=0, \
named only seconds: float64=0, \
named only microseconds: int64=0 \
) -> duration
:index: duration
Create a :eql:type:`duration` value.
This function uses ``named only`` arguments to create a
:eql:type:`duration` value. The available duration fields are:
*hours*, *minutes*, *seconds*, *microseconds*.
.. code-block:: edgeql-repl
db> select to_duration(hours := 1,
... minutes := 20,
... seconds := 45);
{4845s}
db> select to_duration(seconds := 4845);
{4845s}
.. eql:function:: std::duration_to_seconds(cur: duration) -> decimal
Return duration as total number of seconds in interval.
.. code-block:: edgeql-repl
db> select duration_to_seconds(<duration>'1 hour');
{3600.000000n}
db> select duration_to_seconds(<duration>'10 second 123 ms');
{10.123000n}
------------
.. eql:function:: cal::to_relative_duration( \
named only years: int64=0, \
named only months: int64=0, \
named only days: int64=0, \
named only hours: int64=0, \
named only minutes: int64=0, \
named only seconds: float64=0, \
named only microseconds: int64=0 \
) -> cal::relative_duration
:index: parse relative_duration
Create a :eql:type:`cal::relative_duration` value.
This function uses ``named only`` arguments to create a
:eql:type:`cal::relative_duration` value. The available duration fields
are: *years*, *months*, *days*, *hours*, *minutes*, *seconds*,
*microseconds*.
.. code-block:: edgeql-repl
db> select cal::to_relative_duration(years := 5, minutes := 1);
{<cal::relative_duration>'P5YT1S'}
db> select cal::to_relative_duration(months := 3, days := 27);
{<cal::relative_duration>'P3M27D'}
------------
.. eql:function:: cal::to_date_duration( \
named only years: int64=0, \
named only months: int64=0, \
named only days: int64=0 \
) -> cal::date_duration
:index: parse date_duration
Create a :eql:type:`cal::date_duration` value.
This function uses ``named only`` arguments to create a
:eql:type:`cal::date_duration` value. The available duration fields
are: *years*, *months*, *days*.
.. code-block:: edgeql-repl
db> select cal::to_date_duration(years := 1, days := 3);
{<cal::date_duration>'P1Y3D'}
db> select cal::to_date_duration(days := 12);
{<cal::date_duration>'P12D'}
------------
.. eql:function:: cal::duration_normalize_hours( \
dur: cal::relative_duration \
) -> cal::relative_duration
:index: justify_hours
Convert 24-hour chunks into days.
This function converts all 24-hour chunks into day units. The resulting
:eql:type:`cal::relative_duration` is guaranteed to have less than 24
hours in total in the units smaler than days.
.. code-block:: edgeql-repl
db> select cal::duration_normalize_hours(
... <cal::relative_duration>'1312 hours');
{<cal::relative_duration>'P54DT16H'}
This is a lossless operation because 24 hours are always equal to 1 day
in :eql:type:`cal::relative_duration` units.
This is sometimes used together with
:eql:func:`cal::duration_normalize_days`.
------------
.. eql:function:: cal::duration_normalize_days( \
dur: cal::relative_duration \
) -> cal::relative_duration
cal::duration_normalize_days( \
dur: cal::date_duration \
) -> cal::date_duration
:index: justify_days
Convert 30-day chunks into months.
This function converts all 30-day chunks into month units. The resulting
:eql:type:`cal::relative_duration` or :eql:type:`cal::date_duration` is
guaranteed to have less than 30 day units.
.. code-block:: edgeql-repl
db> select cal::duration_normalize_days(
... <cal::relative_duration>'1312 days');
{<cal::relative_duration>'P3Y7M22D'}
db> select cal::duration_normalize_days(
... <cal::date_duration>'1312 days');
{<cal::date_duration>'P3Y7M22D'}
This function is a form of approximation and does not preserve the exact
duration.
This is often used together with
:eql:func:`cal::duration_normalize_hours`.
================================================================================
.. File: deprecated.rst
.. _ref_std_deprecated:
==========
Deprecated
==========
:edb-alt-title: Deprecated Functions
.. list-table::
:class: funcoptable
* - :eql:func:`str_lpad`
- :eql:func-desc:`str_lpad`
* - :eql:func:`str_rpad`
- :eql:func-desc:`str_rpad`
* - :eql:func:`str_ltrim`
- :eql:func-desc:`str_ltrim`
* - :eql:func:`str_rtrim`
- :eql:func-desc:`str_rtrim`
----------
.. eql:function:: std::str_lpad(string: str, n: int64, fill: str = ' ') -> str
Return the input *string* left-padded to the length *n*.
.. warning::
This function is deprecated. Use
:eql:func:`std::str_pad_start` instead.
If the *string* is longer than *n*, then it is truncated to the
first *n* characters. Otherwise, the *string* is padded on the
left up to the total length *n* using *fill* characters (space by
default).
.. code-block:: edgeql-repl
db> select str_lpad('short', 10);
{' short'}
db> select str_lpad('much too long', 10);
{'much too l'}
db> select str_lpad('short', 10, '.:');
{'.:.:.short'}
----------
.. eql:function:: std::str_rpad(string: str, n: int64, fill: str = ' ') -> str
Return the input *string* right-padded to the length *n*.
.. warning::
This function is deprecated. Use
:eql:func:`std::str_pad_end` instead.
If the *string* is longer than *n*, then it is truncated to the
first *n* characters. Otherwise, the *string* is padded on the
right up to the total length *n* using *fill* characters (space by
default).
.. code-block:: edgeql-repl
db> select str_rpad('short', 10);
{'short '}
db> select str_rpad('much too long', 10);
{'much too l'}
db> select str_rpad('short', 10, '.:');
{'short.:.:.'}
----------
.. eql:function:: std::str_ltrim(string: str, trim: str = ' ') -> str
Return the input string with all leftmost *trim* characters removed.
.. warning::
This function is deprecated. Use
:eql:func:`std::str_trim_start` instead.
If the *trim* specifies more than one character they will be
removed from the beginning of the *string* regardless of the order
in which they appear.
.. code-block:: edgeql-repl
db> select str_ltrim(' data');
{'data'}
db> select str_ltrim('.....data', '.:');
{'data'}
db> select str_ltrim(':::::data', '.:');
{'data'}
db> select str_ltrim(':...:data', '.:');
{'data'}
db> select str_ltrim('.:.:.data', '.:');
{'data'}
----------
.. eql:function:: std::str_rtrim(string: str, trim: str = ' ') -> str
Return the input string with all rightmost *trim* characters removed.
.. warning::
This function is deprecated. Use
:eql:func:`std::str_trim_end` instead.
If the *trim* specifies more than one character they will be
removed from the end of the *string* regardless of the order
in which they appear.
.. code-block:: edgeql-repl
db> select str_rtrim('data ');
{'data'}
db> select str_rtrim('data.....', '.:');
{'data'}
db> select str_rtrim('data:::::', '.:');
{'data'}
db> select str_rtrim('data:...:', '.:');
{'data'}
db> select str_rtrim('data.:.:.', '.:');
{'data'}
----------
.. eql:type:: cfg::DatabaseConfig
The branch-level configuration object type.
As of |EdgeDB| 5.0, this config object represents database *branch*
and instance-level configuration.
**Use the identical** :eql:type:`cfg::BranchConfig` instead.
================================================================================
.. File: enum.rst
.. _ref_std_enum:
=====
Enums
=====
:edb-alt-title: Enum Type
.. eql:type:: std::enum
:index: enum
An enumerated type is a data type consisting of an ordered list of values.
An enum type can be declared in a schema by using the following
syntax:
.. code-block:: sdl
scalar type Color extending enum<Red, Green, Blue>;
Enum values can then be accessed directly:
.. code-block:: edgeql-repl
db> select Color.Red is Color;
{true}
:eql:op:`Casting <cast>` can be used to obtain an
enum value in an expression:
.. code-block:: edgeql-repl
db> select 'Red' is Color;
{false}
db> select <Color>'Red' is Color;
{true}
db> select <Color>'Red' = Color.Red;
{true}
.. note::
The enum values in EdgeQL are string-like in the fact that
they can contain any characters that the strings can. This is
different from some other languages where enum values are
identifier-like and thus cannot contain some characters. For
example, when working with GraphQL enum values that contain
characters that aren't allowed in identifiers cannot be
properly reflected. To address this, consider using only
identifier-like enum values in cases where such compatibility
is needed.
Currently, enum values cannot be longer than 63 characters.
================================================================================
.. File: fts.rst
.. _ref_std_fts:
.. versionadded:: 4.0
================
Full-text Search
================
The ``fts`` built-in module contains various tools that enable full-text
search functionality in Gel.
.. note::
Since full-text search is a natural language search, it may not be ideal
for your use case, particularly if you want to find partial matches. In
that case, you may want to look instead at :ref:`ref_ext_pgtrgm`.
.. list-table::
:class: funcoptable
* - :eql:type:`fts::Language`
- Common languages :eql:type:`enum`
* - :eql:type:`fts::PGLanguage`
- Postgres languages :eql:type:`enum`
* - :eql:type:`fts::Weight`
- Weight category :eql:type:`enum`
* - :eql:type:`fts::document`
- Opaque document type
* - :eql:func:`fts::search`
- :eql:func-desc:`fts::search`
* - :eql:func:`fts::with_options`
- :eql:func-desc:`fts::with_options`
When considering FTS functionality our goal was to come up with an interface
that could support different backend FTS providers. To achieve that we've
identified the following components to the FTS functionality:
1) Valid FTS targets must be indexed.
2) The expected language should be specified at the time of creating an index.
3) It should be possible to mark document parts as having different relevance.
4) It should be possible to assign custom weights at runtime so as to make
searching more flexible.
5) The search query should be close to what people are already used to.
To address (1) we introduce a special ``fts::index``. The presence of this
index in a type declaration indicates that the type in question can be subject
to full-text search. This is an unusual index as it actually affects the
results of :eql:func:`fts::search` function. This is unlike most indexes which
only affect the performance and not the actual results. Another special
feature of ``fts::index`` is that at most one such index can be declared per
type. If a type inherits this index from a parent and also declares its own,
only the new index applies and fully overrides the ``fts::index`` inherited
from the parent type. This means that when dealing with a hierarchy of
full-text-searchable types, each type can customize what gets searched as
needed.
The language (2) is defined as part of the ``fts::index on`` expression. A
special function :eql:func:`fts::with_options` is used for that purpose:
.. code-block:: sdl
type Item {
required available: bool {
default := false;
};
required name: str;
description: str;
index fts::index on (
fts::with_options(
.name,
language := fts::Language.eng
)
);
}
The above declaration specifies that ``Item`` is full-text-searchable,
specifically by examining the ``name`` property (and ignoring ``description``)
and assuming that the contents of that property are in English.
Marking different parts of the document as having different relevance (3) can
also be done by the :eql:func:`fts::with_options` function:
.. code-block:: sdl
type Item {
required available: bool {
default := false;
};
required name: str;
description: str;
index fts::index on ((
fts::with_options(
.name,
language := fts::Language.eng,
weight_category := fts::Weight.A,
),
fts::with_options(
.description,
language := fts::Language.eng,
weight_category := fts::Weight.B,
)
));
}
The schema now indicates that both ``name`` and ``description`` properties of
``Item`` are full-text-searchable. Additionally, the ``name`` and
``description`` have potentially different relevance.
By default :eql:func:`fts::search` assumes that the weight categories ``A``,
``B``, ``C``, and ``D`` have the following weights: ``[1, 0.5, 0.25, 0.125]``.
This makes each successive category relevance score halved.
Consider the following:
.. code-block:: edgeql-repl
gel> select Item{name, description};
{
default::Item {name: 'Canned corn', description: {}},
default::Item {
name: 'Candy corn',
description: 'A great Halloween treat',
},
default::Item {
name: 'Sweet',
description: 'Treat made with corn sugar',
},
}
gel> with res := (
.... select fts::search(Item, 'corn treat', language := 'eng')
.... )
.... select res.object {name, description, score := res.score}
.... order by res.score desc;
{
default::Item {
name: 'Candy corn',
description: 'A great Halloween treat',
score: 0.4559453,
},
default::Item {
name: 'Canned corn',
description: {},
score: 0.30396354,
},
default::Item {
name: 'Sweet',
description: 'Treat made with corn sugar',
score: 0.30396354,
},
}
As you can see, the highest scoring match came from an ``Item`` that had the
search terms appear in both ``name`` and ``description``. It is also apparent
that matching a single term from the search query in the ``name`` property
scores the same as matching two terms in ``description`` as we would expect
based on their weight categories. We can, however, customize the weights (4)
to change this trend:
.. code-block:: edgeql-repl
gel> with res := (
.... select fts::search(
.... Item, 'corn treat',
.... language := 'eng',
.... weights := [0.2, 1],
.... )
.... )
.... select res.object {name, description, score := res.score}
.... order by res.score desc;
{
default::Item {
name: 'Sweet',
description: 'Treat made with corn sugar',
score: 0.6079271,
},
default::Item {
name: 'Candy corn',
description: 'A great Halloween treat',
score: 0.36475626,
},
default::Item {
name: 'Canned corn',
description: {},
score: 0.06079271,
},
}
We can even use custom weights to completely ignore one of the properties
(e.g. ``name``) in our search, although we also need to add a filter based on
the score to make this work properly:
.. code-block:: edgeql-repl
gel> with res := (
.... select fts::search(
.... Item, 'corn treat',
.... language := 'eng',
.... weights := [0, 1],
.... )
.... )
.... select res.object {name, description, score := res.score}
.... filter res.score > 0
.... order by res.score desc;
{
default::Item {
name: 'Sweet',
description: 'Treat made with corn sugar',
score: 0.6079271,
},
default::Item {
name: 'Candy corn',
description: 'A great Halloween treat',
score: 0.30396354,
},
}
Finally, the search query supports features for fine-tuning (5). By default,
all search terms are desirable, but ultimately optional. You can enclose a
term or even a phrase in ``"..."`` to indicate that this specific term is of
increased importance and should appear in all matches:
.. code-block:: edgeql-repl
gel> with res := (
.... select fts::search(
.... Item, '"corn sugar"',
.... language := 'eng',
.... )
.... )
.... select res.object {name, description, score := res.score}
.... order by res.score desc;
{
default::Item {
name: 'Sweet',
description: 'Treat made with corn sugar',
score: 0.4955161,
},
}
Only one ``Item`` contains the phrase "corn sugar" and incomplete matches are
omitted.
The search query can also use ``AND`` (using upper-case to indicate that it is
a query modifier and not part of the query) to indicate whether terms are
required or optional:
.. code-block:: edgeql-repl
gel> with res := (
.... select fts::search(
.... Item, 'sweet AND treat',
.... language := 'eng',
.... )
.... )
.... select res.object {name, description, score := res.score}
.... order by res.score desc;
{
default::Item {
name: 'Sweet',
description: 'Treat made with corn sugar',
score: 0.70076555,
},
}
Adding a ``!`` in front of a search term marks it as something that
the matching object *must not* contain:
.. code-block:: edgeql-repl
gel> with res := (
.... select fts::search(
.... Item, '!treat',
.... language := 'eng',
.... )
.... )
.... select res.object {name, description, score := res.score}
.... order by res.score desc;
{
default::Item {
name: 'Canned corn',
description: {},
score: 0,
},
}
----------
.. eql:type:: fts::Language
An :eql:type:`enum` for representing commonly supported languages.
When indexing an object for full-text search it is important to specify
the expected language by :eql:func:`fts::with_options` function. This
particular :eql:type:`enum` represents languages that are common across
several possible [future] backend implementations and thus are "safe" even
if the backend implementation switches from one of the options to another.
This generic enum is the recommended way of specifying the language.
The following `ISO 639-3 <iso639_>`_ language identifiers are available:
``ara``, ``hye``, ``eus``, ``cat``, ``dan``, ``nld``, ``eng``, ``fin``,
``fra``, ``deu``, ``ell``, ``hin``, ``hun``, ``ind``, ``gle``, ``ita``,
``nor``, ``por``, ``ron``, ``rus``, ``spa``, ``swe``, ``tur``.
----------
.. eql:type:: fts::PGLanguage
An :eql:type:`enum` for representing languages supported by PostgreSQL.
When indexing an object for full-text search it is important to specify
the expected language by :eql:func:`fts::with_options` function. This
particular :eql:type:`enum` represents languages that are available in
PostgreSQL implementation of full-text search.
The following `ISO 639-3 <iso639_>`_ language identifiers are available:
``ara``, ``hye``, ``eus``, ``cat``, ``dan``, ``nld``, ``eng``, ``fin``,
``fra``, ``deu``, ``ell``, ``hin``, ``hun``, ``ind``, ``gle``, ``ita``,
``lit``, ``npi``, ``nor``, ``por``, ``ron``, ``rus``, ``srp``, ``spa``,
``swe``, ``tam``, ``tur``, ``yid``.
Additionally, the ``xxx_simple`` identifier is also available to represent
the ``pg_catalog.simple`` language setting.
Unless you need some particular language setting that is not available in
the :eql:type:`fts::Language`, it is recommended that you use the more
general lanuguage enum instead.
----------
.. eql:type:: fts::Weight
An :eql:type:`enum` for representing weight categories.
When indexing an object for full-text search different properties of this
object may have different significance. To account for that, they can be
assigned different weight categories by using
:eql:func:`fts::with_options` function. There are four available weight
categories: ``A``, ``B``, ``C``, or ``D``.
----------
.. eql:type:: fts::document
An opaque transient type used in ``fts::index``.
This type is technically what the ``fts::index`` expects as a valid ``on``
expression. It cannot be directly instantiated and can only be produced as
the result of applying the special :eql:func:`fts::with_options` function.
Thus this type only appears in full-text search index definitions and
cannot appear as either a property type or anywhere in regular queries.
------------
.. eql:function:: fts::search( \
object: anyobject, \
query: str, \
named only language: str = <str>fts::Language.eng, \
named only weights: optional array<float64> = {}, \
) -> optional tuple<object: anyobject, score: float32>
Perform full-text search on a target object.
This function applies the search ``query`` to the specified object. If a
match is found, the result will consist of a tuple with the matched
``object`` and the corresponding ``score``. A higher ``score`` indicates a
better match. In case no match is found, the function will return an empty
set ``{}``. Likewise, ``{}`` is returned if the ``object`` has no
``fts::index`` defined for it.
The ``language`` parameter can be specified in order to match the expected
indexed language. In case of mismatch there is a big chance that the query
will not produce the expected results.
The optional ``weights`` parameter can be passed in order to provide
custom weights to the different weight groups. By default, the weights are
``[1, 0.5, 0.25, 0.125]`` representing groups of diminishing significance.
------------
.. eql:function:: fts::with_options( \
text: str, \
NAMED ONLY language: anyenum, \
NAMED ONLY weight_category: optional fts::Weight = \
fts::Weight.A, \
) -> fts::document
Assign language and weight category to a document portion.
This is a special function that can only appear inside ``fts::index``
expressions.
The ``text`` expression specifies the portion of the document that will be
indexed and available for full-text search.
The ``language`` parameter specifies the expected language of the ``text``
expression. This affects how the index accounts for grammatical variants
of a given word (e.g. how plural and singular forms are determined, etc.).
The ``weight_category`` parameter assigns one of four available weight
categories to the ``text`` expression: ``A``, ``B``, ``C``, or ``D``. By
themselves, the categories simply group together portions of the document
so that these groups can be ascribed different significance by the
:eql:func:`fts::search` function. By default it is assumed that each
successive category is half as significant as the previous, starting with
``A`` as the most significant. However, these default weights can be
overridden when making a call to :eql:func:`fts::search`.
.. _iso639: https://iso639-3.sil.org/code_tables/639/data
================================================================================
.. File: generic.rst
.. _ref_std_generic:
=======
Generic
=======
:edb-alt-title: Generic Functions and Operators
.. list-table::
:class: funcoptable
* - :eql:op:`anytype = anytype <eq>`
- :eql:op-desc:`eq`
* - :eql:op:`anytype != anytype <neq>`
- :eql:op-desc:`neq`
* - :eql:op:`anytype ?= anytype <coaleq>`
- :eql:op-desc:`coaleq`
* - :eql:op:`anytype ?!= anytype <coalneq>`
- :eql:op-desc:`coalneq`
* - :eql:op:`anytype \< anytype <lt>`
- :eql:op-desc:`lt`
* - :eql:op:`anytype \> anytype <gt>`
- :eql:op-desc:`gt`
* - :eql:op:`anytype \<= anytype <lteq>`
- :eql:op-desc:`lteq`
* - :eql:op:`anytype \>= anytype <gteq>`
- :eql:op-desc:`gteq`
* - :eql:func:`len`
- :eql:func-desc:`len`
* - :eql:func:`contains`
- :eql:func-desc:`contains`
* - :eql:func:`find`
- :eql:func-desc:`find`
.. note::
In EdgeQL, any value can be compared to another as long as their types
are compatible.
-----------
.. eql:operator:: eq: anytype = anytype -> bool
:index: =, equal, comparison, compare
Compares two values for equality.
.. code-block:: edgeql-repl
db> select 3 = 3.0;
{true}
db> select 3 = 3.14;
{false}
db> select [1, 2] = [1, 2];
{true}
db> select (1, 2) = (x := 1, y := 2);
{true}
db> select (x := 1, y := 2) = (a := 1, b := 2);
{true}
db> select 'hello' = 'world';
{false}
.. warning::
When either operand in an equality comparison is an empty set, the
result will not be a ``bool`` but instead an empty set.
.. code-block:: edgeql-repl
db> select true = <bool>{};
{}
If one of the operands in an equality comparison could be an empty set,
you may want to use the :eql:op:`coalescing equality <coaleq>` operator
(``?=``) instead.
----------
.. eql:operator:: neq: anytype != anytype -> bool
:index: !=, not equal, comparison, compare
Compares two values for inequality.
.. code-block:: edgeql-repl
db> select 3 != 3.0;
{false}
db> select 3 != 3.14;
{true}
db> select [1, 2] != [2, 1];
{false}
db> select (1, 2) != (x := 1, y := 2);
{false}
db> select (x := 1, y := 2) != (a := 1, b := 2);
{false}
db> select 'hello' != 'world';
{true}
.. warning::
When either operand in an inequality comparison is an empty set, the
result will not be a ``bool`` but instead an empty set.
.. code-block:: edgeql-repl
db> select true != <bool>{};
{}
If one of the operands in an inequality comparison could be an empty
set, you may want to use the :eql:op:`coalescing inequality <coaleq>`
operator (``?!=``) instead.
----------
.. eql:operator:: coaleq: optional anytype ?= optional anytype -> bool
:index: ?=, coalesce equal, comparison, compare, empty set
Compares two (potentially empty) values for equality.
This works the same as a regular :eql:op:`=<eq>` operator, but also allows
comparing an empty ``{}`` set. Two empty sets are considered equal.
.. code-block:: edgeql-repl
db> select {1} ?= {1.0};
{true}
db> select {1} ?= <int64>{};
{false}
db> select <int64>{} ?= <int64>{};
{true}
----------
.. eql:operator:: coalneq: optional anytype ?!= optional anytype -> bool
:index: ?!=, coalesce not equal, comparison, compare
Compares two (potentially empty) values for inequality.
This works the same as a regular :eql:op:`=<eq>` operator, but also allows
comparing an empty ``{}`` set. Two empty sets are considered equal.
.. code-block:: edgeql-repl
db> select {2} ?!= {2};
{false}
db> select {1} ?!= <int64>{};
{true}
db> select <bool>{} ?!= <bool>{};
{false}
----------
.. eql:operator:: lt: anytype < anytype -> bool
:index: <, less than, comparison, compare
Less than operator.
The operator returns ``true`` if the value of the left expression is less
than the value of the right expression:
.. code-block:: edgeql-repl
db> select 1 < 2;
{true}
db> select 2 < 2;
{false}
db> select 'hello' < 'world';
{true}
db> select (1, 'hello') < (1, 'world');
{true}
.. warning::
When either operand in a comparison is an empty set, the result will
not be a ``bool`` but instead an empty set.
.. code-block:: edgeql-repl
db> select 1 < <int16>{};
{}
If one of the operands in a comparison could be an empty set, you may
want to coalesce the result of the comparison with ``false`` to ensure
your result is boolean.
.. code-block:: edgeql-repl
db> select (1 < <int16>{}) ?? false;
{false}
----------
.. eql:operator:: gt: anytype > anytype -> bool
:index: >, greater than, comparison, compare
Greater than operator.
The operator returns ``true`` if the value of the left expression is
greater than the value of the right expression:
.. code-block:: edgeql-repl
db> select 1 > 2;
{false}
db> select 3 > 2;
{true}
db> select 'hello' > 'world';
{false}
db> select (1, 'hello') > (1, 'world');
{false}
.. warning::
When either operand in a comparison is an empty set, the result will
not be a ``bool`` but instead an empty set.
.. code-block:: edgeql-repl
db> select 1 > <int16>{};
{}
If one of the operands in a comparison could be an empty set, you may
want to coalesce the result of the comparison with ``false`` to ensure
your result is boolean.
.. code-block:: edgeql-repl
db> select (1 > <int16>{}) ?? false;
{false}
----------
.. eql:operator:: lteq: anytype <= anytype -> bool
:index: <=, less than or equal, comparison, compare
Less or equal operator.
The operator returns ``true`` if the value of the left expression is less
than or equal to the value of the right expression:
.. code-block:: edgeql-repl
db> select 1 <= 2;
{true}
db> select 2 <= 2;
{true}
db> select 3 <= 2;
{false}
db> select 'hello' <= 'world';
{true}
db> select (1, 'hello') <= (1, 'world');
{true}
.. warning::
When either operand in a comparison is an empty set, the result will
not be a ``bool`` but instead an empty set.
.. code-block:: edgeql-repl
db> select 1 <= <int16>{};
{}
If one of the operands in a comparison could be an empty set, you may
want to coalesce the result of the comparison with ``false`` to ensure
your result is boolean.
.. code-block:: edgeql-repl
db> select (1 <= <int16>{}) ?? false;
{false}
----------
.. eql:operator:: gteq: anytype >= anytype -> bool
:index: >=, greater than or equal, comparison, compare
Greater or equal operator.
The operator returns ``true`` if the value of the left expression is
greater than or equal to the value of the right expression:
.. code-block:: edgeql-repl
db> select 1 >= 2;
{false}
db> select 2 >= 2;
{true}
db> select 3 >= 2;
{true}
db> select 'hello' >= 'world';
{false}
db> select (1, 'hello') >= (1, 'world');
{false}
.. warning::
When either operand in a comparison is an empty set, the result will
not be a ``bool`` but instead an empty set.
.. code-block:: edgeql-repl
db> select 1 >= <int16>{};
{}
If one of the operands in a comparison could be an empty set, you may
want to coalesce the result of the comparison with ``false`` to ensure
your result is boolean.
.. code-block:: edgeql-repl
db> select (1 >= <int16>{}) ?? false;
{false}
----------
.. eql:function:: std::len(value: str) -> int64
std::len(value: bytes) -> int64
std::len(value: array<anytype>) -> int64
:index: length count array
Returns the number of elements of a given value.
This function works with the :eql:type:`str`, :eql:type:`bytes` and
:eql:type:`array` types:
.. code-block:: edgeql-repl
db> select len('foo');
{3}
db> select len(b'bar');
{3}
db> select len([2, 5, 7]);
{3}
----------
.. eql:function:: std::contains(haystack: str, needle: str) -> bool
std::contains(haystack: bytes, needle: bytes) -> bool
std::contains(haystack: array<anytype>, needle: anytype) \
-> bool
std::contains(haystack: range<anypoint>, \
needle: range<anypoint>) \
-> std::bool
std::contains(haystack: range<anypoint>, \
needle: anypoint) \
-> std::bool
std::contains(haystack: multirange<anypoint>, \
needle: multirange<anypoint>) \
-> std::bool
std::contains(haystack: multirange<anypoint>, \
needle: range<anypoint>) \
-> std::bool
std::contains(haystack: multirange<anypoint>, \
needle: anypoint) \
-> std::bool
:index: find strpos strstr position array
Returns true if the given sub-value exists within the given value.
When *haystack* is a :eql:type:`str` or a :eql:type:`bytes` value,
this function will return ``true`` if it contains *needle* as a
subsequence within it or ``false`` otherwise:
.. code-block:: edgeql-repl
db> select contains('qwerty', 'we');
{true}
db> select contains(b'qwerty', b'42');
{false}
When *haystack* is an :eql:type:`array`, the function will return
``true`` if the array contains the element specified as *needle* or
``false`` otherwise:
.. code-block:: edgeql-repl
db> select contains([2, 5, 7, 2, 100], 2);
{true}
When *haystack* is a :ref:`range <ref_std_range>`, the function will
return ``true`` if it contains either the specified sub-range or element.
The function will return ``false`` otherwise.
.. code-block:: edgeql-repl
db> select contains(range(1, 10), range(2, 5));
{true}
db> select contains(range(1, 10), range(2, 15));
{false}
db> select contains(range(1, 10), 2);
{true}
db> select contains(range(1, 10), 10);
{false}
When *haystack* is a :ref:`multirange <ref_std_multirange>`, the function
will return ``true`` if it contains either the specified multirange,
sub-range or element. The function will return ``false`` otherwise.
.. code-block:: edgeql-repl
db> select contains(
... multirange([
... range(1, 4), range(7),
... ]),
... multirange([
... range(1, 2), range(8, 10),
... ]),
... );
{true}
db> select contains(
... multirange([
... range(1, 4), range(8, 10),
... ]),
... range(8),
... );
{false}
db> select contains(
... multirange([
... range(1, 4), range(8, 10),
... ]),
... 3,
... );
{true}
When *haystack* is :ref:`JSON <ref_std_json>`, the function will return
``true`` if the json data contains the element specified as *needle* or
``false`` otherwise:
.. code-block:: edgeql-repl
db> with haystack := to_json('{
... "city": "Baerlon",
... "city": "Caemlyn"
... }'),
... needle := to_json('{
... "city": "Caemlyn"
... }'),
... select contains(haystack, needle);
{true}
----------
.. eql:function:: std::find(haystack: str, needle: str) -> int64
std::find(haystack: bytes, needle: bytes) -> int64
std::find(haystack: array<anytype>, needle: anytype, \
from_pos: int64=0) -> int64
:index: find strpos strstr position array
Returns the index of a given sub-value in a given value.
When *haystack* is a :eql:type:`str` or a :eql:type:`bytes` value, the
function will return the index of the first occurrence of *needle* in it.
When *haystack* is an :eql:type:`array`, this will return the index of the
the first occurrence of the element passed as *needle*. For
:eql:type:`array` inputs it is also possible to provide an optional
*from_pos* argument to specify the position from which to start the
search.
If the *needle* is not found, return ``-1``.
.. code-block:: edgeql-repl
db> select find('qwerty', 'we');
{1}
db> select find(b'qwerty', b'42');
{-1}
db> select find([2, 5, 7, 2, 100], 2);
{0}
db> select find([2, 5, 7, 2, 100], 2, 1);
{3}
================================================================================
.. File: index.rst
.. versioned-section::
.. _ref_std:
================
Standard Library
================
.. toctree::
:maxdepth: 3
:hidden:
generic
set
type
math
string
bool
numbers
json
uuid
enum
datetime
array
tuple
range
bytes
sequence
objects
abstract
constraints
net
fts
sys
cfg
pgcrypto
pg_trgm
pg_unaccent
pgvector
deprecated
|Gel| comes with a rigorously defined type system consisting of **scalar
types**, **collection types** (like arrays and tuples), and **object types**.
There is also a library of built-in functions and operators for working with
each datatype.
.. _ref_datamodel_typesystem:
Scalar Types
------------
.. _ref_datamodel_scalar_types:
*Scalar types* store primitive data.
- :ref:`Strings <ref_std_string>`
- :ref:`Numbers <ref_std_numeric>`
- :ref:`Booleans <ref_std_logical>`
- :ref:`Dates and times <ref_std_datetime>`
- :ref:`Enums <ref_std_enum>`
- :ref:`JSON <ref_std_json>`
- :ref:`UUID <ref_std_uuid>`
- :ref:`Bytes <ref_std_bytes>`
- :ref:`Sequences <ref_std_sequence>`
- :ref:`Abstract types <ref_std_abstract_types>`: these are the types that
undergird the scalar hierarchy.
.. _ref_datamodel_collection_types:
Collection Types
----------------
*Collection types* are special generic types used to group homogeneous or
heterogeneous data.
- :ref:`Arrays <ref_std_array>`
- :ref:`Tuples <ref_std_tuple>`
Range Types
-----------
- :ref:`Range <ref_std_range>`
- :ref:`Multirange <ref_std_multirange>`
Object Types
------------
- :ref:`Object Types <ref_std_object_types>`
Types and Sets
--------------
- :ref:`Sets <ref_std_set>`
- :ref:`Types <ref_std_type>`
- :ref:`Casting <ref_eql_casts>`
Utilities
---------
- :ref:`Math <ref_std_math>`
- :ref:`Comparison <ref_std_generic>`
- :ref:`Constraints <ref_std_constraints>`
- :ref:`Full-text Search <ref_std_fts>`
- :ref:`System <ref_std_sys>`
Extensions
----------
- :ref:`ext::pgvector <ref_ext_pgvector>`
================================================================================
.. File: json.rst
.. _ref_std_json:
====
JSON
====
:edb-alt-title: JSON Functions and Operators
.. list-table::
:class: funcoptable
* - :eql:type:`json`
- JSON scalar type
* - :eql:op:`json[i] <jsonidx>`
- :eql:op-desc:`jsonidx`
* - :eql:op:`json[from:to] <jsonslice>`
- :eql:op-desc:`jsonslice`
* - :eql:op:`json ++ json <jsonplus>`
- :eql:op-desc:`jsonplus`
* - :eql:op:`json[name] <jsonobjdest>`
- :eql:op-desc:`jsonobjdest`
* - :eql:op:`= <eq>` :eql:op:`\!= <neq>` :eql:op:`?= <coaleq>`
:eql:op:`?!= <coalneq>` :eql:op:`\< <lt>` :eql:op:`\> <gt>`
:eql:op:`\<= <lteq>` :eql:op:`\>= <gteq>`
- Comparison operators
* - :eql:func:`to_json`
- :eql:func-desc:`to_json`
* - :eql:func:`to_str`
- Render JSON value to a string.
* - :eql:func:`json_get`
- :eql:func-desc:`json_get`
* - :eql:func:`json_set`
- :eql:func-desc:`json_set`
* - :eql:func:`json_array_unpack`
- :eql:func-desc:`json_array_unpack`
* - :eql:func:`json_object_pack`
- :eql:func-desc:`json_object_pack`
* - :eql:func:`json_object_unpack`
- :eql:func-desc:`json_object_unpack`
* - :eql:func:`json_typeof`
- :eql:func-desc:`json_typeof`
.. _ref_std_json_construction:
Constructing JSON Values
------------------------
JSON in Gel is a :ref:`scalar type <ref_datamodel_scalar_types>`. This type
doesn't have its own literal, and instead can be obtained by either casting a
value to the :eql:type:`json` type, or by using the :eql:func:`to_json`
function:
.. code-block:: edgeql-repl
db> select to_json('{"hello": "world"}');
{Json("{\"hello\": \"world\"}")}
db> select <json>'hello world';
{Json("\"hello world\"")}
Any value in Gel can be cast to a :eql:type:`json` type as well:
.. code-block:: edgeql-repl
db> select <json>2019;
{Json("2019")}
db> select <json>cal::to_local_date(datetime_current(), 'UTC');
{Json("\"2022-11-21\"")}
The :eql:func:`json_object_pack` function provides one more way to
construct JSON. It constructs a JSON object from an array of key/value
tuples:
.. code-block:: edgeql-repl
db> select json_object_pack({("hello", <json>"world")});
{Json("{\"hello\": \"world\"}")}
Additionally, any :eql:type:`Object` in Gel can be cast as a
:eql:type:`json` type. This produces the same JSON value as the
JSON-serialized result of that said object. Furthermore, this result will
be the same as the output of a :eql:stmt:`select expression <select>` in
*JSON mode*, including the shape of that type:
.. code-block:: edgeql-repl
db> select <json>(
... select schema::Object {
... name,
... timestamp := cal::to_local_date(
... datetime_current(), 'UTC')
... }
... filter .name = 'std::bool');
{Json("{\"name\": \"std::bool\", \"timestamp\": \"2022-11-21\"}")}
JSON values can also be cast back into scalars. Casting JSON is symmetrical
meaning that, if a scalar value can be cast into JSON, a compatible JSON value
can be cast into a scalar of that type. Some scalar types will have specific
conditions for casting:
- JSON strings can be cast to a :eql:type:`str` type. Casting :eql:type:`uuid`
and :ref:`date/time <ref_std_datetime>` types to JSON results in a JSON
string representing its original value. This means it is also possible to
cast a JSON string back to those types. The value of the UUID or datetime
string must be properly formatted to successfully cast from JSON, otherwise
Gel will raise an exception.
- JSON numbers can be cast to any :ref:`numeric type <ref_std_numeric>`.
- JSON booleans can be cast to a :eql:type:`bool` type.
- JSON ``null`` is unique because it can be cast to an empty set (``{}``) of
any type.
- JSON arrays can be cast to any valid array type, as long as the JSON array
is homogeneous, does not contain ``null`` as an element of the array, and
does not contain another array.
A named :eql:type:`tuple` is converted into a JSON object when cast as a
:eql:type:`json` while a standard :eql:type:`tuple` is converted into a
JSON array.
----------
.. eql:type:: std::json
Arbitrary JSON data.
Any other type can be :eql:op:`cast <cast>` to and from JSON:
.. code-block:: edgeql-repl
db> select <json>42;
{Json("42")}
db> select <bool>to_json('true');
{true}
A :eql:type:`json` value can also be cast as a :eql:type:`str` type, but
only when recognized as a JSON string:
.. code-block:: edgeql-repl
db> select <str>to_json('"something"');
{'something'}
Casting a JSON array of strings (``["a", "b", "c"]``) to a :eql:type:`str`
will result in an error:
.. code-block:: edgeql-repl
db> select <str>to_json('["a", "b", "c"]');
InvalidValueError: expected json string or null; got JSON array
Instead, use the :eql:func:`to_str` function to dump a JSON value to a
:eql:type:`str` value. Use the :eql:func:`to_json` function to parse a
JSON string to a :eql:type:`json` value:
.. code-block:: edgeql-repl
db> select to_json('[1, "a"]');
{Json("[1, \"a\"]")}
db> select to_str(<json>[1, 2]);
{'[1, 2]'}
.. note::
This type is backed by the Postgres ``jsonb`` type which has a size
limit of 256MiB minus one byte. The Gel ``json`` type is also
subject to this limitation.
----------
.. eql:operator:: jsonidx: json [ int64 ] -> json
:index: [int], index access
Accesses the element of the JSON string or array at a given index.
The contents of JSON *arrays* and *strings* can also be
accessed via ``[]``:
.. code-block:: edgeql-repl
db> select <json>'hello'[1];
{Json("\"e\"")}
db> select <json>'hello'[-1];
{Json("\"o\"")}
db> select to_json('[1, "a", null]')[1];
{Json("\"a\"")}
db> select to_json('[1, "a", null]')[-1];
{Json("null")}
This will raise an exception if the specified index is not valid for the
base JSON value. To access an index that is potentially out of bounds, use
:eql:func:`json_get`.
----------
.. eql:operator:: jsonslice: json [ int64 : int64 ] -> json
:index: [int:int]
Produces a JSON value comprising a portion of the existing JSON value.
JSON *arrays* and *strings* can be sliced in the same way as
regular arrays, producing a new JSON array or string:
.. code-block:: edgeql-repl
db> select <json>'hello'[0:2];
{Json("\"he\"")}
db> select <json>'hello'[2:];
{Json("\"llo\"")}
db> select to_json('[1, 2, 3]')[0:2];
{Json("[1, 2]")}
db> select to_json('[1, 2, 3]')[2:];
{Json("[3]")}
db> select to_json('[1, 2, 3]')[:1];
{Json("[1]")}
db> select to_json('[1, 2, 3]')[:-2];
{Json("[1]")}
----------
.. eql:operator:: jsonplus: json ++ json -> json
:index: ++, concatenate, join, add
Concatenates two JSON arrays, objects, or strings into one.
JSON arrays, objects and strings can be concatenated with JSON values of
the same type into a new JSON value.
If you concatenate two JSON objects, you get a new object whose keys will
be a union of the keys of the input objects. If a key is present in both
objects, the value from the second object is taken.
.. code-block:: edgeql-repl
db> select to_json('[1, 2]') ++ to_json('[3]');
{Json("[1, 2, 3]")}
db> select to_json('{"a": 1}') ++ to_json('{"b": 2}');
{Json("{\"a\": 1, \"b\": 2}")}
db> select to_json('{"a": 1, "b": 2}') ++ to_json('{"b": 3}');
{Json("{\"a\": 1, \"b\": 3}")}
db> select to_json('"123"') ++ to_json('"456"');
{Json("\"123456\"")}
----------
.. eql:operator:: jsonobjdest: json [ str ] -> json
:index: [str], json get key
Accesses an element of a JSON object given its key.
The fields of JSON *objects* can also be accessed via ``[]``:
.. code-block:: edgeql-repl
db> select to_json('{"a": 2, "b": 5}')['b'];
{Json("5")}
db> select j := <json>(schema::Type {
... name,
... timestamp := cal::to_local_date(datetime_current(), 'UTC')
... })
... filter j['name'] = <json>'std::bool';
{Json("{\"name\": \"std::bool\", \"timestamp\": \"2022-11-21\"}")}
This will raise an exception if the specified field does not exist for the
base JSON value. To access an index that is potentially out of bounds, use
:eql:func:`json_get`.
----------
.. eql:function:: std::to_json(string: str) -> json
:index: json parse loads
Returns a JSON value parsed from the given string.
.. code-block:: edgeql-repl
db> select to_json('[1, "hello", null]');
{Json("[1, \"hello\", null]")}
db> select to_json('{"hello": "world"}');
{Json("{\"hello\": \"world\"}")}
----------
.. eql:function:: std::json_array_unpack(json: json) -> set of json
:index: array unpack
Returns the elements of a JSON array as a set of :eql:type:`json`.
Calling this function on anything other than a JSON array will
result in a runtime error.
This function should be used only if the ordering of elements is not
important, or when the ordering of the set is preserved (such as an
immediate input to an aggregate function).
.. code-block:: edgeql-repl
db> select json_array_unpack(to_json('[1, "a"]'));
{Json("1"), Json("\"a\"")}
----------
.. eql:function:: std::json_get(json: json, \
variadic path: str) -> optional json
:index: safe navigation
Returns a value from a JSON object or array given its path.
This function provides "safe" navigation of a JSON value. If the
input path is a valid path for the input JSON object/array, the
JSON value at the end of that path is returned:
.. code-block:: edgeql-repl
db> select json_get(to_json('{
... "q": 1,
... "w": [2, "foo"],
... "e": true
... }'), 'w', '1');
{Json("\"foo\"")}
This is useful when certain structure of JSON data is assumed, but cannot
be reliably guaranteed. If the path cannot be followed for any reason, the
empty set is returned:
.. code-block:: edgeql-repl
db> select json_get(to_json('{
... "q": 1,
... "w": [2, "foo"],
... "e": true
... }'), 'w', '2');
{}
If you want to supply your own default for the case where the path cannot
be followed, you can do so using the :eql:op:`coalesce` operator:
.. code-block:: edgeql-repl
db> select json_get(to_json('{
... "q": 1,
... "w": [2, "foo"],
... "e": true
... }'), 'w', '2') ?? <json>'mydefault';
{Json("\"mydefault\"")}
----------
.. eql:function:: std::json_set( \
target: json, \
variadic path: str, \
named only value: optional json, \
named only create_if_missing: bool = true, \
named only empty_treatment: JsonEmpty = \
JsonEmpty.ReturnEmpty) \
-> optional json
Returns an updated JSON target with a new value.
.. code-block:: edgeql-repl
db> select json_set(
... to_json('{"a": 10, "b": 20}'),
... 'a',
... value := <json>true,
... );
{Json("{\"a\": true, \"b\": 20}")}
db> select json_set(
... to_json('{"a": {"b": {}}}'),
... 'a', 'b', 'c',
... value := <json>42,
... );
{Json("{\"a\": {\"b\": {\"c\": 42}}}")}
If *create_if_missing* is set to ``false``, a new path for the value
won't be created:
.. code-block:: edgeql-repl
db> select json_set(
... to_json('{"a": 10, "b": 20}'),
... 'с',
... value := <json>42,
... );
{Json("{\"a\": 10, \"b\": 20, \"с\": 42}")}
db> select json_set(
... to_json('{"a": 10, "b": 20}'),
... 'с',
... value := <json>42,
... create_if_missing := false,
... );
{Json("{\"a\": 10, \"b\": 20}")}
The *empty_treatment* parameter defines the behavior of the function if an
empty set is passed as *new_value*. This parameter can take these values:
- ``ReturnEmpty``: return empty set, default
- ``ReturnTarget``: return ``target`` unmodified
- ``Error``: raise an ``InvalidValueError``
- ``UseNull``: use a ``null`` JSON value
- ``DeleteKey``: delete the object key
.. code-block:: edgeql-repl
db> select json_set(
... to_json('{"a": 10, "b": 20}'),
... 'a',
... value := <json>{}
... );
{}
db> select json_set(
... to_json('{"a": 10, "b": 20}'),
... 'a',
... value := <json>{},
... empty_treatment := JsonEmpty.ReturnTarget,
... );
{Json("{\"a\": 10, \"b\": 20}")}
db> select json_set(
... to_json('{"a": 10, "b": 20}'),
... 'a',
... value := <json>{},
... empty_treatment := JsonEmpty.Error,
... );
InvalidValueError: invalid empty JSON value
db> select json_set(
... to_json('{"a": 10, "b": 20}'),
... 'a',
... value := <json>{},
... empty_treatment := JsonEmpty.UseNull,
... );
{Json("{\"a\": null, \"b\": 20}")}
db> select json_set(
... to_json('{"a": 10, "b": 20}'),
... 'a',
... value := <json>{},
... empty_treatment := JsonEmpty.DeleteKey,
... );
{Json("{\"b\": 20}")}
----------
.. eql:function:: std::json_object_pack(pairs: SET OF tuple<str, json>) -> \
json
Returns the given set of key/value tuples as a JSON object.
.. code-block:: edgeql-repl
db> select json_object_pack({
... ("foo", to_json("1")),
... ("bar", to_json("null")),
... ("baz", to_json("[]"))
... });
{Json("{\"bar\": null, \"baz\": [], \"foo\": 1}")}
If the key/value tuples being packed have common keys, the last value for
each key will make the final object.
.. code-block:: edgeql-repl
db> select json_object_pack({
... ("hello", <json>"world"),
... ("hello", <json>true)
... });
{Json("{\"hello\": true}")}
----------
.. eql:function:: std::json_object_unpack(json: json) -> \
set of tuple<str, json>
Returns the data in a JSON object as a set of key/value tuples.
Calling this function on anything other than a JSON object will
result in a runtime error.
.. code-block:: edgeql-repl
db> select json_object_unpack(to_json('{
... "q": 1,
... "w": [2, "foo"],
... "e": true
... }'));
{('e', Json("true")), ('q', Json("1")), ('w', Json("[2, \"foo\"]"))}
----------
.. eql:function:: std::json_typeof(json: json) -> str
:index: type
Returns the type of the outermost JSON value as a string.
Possible return values are: ``'object'``, ``'array'``,
``'string'``, ``'number'``, ``'boolean'``, or ``'null'``:
.. code-block:: edgeql-repl
db> select json_typeof(<json>2);
{'number'}
db> select json_typeof(to_json('null'));
{'null'}
db> select json_typeof(to_json('{"a": 2}'));
{'object'}
================================================================================
.. File: math_funcops_table.rst
.. list-table::
:class: funcoptable
* - :eql:func:`math::abs`
- :eql:func-desc:`math::abs`
* - :eql:func:`math::ceil`
- :eql:func-desc:`math::ceil`
* - :eql:func:`math::floor`
- :eql:func-desc:`math::floor`
* - :eql:func:`math::ln`
- :eql:func-desc:`math::ln`
* - :eql:func:`math::lg`
- :eql:func-desc:`math::lg`
* - :eql:func:`math::log`
- :eql:func-desc:`math::log`
* - :eql:func:`math::mean`
- :eql:func-desc:`math::mean`
* - :eql:func:`math::stddev`
- :eql:func-desc:`math::stddev`
* - :eql:func:`math::stddev_pop`
- :eql:func-desc:`math::stddev_pop`
* - :eql:func:`math::var`
- :eql:func-desc:`math::var`
* - :eql:func:`math::var_pop`
- :eql:func-desc:`math::var_pop`
* - :eql:func:`math::pi`
- :eql:func-desc:`math::pi`
* - :eql:func:`math::acos`
- :eql:func-desc:`math::acos`
* - :eql:func:`math::asin`
- :eql:func-desc:`math::asin`
* - :eql:func:`math::atan`
- :eql:func-desc:`math::atan`
* - :eql:func:`math::atan2`
- :eql:func-desc:`math::atan2`
* - :eql:func:`math::cos`
- :eql:func-desc:`math::cos`
* - :eql:func:`math::cot`
- :eql:func-desc:`math::cot`
* - :eql:func:`math::sin`
- :eql:func-desc:`math::sin`
* - :eql:func:`math::tan`
- :eql:func-desc:`math::tan`
================================================================================
.. File: math.rst
.. _ref_std_math:
====
Math
====
:edb-alt-title: Mathematical Functions
.. include:: math_funcops_table.rst
-----------
.. eql:function:: math::abs(x: anyreal) -> anyreal
:index: absolute
Returns the absolute value of the input.
.. code-block:: edgeql-repl
db> select math::abs(1);
{1}
db> select math::abs(-1);
{1}
----------
.. eql:function:: math::ceil(x: int64) -> float64
math::ceil(x: float64) -> float64
math::ceil(x: bigint) -> bigint
math::ceil(x: decimal) -> decimal
:index: round
Rounds up a given value to the nearest integer.
.. code-block:: edgeql-repl
db> select math::ceil(1.1);
{2}
db> select math::ceil(-1.1);
{-1}
----------
.. eql:function:: math::floor(x: int64) -> float64
math::floor(x: float64) -> float64
math::floor(x: bigint) -> bigint
math::floor(x: decimal) -> decimal
:index: round
Rounds down a given value to the nearest integer.
.. code-block:: edgeql-repl
db> select math::floor(1.1);
{1}
db> select math::floor(-1.1);
{-2}
----------
.. eql:function:: math::ln(x: int64) -> float64
math::ln(x: float64) -> float64
math::ln(x: decimal) -> decimal
:index: logarithm
Returns the natural logarithm of a given value.
.. code-block:: edgeql-repl
db> select 2.718281829 ^ math::ln(100);
{100.00000009164575}
----------
.. eql:function:: math::lg(x: int64) -> float64
math::lg(x: float64) -> float64
math::lg(x: decimal) -> decimal
:index: logarithm
Returns the base 10 logarithm of a given value.
.. code-block:: edgeql-repl
db> select 10 ^ math::lg(42);
{42.00000000000001}
----------
.. eql:function:: math::log(x: decimal, named only base: decimal) -> decimal
:index: logarithm
Returns the logarithm of a given value in the specified base.
.. code-block:: edgeql-repl
db> select 3 ^ math::log(15n, base := 3n);
{15.0000000000000005n}
----------
.. eql:function:: math::mean(vals: set of int64) -> float64
math::mean(vals: set of float64) -> float64
math::mean(vals: set of decimal) -> decimal
:index: average avg
Returns the arithmetic mean of the input set.
.. code-block:: edgeql-repl
db> select math::mean({1, 3, 5});
{3}
----------
.. eql:function:: math::stddev(vals: set of int64) -> float64
math::stddev(vals: set of float64) -> float64
math::stddev(vals: set of decimal) -> decimal
:index: average
Returns the sample standard deviation of the input set.
.. code-block:: edgeql-repl
db> select math::stddev({1, 3, 5});
{2}
.. eql:function:: math::stddev_pop(vals: set of int64) -> float64
math::stddev_pop(vals: set of float64) -> float64
math::stddev_pop(vals: set of decimal) -> decimal
:index: average
Returns the population standard deviation of the input set.
.. code-block:: edgeql-repl
db> select math::stddev_pop({1, 3, 5});
{1.63299316185545}
----------
.. eql:function:: math::var(vals: set of int64) -> float64
math::var(vals: set of float64) -> float64
math::var(vals: set of decimal) -> decimal
:index: average
Returns the sample variance of the input set.
.. code-block:: edgeql-repl
db> select math::var({1, 3, 5});
{4}
----------
.. eql:function:: math::var_pop(vals: set of int64) -> float64
math::var_pop(vals: set of float64) -> float64
math::var_pop(vals: set of decimal) -> decimal
:index: average
Returns the population variance of the input set.
.. code-block:: edgeql-repl
db> select math::var_pop({1, 3, 5});
{2.66666666666667}
-----------
.. eql:function:: math::pi() -> float64
:index: trigonometry
Returns the value of pi.
.. code-block:: edgeql-repl
db> select math::pi();
{3.141592653589793}
-----------
.. eql:function:: math::acos(x: float64) -> float64
:index: trigonometry
Returns the arc cosine of the input.
.. code-block:: edgeql-repl
db> select math::acos(-1);
{3.141592653589793}
db> select math::acos(0);
{1.5707963267948966}
db> select math::acos(1);
{0}
-----------
.. eql:function:: math::asin(x: float64) -> float64
:index: trigonometry
Returns the arc sine of the input.
.. code-block:: edgeql-repl
db> select math::asin(-1);
{-1.5707963267948966}
db> select math::asin(0);
{0}
db> select math::asin(1);
{1.5707963267948966}
-----------
.. eql:function:: math::atan(x: float64) -> float64
:index: trigonometry
Returns the arc tangent of the input.
.. code-block:: edgeql-repl
db> select math::atan(-1);
{-0.7853981633974483}
db> select math::atan(0);
{0}
db> select math::atan(1);
{0.7853981633974483}
-----------
.. eql:function:: math::atan2(y: float64, x: float64) -> float64
:index: trigonometry
Returns the arc tangent of ``y / x``.
Uses the signs of the arguments determine the correct quadrant.
.. code-block:: edgeql-repl
db> select math::atan2(1, 1);
{0.7853981633974483}
db> select math::atan2(1, -1);
{2.356194490192345}
db> select math::atan2(-1, -1);
{-2.356194490192345}
db> select math::atan2(-1, 1);
{-0.7853981633974483}
-----------
.. eql:function:: math::cos(x: float64) -> float64
:index: trigonometry
Returns the cosine of the input.
.. code-block:: edgeql-repl
db> select math::cos(0);
{1}
db> select math::cos(math::pi() / 2);
{0.000000000}
db> select math::cos(math::pi());
{-1}
db> select math::cos(math::pi() * 3 / 2);
{-0.000000000}
-----------
.. eql:function:: math::cot(x: float64) -> float64
:index: trigonometry
Returns the cotangent of the input.
.. code-block:: edgeql-repl
db> select math::cot(math::pi() / 4);
{1.000000000}
db> select math::cot(math::pi() / 2);
{0.000000000}
db> select math::cot(math::pi() * 3 / 4);
{-0.999999999}
-----------
.. eql:function:: math::sin(x: float64) -> float64
:index: trigonometry
Returns the sinine of the input.
.. code-block:: edgeql-repl
db> select math::sin(0);
{0}
db> select math::sin(math::pi() / 2);
{1}
db> select math::sin(math::pi());
{0.000000000}
db> select math::sin(math::pi() * 3 / 2);
{-1}
-----------
.. eql:function:: math::tan(x: float64) -> float64
:index: trigonometry
Returns the tanangent of the input.
.. code-block:: edgeql-repl
db> select math::tan(-math::pi() / 4);
{-0.999999999}
db> select math::tan(0);
{0}
db> select math::tan(math::pi() / 4);
{0.999999999}
================================================================================
.. File: net.rst
.. _ref_std_net:
.. versionadded:: 6.0
===
Net
===
The ``net`` module provides an interface for performing network-related operations directly from Gel. It is useful for integrating with external services, fetching data from APIs, or triggering webhooks as part of your database logic.
.. list-table::
:class: funcoptable
* - :eql:type:`net::RequestState`
- An enum representing the state of a network request.
* - :eql:type:`net::RequestFailureKind`
- An enum representing the kind of failure that occurred for a network request.
* - :eql:type:`net::http::Method`
- An enum representing HTTP methods.
* - :eql:type:`net::http::Response`
- A type representing an HTTP response.
* - :eql:type:`net::http::ScheduledRequest`
- A type representing a scheduled HTTP request.
----------
.. eql:type:: net::RequestState
An enumeration of possible states for a network request.
Possible values are:
* ``Pending``
* ``InProgress``
* ``Completed``
* ``Failed``
----------
.. eql:type:: net::RequestFailureKind
An enumeration of possible failure kinds for a network request.
Possible values are:
* ``NetworkError``
* ``Timeout``
----------
HTTP Submodule
==============
The ``net::http`` submodule provides types and functions for making HTTP requests.
Overview
--------
The primary function for scheduling HTTP requests is :eql:func:`net::http::schedule_request`. This function lets you specify the URL, HTTP method, headers, and body of the request. Once scheduled, you can monitor the request's status and process its response when available.
Example Usage
-------------
Here's a simple example of how to use the ``net::http`` module to make a GET request:
.. code-block:: edgeql
with request := (
net::http::schedule_request(
'https://example.com',
method := net::http::Method.POST,
headers := [('Content-Type', 'application/json')],
body := <bytes>$${"key": "value"}$$
)
)
select request.id;
This ID will be helpful if you need to observe a request's response. You can poll the ``ScheduledRequest`` object in order to get any response data or failure information:
1. **Check the State**: Use the ``state`` field to determine the current status of the request.
2. **Handle Failures**: If the request has failed, inspect the ``failure`` field to understand the kind of failure (e.g., ``NetworkError`` or ``Timeout``) and any associated message.
3. **Process the Response**: If the request is completed successfully, access the ``response.body`` to retrieve the data returned by the request. The body is in ``bytes`` format and may need conversion or parsing.
In the following example, we'll query the ``ScheduledRequest`` object we created above using the ID we selected. Once the request is completed or it has failed, this query will return the response data or the failure information:
.. code-block:: edgeql
with
request := <std::net::http::ScheduledRequest><uuid>$request_id,
select request {
state,
failure,
response: {
status,
headers,
body,
},
} filter .state in {net::RequestState.Failed, net::RequestState.Completed};
Reference
---------
.. eql:type:: net::http::Method
An enumeration of supported HTTP methods.
Possible values are:
* ``GET``
* ``POST``
* ``PUT``
* ``DELETE``
* ``HEAD``
* ``OPTIONS``
* ``PATCH``
----------
.. eql:type:: net::http::Response
A type representing an HTTP response.
:eql:synopsis:`created_at -> datetime`
The timestamp when the response was created.
:eql:synopsis:`status -> int16`
The HTTP status code of the response.
:eql:synopsis:`headers -> array<tuple<name: str, value: str>>`
The headers of the response.
:eql:synopsis:`body -> bytes`
The body of the response.
----------
.. eql:type:: net::http::ScheduledRequest
A type representing a scheduled HTTP request.
:eql:synopsis:`state -> net::RequestState`
The current state of the request.
:eql:synopsis:`created_at -> datetime`
The timestamp when the request was created.
:eql:synopsis:`failure -> tuple<kind: net::RequestFailureKind, message: str>`
Information about the failure, if the request failed.
:eql:synopsis:`url -> str`
The URL of the request.
:eql:synopsis:`method -> net::http::Method`
The HTTP method of the request.
:eql:synopsis:`headers -> array<tuple<name: str, value: str>>`
The headers of the request.
:eql:synopsis:`body -> bytes`
The body of the request.
:eql:synopsis:`response -> net::http::Response`
The response to the request, if completed.
----------
.. eql:function:: net::http::schedule_request( \
url: str, \
body: optional bytes = {}, \
method: optional net::http::Method = net::http::Method.`GET`, \
headers: optional array<tuple<name: str, value: str>> = {} \
) -> net::http::ScheduledRequest
Schedules an HTTP request.
Parameters:
* ``url``: The URL to send the request to.
* ``body``: The body of the request (optional).
* ``method``: The HTTP method to use (optional, defaults to GET).
* ``headers``: The headers to include in the request (optional).
Returns ``net::http::ScheduledRequest`` object representing
the scheduled request.
Example:
.. code-block:: edgeql
SELECT net::http::schedule_request(
'https://example.com',
method := net::http::Method.POST,
headers := [('Content-Type', 'application/json')],
body := <bytes>$${"key": "value"}$$
);
================================================================================
.. File: numbers.rst
.. _ref_std_numeric:
=======
Numbers
=======
:edb-alt-title: Numerical Types, Functions, and Operators
.. list-table::
:class: funcoptable
* - :eql:type:`int16`
- 16-bit integer
* - :eql:type:`int32`
- 32-bit integer
* - :eql:type:`int64`
- 64-bit integer
* - :eql:type:`float32`
- 32-bit floating point number
* - :eql:type:`float64`
- 64-bit floating point number
* - :eql:type:`bigint`
- Arbitrary precision integer.
* - :eql:type:`decimal`
- Arbitrary precision number.
* - :eql:op:`anyreal + anyreal <plus>`
- :eql:op-desc:`plus`
* - :eql:op:`anyreal - anyreal <minus>`
- :eql:op-desc:`minus`
* - :eql:op:`-anyreal <uminus>`
- :eql:op-desc:`uminus`
* - :eql:op:`anyreal * anyreal <mult>`
- :eql:op-desc:`mult`
* - :eql:op:`anyreal / anyreal <div>`
- :eql:op-desc:`div`
* - :eql:op:`anyreal // anyreal <floordiv>`
- :eql:op-desc:`floordiv`
* - :eql:op:`anyreal % anyreal <mod>`
- :eql:op-desc:`mod`
* - :eql:op:`anyreal ^ anyreal <pow>`
- :eql:op-desc:`pow`
* - :eql:op:`= <eq>` :eql:op:`\!= <neq>` :eql:op:`?= <coaleq>`
:eql:op:`?!= <coalneq>` :eql:op:`\< <lt>` :eql:op:`\> <gt>`
:eql:op:`\<= <lteq>` :eql:op:`\>= <gteq>`
- Comparison operators
* - :eql:func:`sum`
- :eql:func-desc:`sum`
* - :eql:func:`min`
- :eql:func-desc:`min`
* - :eql:func:`max`
- :eql:func-desc:`max`
* - :eql:func:`round`
- :eql:func-desc:`round`
* - :eql:func:`random`
- :eql:func-desc:`random`
Mathematical functions
----------------------
.. include:: math_funcops_table.rst
Bitwise functions
-----------------
.. list-table::
:class: funcoptable
* - :eql:func:`bit_and`
- :eql:func-desc:`bit_and`
* - :eql:func:`bit_or`
- :eql:func-desc:`bit_or`
* - :eql:func:`bit_xor`
- :eql:func-desc:`bit_xor`
* - :eql:func:`bit_not`
- :eql:func-desc:`bit_not`
* - :eql:func:`bit_lshift`
- :eql:func-desc:`bit_lshift`
* - :eql:func:`bit_rshift`
- :eql:func-desc:`bit_rshift`
* - :eql:func:`bit_count`
- :eql:func-desc:`bit_count`
String parsing
--------------
.. list-table::
:class: funcoptable
* - :eql:func:`to_bigint`
- :eql:func-desc:`to_bigint`
* - :eql:func:`to_decimal`
- :eql:func-desc:`to_decimal`
* - :eql:func:`to_int16`
- :eql:func-desc:`to_int16`
* - :eql:func:`to_int32`
- :eql:func-desc:`to_int32`
* - :eql:func:`to_int64`
- :eql:func-desc:`to_int64`
* - :eql:func:`to_float32`
- :eql:func-desc:`to_float32`
* - :eql:func:`to_float64`
- :eql:func-desc:`to_float64`
It's possible to explicitly :eql:op:`cast <cast>`
between all numeric types. All numeric types can also be cast to and
from :eql:type:`str` and :eql:type:`json`.
Definitions
-----------
.. eql:type:: std::int16
:index: int integer
A 16-bit signed integer.
``int16`` is capable of representing values from ``-32768`` to
``+32767`` (inclusive).
----------
.. eql:type:: std::int32
:index: int integer
A 32-bit signed integer.
``int32`` is capable of representing values from ``-2147483648`` to
``+2147483647`` (inclusive).
----------
.. eql:type:: std::int64
:index: int integer
A 64-bit signed integer.
``int64`` is capable of representing values from ``-9223372036854775808``
to ``+9223372036854775807`` (inclusive).
----------
.. eql:type:: std::float32
:index: float
A variable precision, inexact number.
The minimal guaranteed precision is at least 6 decimal digits. The
approximate range of a ``float32`` spans from ``-3.4e+38`` to
``+3.4e+38``.
----------
.. eql:type:: std::float64
:index: float double
A variable precision, inexact number.
The minimal guaranteed precision is at least 15 decimal digits. The
approximate range of a ``float64`` spans from ``-1.7e+308`` to
``+1.7e+308``.
----------
.. eql:type:: std::bigint
:index: numeric bigint
An arbitrary precision integer.
Our philosophy is that use of ``bigint`` should always be an explicit
opt-in and should never be implicit. Once used, these values should not be
accidentally cast to a different numerical type that could lead to a loss
of precision.
In keeping with this philosophy, :ref:`our mathematical functions
<ref_std_math>` are designed to maintain separation between big integer
values and the rest of our numeric types.
All of the following types can be explicitly cast into a ``bigint`` type:
- :eql:type:`str`
- :eql:type:`json`
- :eql:type:`int16`
- :eql:type:`int32`
- :eql:type:`int64`
- :eql:type:`float32`
- :eql:type:`float64`
- :eql:type:`decimal`
A bigint literal is an integer literal, followed by 'n':
.. code-block:: edgeql-repl
db> select 42n is bigint;
{true}
To represent really big integers, it is possible to use the
exponent notation (e.g. ``1e20n`` instead of ``100000000000000000000n``)
as long as the exponent is positive and there is no dot anywhere:
.. code-block:: edgeql-repl
db> select 1e+100n is bigint;
{true}
When a float literal is followed by ``n`` it will produce a
:eql:type:`decimal` value instead:
.. code-block:: edgeql-repl
db> select 1.23n is decimal;
{true}
db> select 1.0e+100n is decimal;
{true}
.. note::
Use caution when casting ``bigint`` values into
:eql:type:`json`. The JSON specification does not have a limit on
significant digits, so a ``bigint`` number can be losslessly
represented in JSON. However, JSON decoders in many languages
will read all such numbers as some kind of 32-bit or 64-bit
number type, which may result in errors or precision loss. If
such loss is unacceptable, then consider casting the value
into :eql:type:`str` and decoding it on the client side into a more
appropriate type.
----------
.. eql:type:: std::decimal
:index: numeric float
Any number of arbitrary precision.
Our philosophy is that use of ``decimal`` should always be an explicit
opt-in and should never be implicit. Once used, these values should not be
accidentally cast to a different numerical type that could lead to a loss
of precision.
In keeping with this philosophy, :ref:`our mathematical functions
<ref_std_math>` are designed to maintain separation between ``decimal``
values and the rest of our numeric types.
All of the following types can be explicitly cast into decimal:
- :eql:type:`str`
- :eql:type:`json`
- :eql:type:`int16`
- :eql:type:`int32`
- :eql:type:`int64`
- :eql:type:`float32`
- :eql:type:`float64`
- :eql:type:`bigint`
A decimal literal is a float literal, followed by ``n``:
The Gel philosophy is that using a decimal type should be an
explicit opt-in, but once used, the values should not be
accidentally cast into a numeric type with less precision.
In accordance with this :ref:`the mathematical functions
<ref_std_math>` are designed to keep the separation
between decimal values and the rest of the numeric types.
All of the following types can be explicitly cast into decimal:
:eql:type:`str`, :eql:type:`json`, :eql:type:`int16`,
:eql:type:`int32`, :eql:type:`int64`, :eql:type:`float32`,
:eql:type:`float64`, and :eql:type:`bigint`.
A decimal literal is a float literal followed by 'n':
.. code-block:: edgeql-repl
db> select 1.23n is decimal;
{true}
db> select 1.0e+100n is decimal;
{true}
Note that an integer literal (without a dot or exponent) followed
by ``n`` produces a :eql:type:`bigint` value. A literal without a dot
and with a positive exponent makes a :eql:type:`bigint`, too:
.. code-block:: edgeql-repl
db> select 42n is bigint;
{true}
db> select 12e+34n is bigint;
{true}
.. note::
Use caution when casting ``decimal`` values into :eql:type:`json`. The
JSON specification does not have a limit on significant digits, so a
``decimal`` number can be losslessly represented in JSON. However,
JSON decoders in many languages will read all such numbers as some
kind of floating point values, which may result in precision loss. If
such loss is unacceptable, then consider casting the value into a
:eql:type:`str` and decoding it on the client side into a more
appropriate type.
----------
.. eql:operator:: plus: anyreal + anyreal -> anyreal
:index: +, addition
Arithmetic addition.
.. code-block:: edgeql-repl
db> select 2 + 2;
{4}
----------
.. eql:operator:: minus: anyreal - anyreal -> anyreal
:index: -, subtraction
Arithmetic subtraction.
.. code-block:: edgeql-repl
db> select 3 - 2;
{1}
----------
.. eql:operator:: uminus: - anyreal -> anyreal
:index: -, unary minus, subtraction
Arithmetic negation.
.. code-block:: edgeql-repl
db> select -5;
{-5}
----------
.. eql:operator:: mult: anyreal * anyreal -> anyreal
:index: \*, multiply, multiplication
Arithmetic multiplication.
.. code-block:: edgeql-repl
db> select 2 * 10;
{20}
----------
.. eql:operator:: div: anyreal / anyreal -> anyreal
:index: /, divide, division
Arithmetic division.
.. code-block:: edgeql-repl
db> select 10 / 4;
{2.5}
Division by zero will result in an error:
.. code-block:: edgeql-repl
db> select 10 / 0;
DivisionByZeroError: division by zero
----------
.. eql:operator:: floordiv: anyreal // anyreal -> anyreal
:index: //, floor divide, division
Floor division.
In floor-based division, the result of a standard division operation is
rounded down to its nearest integer. It is the equivalent to using regular
division and then applying :eql:func:`math::floor` to the result.
.. code-block:: edgeql-repl
db> select 10 // 4;
{2}
db> select math::floor(10 / 4);
{2}
db> select -10 // 4;
{-3}
It also works on :eql:type:`float <anyfloat>`, :eql:type:`bigint`, and
:eql:type:`decimal` types. The type of the result corresponds to
the type of the operands:
.. code-block:: edgeql-repl
db> select 3.7 // 1.1;
{3.0}
db> select 3.7n // 1.1n;
{3.0n}
db> select 37 // 11;
{3}
Regular division, floor division, and :eql:op:`%<mod>` operations are
related in the following way: ``A // B = (A - (A % B)) / B``.
----------
.. eql:operator:: mod: anyreal % anyreal -> anyreal
:index: %, modulo division, remainder
Remainder from division (modulo).
This is commonly referred to as a "modulo" operation.
This is the remainder from floor division. Just as is
the case with :eql:op:`//<floordiv>` the result type of the
remainder operator corresponds to the operand type:
.. code-block:: edgeql-repl
db> select 10 % 4;
{2}
db> select 10n % 4;
{2n}
db> select -10 % 4;
{2}
db> # floating arithmetic is inexact, so
... # we get 0.3999999999999999 instead of 0.4
... select 3.7 % 1.1;
{0.3999999999999999}
db> select 3.7n % 1.1n;
{0.4n}
db> select 37 % 11;
{4}
Regular division, :eql:op:`//<floordiv>` and :eql:op:`%<mod>` operations
are related in the following way: ``A // B = (A - (A % B)) / B``.
Modulo division by zero will result in an error:
.. code-block:: edgeql-repl
db> select 10 % 0;
DivisionByZeroError: division by zero
-----------
.. eql:operator:: pow: anyreal ^ anyreal -> anyreal
:index: ^, power, exponentiation
Power operation.
.. code-block:: edgeql-repl
db> select 2 ^ 4;
{16}
----------
.. eql:function:: std::round(value: int64) -> float64
std::round(value: float64) -> float64
std::round(value: bigint) -> bigint
std::round(value: decimal) -> decimal
std::round(value: decimal, d: int64) -> decimal
Rounds a given number to the nearest value.
The function will round a ``.5`` value differently depending on the type
of the parameter passed.
The :eql:type:`float64` tie is rounded to the nearest even number:
.. code-block:: edgeql-repl
db> select round(1.2);
{1}
db> select round(1.5);
{2}
db> select round(2.5);
{2}
But the :eql:type:`decimal` tie is rounded away from zero:
.. code-block:: edgeql-repl
db> select round(1.2n);
{1n}
db> select round(1.5n);
{2n}
db> select round(2.5n);
{3n}
Additionally, when rounding a :eql:type:`decimal` value, you may pass the
optional argument *d* to specify the precision of the rounded result:
.. code-block:: edgeql-repl
db> select round(163.278n, 2);
{163.28n}
db> select round(163.278n, 1);
{163.3n}
db> select round(163.278n, 0);
{163n}
db> select round(163.278n, -1);
{160n}
db> select round(163.278n, -2);
{200n}
----------
.. eql:function:: std::random() -> float64
Returns a pseudo-random number in the range of ``0.0 <= x < 1.0``.
.. code-block:: edgeql-repl
db> select random();
{0.62649393780157}
----------
.. eql:function:: std::bit_and(l: int16, r: int16) -> int16
std::bit_and(l: int32, r: int32) -> int32
std::bit_and(l: int64, r: int64) -> int64
Bitwise AND operator for 2 intergers.
.. code-block:: edgeql-repl
db> select bit_and(17, 3);
{1}
----------
.. eql:function:: std::bit_or(l: int16, r: int16) -> int16
std::bit_or(l: int32, r: int32) -> int32
std::bit_or(l: int64, r: int64) -> int64
Bitwise OR operator for 2 intergers.
.. code-block:: edgeql-repl
db> select bit_or(17, 3);
{19}
----------
.. eql:function:: std::bit_xor(l: int16, r: int16) -> int16
std::bit_xor(l: int32, r: int32) -> int32
std::bit_xor(l: int64, r: int64) -> int64
Bitwise exclusive OR operator for 2 intergers.
.. code-block:: edgeql-repl
db> select bit_xor(17, 3);
{18}
----------
.. eql:function:: std::bit_not(r: int16) -> int16
std::bit_not(r: int32) -> int32
std::bit_not(r: int64) -> int64
Bitwise negation operator for 2 intergers.
Bitwise negation for integers ends up similar to mathematical negation
because typically the signed integers use "two's complement"
representation. In this represenation mathematical negation is achieved by
aplying bitwise negation and adding ``1``.
.. code-block:: edgeql-repl
db> select bit_not(17);
{-18}
db> select -17 = bit_not(17) + 1;
{true}
----------
.. eql:function:: std::bit_lshift(val: int16, n: int64) -> int16
std::bit_lshift(val: int32, n: int64) -> int32
std::bit_lshift(val: int64, n: int64) -> int64
Bitwise left-shift operator for intergers.
The integer *val* is shifted by *n* bits to the left. The rightmost added
bits are all ``0``. Shifting an integer by a number of bits greater than
the bit size of the integer results in ``0``.
.. code-block:: edgeql-repl
db> select bit_lshift(123, 2);
{492}
db> select bit_lshift(123, 65);
{0}
Left-shifting an integer can change the sign bit:
.. code-block:: edgeql-repl
db> select bit_lshift(123, 60);
{-5764607523034234880}
In general, left-shifting an integer in small increments produces the same
result as shifting it in one step:
.. code-block:: edgeql-repl
db> select bit_lshift(bit_lshift(123, 1), 3);
{1968}
db> select bit_lshift(123, 4);
{1968}
It is an error to attempt to shift by a negative number of bits:
.. code-block:: edgeql-repl
db> select bit_lshift(123, -2);
gel error: InvalidValueError: bit_lshift(): cannot shift by
negative amount
----------
.. eql:function:: std::bit_rshift(val: int16, n: int64) -> int16
std::bit_rshift(val: int32, n: int64) -> int32
std::bit_rshift(val: int64, n: int64) -> int64
Bitwise arithemtic right-shift operator for intergers.
The integer *val* is shifted by *n* bits to the right. In the arithmetic
right-shift, the sign is preserved. This means that the leftmost added bits
are ``1`` or ``0`` depending on the sign bit. Shifting an integer by a
number of bits greater than the bit size of the integer results in ``0``
for positive numbers or ``-1`` for negative numbers.
.. code-block:: edgeql-repl
db> select bit_rshift(123, 2);
{30}
db> select bit_rshift(123, 65);
{0}
db> select bit_rshift(-123, 2);
{-31}
db> select bit_rshift(-123, 65);
{-1}
In general, right-shifting an integer in small increments produces the same
result as shifting it in one step:
.. code-block:: edgeql-repl
db> select bit_rshift(bit_rshift(123, 1), 3);
{7}
db> select bit_rshift(123, 4);
{7}
db> select bit_rshift(bit_rshift(-123, 1), 3);
{-8}
db> select bit_rshift(-123, 4);
{-8}
It is an error to attempt to shift by a negative number of bits:
.. code-block:: edgeql-repl
db> select bit_rshift(123, -2);
gel error: InvalidValueError: bit_rshift(): cannot shift by
negative amount
------------
.. eql:function:: std::bit_count(val: int16) -> int64
std::bit_count(val: int32) -> int64
std::bit_count(val: int64) -> int64
std::bit_count(bytes: bytes) -> int64
Return the number of bits set in the :eql:type:`bytes` value.
This is also known as the population count.
.. code-block:: edgeql-repl
db> select bit_count(255);
{8}
db> select bit_count(b'\xff\xff');
{16}
------------
.. eql:function:: std::to_bigint(s: str, fmt: optional str={}) -> bigint
:index: parse bigint
Returns a :eql:type:`bigint` value parsed from the given string.
The function will use an optional format string passed as *fmt*. See the
:ref:`number formatting options <ref_std_converters_number_fmt>` for help
writing a format string.
.. code-block:: edgeql-repl
db> select to_bigint('-000,012,345', 'S099,999,999,999');
{-12345n}
db> select to_bigint('31st', '999th');
{31n}
------------
.. eql:function:: std::to_decimal(s: str, fmt: optional str={}) -> decimal
:index: parse decimal
Returns a :eql:type:`decimal` value parsed from the given string.
The function will use an optional format string passed as *fmt*. See the
:ref:`number formatting options <ref_std_converters_number_fmt>` for help
writing a format string.
.. code-block:: edgeql-repl
db> select to_decimal('-000,012,345', 'S099,999,999,999');
{-12345.0n}
db> select to_decimal('-012.345');
{-12.345n}
db> select to_decimal('31st', '999th');
{31.0n}
------------
.. eql:function:: std::to_int16(s: str, fmt: optional str={}) -> int16
std::to_int16(val: bytes, endian: Endian) -> int16
:index: parse int16
Returns an :eql:type:`int16` value parsed from the given input.
The string parsing function will use an optional format string passed as
*fmt*. See the :ref:`number formatting options
<ref_std_converters_number_fmt>` for help writing a format string.
.. code-block:: edgeql-repl
db> select to_int16('23');
{23}
db> select to_int16('23%', '99%');
{23}
The bytes conversion function expects exactly 2 bytes with specified
endianness.
.. code-block:: edgeql-repl
db> select to_int16(b'\x00\x07', Endian.Big);
{7}
db> select to_int16(b'\x07\x00', Endian.Little);
{7}
.. note::
Due to underlying implementation details using big-endian encoding
results in slightly faster performance of ``to_int16``.
------------
.. eql:function:: std::to_int32(s: str, fmt: optional str={}) -> int32
std::to_int32(val: bytes, endian: Endian) -> int32
:index: parse int32
Returns an :eql:type:`int32` value parsed from the given input.
The string parsin function will use an optional format string passed as
*fmt*. See the :ref:`number formatting options
<ref_std_converters_number_fmt>` for help writing a format string.
.. code-block:: edgeql-repl
db> select to_int32('1000023');
{1000023}
db> select to_int32('1000023%', '9999999%');
{1000023}
The bytes conversion function expects exactly 4 bytes with specified
endianness.
.. code-block:: edgeql-repl
db> select to_int32(b'\x01\x02\x00\x07', Endian.Big);
{16908295}
db> select to_int32(b'\x07\x00\x02\x01', Endian.Little);
{16908295}
.. note::
Due to underlying implementation details using big-endian encoding
results in slightly faster performance of ``to_int32``.
------------
.. eql:function:: std::to_int64(s: str, fmt: optional str={}) -> int64
std::to_int64(val: bytes, endian: Endian) -> int64
:index: parse int64
Returns an :eql:type:`int64` value parsed from the given input.
The string parsing function will use an optional format string passed as
*fmt*. See the :ref:`number formatting options
<ref_std_converters_number_fmt>` for help writing a format string.
.. code-block:: edgeql-repl
db> select to_int64('10000234567');
{10000234567}
db> select to_int64('10000234567%', '99999999999%');
{10000234567}
The bytes conversion function expects exactly 8 bytes with specified
endianness.
.. code-block:: edgeql-repl
db> select to_int64(b'\x01\x02\x00\x07\x11\x22\x33\x44',
... Endian.Big);
{72620574343574340}
db> select to_int64(b'\x44\x33\x22\x11\x07\x00\x02\x01',
... Endian.Little);
{72620574343574340}
.. note::
Due to underlying implementation details using big-endian encoding
results in slightly faster performance of ``to_int64``.
------------
.. eql:function:: std::to_float32(s: str, fmt: optional str={}) -> float32
:index: parse float32
Returns a :eql:type:`float32` value parsed from the given string.
The function will use an optional format string passed as *fmt*. See the
:ref:`number formatting options <ref_std_converters_number_fmt>` for help
writing a format string.
------------
.. eql:function:: std::to_float64(s: str, fmt: optional str={}) -> float64
:index: parse float64
Returns a :eql:type:`float64` value parsed from the given string.
The function will use an optional format string passed as *fmt*. See the
:ref:`number formatting options <ref_std_converters_number_fmt>` for help
writing a format string.
================================================================================
.. File: objects.rst
.. _ref_std_object_types:
============
Base Objects
============
.. list-table::
:class: funcoptable
* - :eql:type:`BaseObject`
- Root object type
* - :eql:type:`Object`
- Root for user-defined object types
``std::BaseObject`` is the root of the object type hierarchy and all object
types in Gel, including system types, extend it either directly or
indirectly. User-defined object types extend from :eql:type:`std::Object`
type, which is a subtype of ``std::BaseObject``.
---------
.. eql:type:: std::BaseObject
The root object type.
Definition:
.. code-block:: sdl
abstract type std::BaseObject {
# Universally unique object identifier
required id: uuid {
default := (select std::uuid_generate_v1mc());
readonly := true;
constraint exclusive;
}
# Object type in the information schema.
required readonly __type__: schema::ObjectType;
}
Subtypes may override the ``id`` property, but only with a valid UUID
generation function. Currently, these are :eql:func:`uuid_generate_v1mc`
and :eql:func:`uuid_generate_v4`.
---------
.. eql:type:: std::Object
The root object type for user-defined types.
Definition:
.. code-block:: sdl
abstract type std::Object extending std::BaseObject;
================================================================================
.. File: pg_trgm.rst
.. versionadded:: 4.0
.. _ref_ext_pgtrgm:
============
ext::pg_trgm
============
This extension provides tools for determining similarity of text based on
trigram matching.
Word similarity tools can often supplement :ref:`full-text search
<ref_std_fts>`. Full-text search concentrates on matching words and phrases
typically trying to account for some grammatical variations, while trigram
matching analyzes similarity between words. Thus trigram matching can account
for misspelling or words that aren't dictionary words:
.. code-block:: edgeql-repl
db> select fts::search(Doc, 'thaco').object{text};
{}
db> select Doc{text} filter ext::pg_trgm::word_similar('thaco', Doc.text);
{
default::Doc {
text: 'THAC0 is used in AD&D 2 to determine likelihood of hitting',
},
}
The first search attempt fails to produce results because "THAC0" is an
obscure acronym that is misspelled in the query. However, using similarity
search produces a hit because the acronym is not too badly misspelled and is
close enough.
The Postgres that comes packaged with |Gel| (since |EdgeDB| 4.0+) server
includes ``pg_trgm``, as does Gel Cloud. It you are using a separate
Postgres backend, you will need to arrange for it to be installed.
To activate this functionality you can use the :ref:`extension
<ref_datamodel_extensions>` mechanism:
.. code-block:: sdl
using extension pg_trgm;
That will give you access to the ``ext::pg_trgm`` module where you may find
the following functions:
.. list-table::
:class: funcoptable
* - :eql:func:`ext::pg_trgm::similarity`
- :eql:func-desc:`ext::pg_trgm::similarity`
* - :eql:func:`ext::pg_trgm::similarity_dist`
- :eql:func-desc:`ext::pg_trgm::similarity_dist`
* - :eql:func:`ext::pg_trgm::similar`
- :eql:func-desc:`ext::pg_trgm::similar`
* - :eql:func:`ext::pg_trgm::word_similarity`
- :eql:func-desc:`ext::pg_trgm::word_similarity`
* - :eql:func:`ext::pg_trgm::word_similarity_dist`
- :eql:func-desc:`ext::pg_trgm::word_similarity_dist`
* - :eql:func:`ext::pg_trgm::word_similar`
- :eql:func-desc:`ext::pg_trgm::word_similar`
* - :eql:func:`ext::pg_trgm::strict_word_similarity`
- :eql:func-desc:`ext::pg_trgm::strict_word_similarity`
* - :eql:func:`ext::pg_trgm::strict_word_similarity_dist`
- :eql:func-desc:`ext::pg_trgm::strict_word_similarity_dist`
* - :eql:func:`ext::pg_trgm::strict_word_similar`
- :eql:func-desc:`ext::pg_trgm::strict_word_similar`
In addition to the functions this extension has two indexes that speed up
queries that involve similarity searches: ``ext::pg_trgm::gin`` and
``ext::pg_trgm::gist``.
.. _ref_ext_pgtrgm_config:
Configuration
^^^^^^^^^^^^^
This extension also adds a few configuration options to control some of the
similarity search behavior:
.. code-block:: sdl
type Config extending cfg::ConfigObject {
required similarity_threshold: float32;
required word_similarity_threshold: float32;
required strict_word_similarity_threshold: float32;
}
All of the configuration parameters have to take values between 0 and 1.
The ``similarity_threshold`` sets the current similarity threshold that is
used by :eql:func:`ext::pg_trgm::similar` (default is 0.3).
The ``word_similarity_threshold`` sets the current word similarity threshold
that is used by :eql:func:`ext::pg_trgm::word_similar` (default is 0.6).
The ``strict_word_similarity_threshold`` sets the current strict word
similarity threshold that is used by
:eql:func:`ext::pg_trgm::strict_word_similar` (default is 0.5).
------------
.. eql:function:: ext::pg_trgm::similarity(a: str, b: str) -> float32
Computes how similar two strings are.
The result is always a value between 0 and 1, where 0 indicates no
similarity and 1 indicates the strings are identical.
.. code-block:: edgeql-repl
db> select ext::pg_trgm::similarity('cat', 'dog');
{0}
db> select ext::pg_trgm::similarity('cat', 'cart');
{0.28571427}
db> select ext::pg_trgm::similarity('cat', 'car');
{0.33333337}
db> select ext::pg_trgm::similarity('cat', 'cat');
{1}
------------
.. eql:function:: ext::pg_trgm::similarity_dist(a: str, b: str) -> float32
Computes how distant two strings are.
The distance between *a* and *b* is simply defined as ``1 -
ext::pg_trgm::similarity(a, b)``.
.. code-block:: edgeql-repl
db> select ext::pg_trgm::similarity_dist('cat', 'dog');
{1}
db> select ext::pg_trgm::similarity_dist('cat', 'cart');
{0.71428573}
db> select ext::pg_trgm::similarity_dist('cat', 'car');
{0.6666666}
db> select ext::pg_trgm::similarity_dist('cat', 'cat');
{0}
------------
.. eql:function:: ext::pg_trgm::similar(a: str, b: str) -> bool
Returns whether two strings are similar.
The result is ``true`` if the :eql:func:`ext::pg_trgm::similarity` between
the two strings is greater than the currently configured
:ref:`similarity_threshold <ref_ext_pgtrgm_config>`.
.. code-block:: edgeql-repl
db> select ext::pg_trgm::similar('cat', 'dog');
{false}
db> select ext::pg_trgm::similar('cat', 'cart');
{false}
db> select ext::pg_trgm::similar('cat', 'car');
{true}
db> select ext::pg_trgm::similar('cat', 'cat');
{true}
------------
.. eql:function:: ext::pg_trgm::word_similarity(a: str, b: str) -> float32
Returns similarity between the first and any part of the second string.
The result is the greatest similarity between the set of
trigrams in *a* and any continuous extent of an ordered set
of trigrams in *b*.
.. code-block:: edgeql-repl
db> select ext::pg_trgm::word_similarity('cat', 'Lazy dog');
{0}
db> select ext::pg_trgm::word_similarity('cat', 'Dog in a car');
{0.5}
db> select ext::pg_trgm::word_similarity('cat', 'Dog catastrophe');
{0.75}
db> select ext::pg_trgm::word_similarity('cat', 'Lazy dog and cat');
{1}
------------
.. eql:function:: ext::pg_trgm::word_similarity_dist(a: str, b: str) \
-> float32
Returns distance between the first and any part of the second string.
The distance between *a* and *b* is simply defined as ``1 -
ext::pg_trgm::word_similarity(a, b)``.
.. code-block:: edgeql-repl
db> select ext::pg_trgm::word_similarity_dist('cat', 'Lazy dog');
{1}
db> select ext::pg_trgm::word_similarity_dist('cat', 'Dog in a car');
{0.5}
db> select ext::pg_trgm::word_similarity_dist('cat', 'Dog catastrophe');
{0.25}
db> select ext::pg_trgm::word_similarity_dist('cat', 'Lazy dog and cat');
{0}
------------
.. eql:function:: ext::pg_trgm::word_similar(a: str, b: str) -> bool
Returns whether the first string is similar to any part of the second.
The result is ``true`` if the :eql:func:`ext::pg_trgm::word_similarity`
between the two strings is greater than the currently configured
:ref:`word_similarity_threshold <ref_ext_pgtrgm_config>`.
.. code-block:: edgeql-repl
db> select ext::pg_trgm::word_similar('cat', 'Lazy dog');
{false}
db> select ext::pg_trgm::word_similar('cat', 'Dog in a car');
{false}
db> select ext::pg_trgm::word_similar('cat', 'Dog catastrophe');
{true}
db> select ext::pg_trgm::word_similar('cat', 'Lazy dog and cat');
{true}
------------
.. eql:function:: ext::pg_trgm::strict_word_similarity(a: str, b: str) \
-> float32
Same as ``word_similarity``, but with stricter boundaries.
This works much like :eql:func:`ext::pg_trgm::word_similarity`, but also
forces the match within *b* to happen at word boundaries.
.. code-block:: edgeql-repl
db> select ext::pg_trgm::strict_word_similarity('cat', 'Lazy dog');
{0}
db> select ext::pg_trgm::strict_word_similarity('cat', 'Dog in a car');
{0.5}
db> select ext::pg_trgm::strict_word_similarity(
... 'cat', 'Dog catastrophy');
{0.23076922}
db> select ext::pg_trgm::strict_word_similarity(
... 'cat', 'Lazy dog and cat');
{1}
------------
.. eql:function:: ext::pg_trgm::strict_word_similarity_dist(a: str, b: str) \
-> float32
Same as ``word_similarity_dist``, but with stricter boundaries.
This works much like :eql:func:`ext::pg_trgm::word_similarity_dist`, but
also forces the match within *b* to happen at word boundaries.
.. code-block:: edgeql-repl
db> select ext::pg_trgm::strict_word_similarity_dist(
... 'cat', 'Lazy dog');
{1}
db> select ext::pg_trgm::strict_word_similarity_dist(
... 'cat', 'Dog in a car');
{0.5}
db> select ext::pg_trgm::strict_word_similarity_dist(
... 'cat', 'Dog catastrophy');
{0.7692308}
db> select ext::pg_trgm::strict_word_similarity_dist(
... 'cat', 'Lazy dog and cat');
{0}
------------
.. eql:function:: ext::pg_trgm::strict_word_similar(a: str, b: str) -> bool
Same as ``word_similar``, but with stricter boundaries.
This works much like :eql:func:`ext::pg_trgm::word_similar`, but
also forces the match within *b* to happen at word boundaries.
.. code-block:: edgeql-repl
db> select ext::pg_trgm::strict_word_similar(
... 'cat', 'Lazy dog');
{false}
db> select ext::pg_trgm::strict_word_similar(
... 'cat', 'Lazy dog');
{false}
db> select ext::pg_trgm::strict_word_similar(
... 'cat', 'Dog catastrophy');
{false}
db> select ext::pg_trgm::strict_word_similar(
... 'cat', 'Lazy dog and cat');
{true}
================================================================================
.. File: pg_unaccent.rst
.. versionadded:: 4.0
.. _ref_ext_pgunaccent:
================
ext::pg_unaccent
================
This extension provides a dictionary for removing accents (diacritic signs) from
text.
.. code-block:: edgeql-repl
db> select ext::pg_unaccent::unaccent('Hôtel de la Mer');
{'Hotel de la Mer'}
To activate this functionality you can use the :ref:`extension
<ref_datamodel_extensions>` mechanism:
.. code-block:: sdl
using extension pg_unaccent;
That will give you access to the ``ext::pg_unaccent`` module where you may find
the function ``unaccent``.
PostgreSQL extension ``unaccent`` also supports creating dictionaries that are
used by other PostgreSQL text search, such as ``to_tsvector``. Gel extension
currently does not support creating such dictionaries, but one can use the
``unaccent`` function to achieve the same effect:
.. code-block:: sdl
type Post {
title: str;
index fts::index on ((
fts::with_options(
ext::pg_unaccent::unaccent(.title),
language := fts::Language.fra
),
));
};
.. code-block:: edgeql-repl
db> select fts::search(
... Post,
... ext::pg_unaccent::unaccent('Hôtel'),
... language := 'eng',
... ).object.title;
{'Hôtel de la Mer'}
================================================================================
.. File: pgcrypto.rst
.. versionadded:: 4.0
.. _ref_ext_pgcrypto:
=============
ext::pgcrypto
=============
This extension provides tools for your hashing and encrypting needs.
The Postgres that comes packaged with the |Gel| (since |EdgeDB| 4.0) server
includes ``pgcrypto``, as does Gel Cloud. It you are using a separate
Postgres backend, you will need to arrange for it to be installed.
To activate this functionality you can use the :ref:`extension
<ref_datamodel_extensions>` mechanism:
.. code-block:: sdl
using extension pgcrypto;
That will give you access to the ``ext::pgcrypto`` module where you may find
the following functions:
.. list-table::
:class: funcoptable
* - :eql:func:`ext::pgcrypto::digest`
- :eql:func-desc:`ext::pgcrypto::digest`
* - :eql:func:`ext::pgcrypto::hmac`
- :eql:func-desc:`ext::pgcrypto::hmac`
* - :eql:func:`ext::pgcrypto::gen_salt`
- :eql:func-desc:`ext::pgcrypto::gen_salt`
* - :eql:func:`ext::pgcrypto::crypt`
- :eql:func-desc:`ext::pgcrypto::crypt`
------------
.. eql:function:: ext::pgcrypto::digest(data: str, type: str) -> bytes
ext::pgcrypto::digest(data: bytes, type: str) -> bytes
Computes a hash of the *data* using the specified algorithm.
The *data* may come as a :eql:type:`str` or :eql:type:`bytes`. The value
of *type* argument determines the hashing algorithm that will be used.
Valid algorithms are: ``md5``, ``sha1``, ``sha224``, ``sha256``,
``sha384`` and ``sha512``. Also, any digest algorithm OpenSSL supports is
automatically picked up as well.
The result is always a binary hash.
.. code-block:: edgeql-repl
db> select ext::pgcrypto::digest('encrypt this', 'sha1');
{b'\x05\x82\xd8YLF\xe7\xd4\x12\x91\n\xdb$\xf1!v\xf9\xd4\x89\xc4'}
db> select ext::pgcrypto::digest(b'encrypt this', 'md5');
{b'\x15\xd6\x14y\xcb\xf2"\xa1+Z]8\xf8\xcf\x0c['}
------------
.. eql:function:: ext::pgcrypto::hmac(data: str, key: str, type: str) \
-> bytes
ext::pgcrypto::hmac(data: bytes, key: bytes, type: str) \
-> bytes
Computes a hashed MAC for *data* using *key* and the specified algorithm.
The *data* may come as a :eql:type:`str` or :eql:type:`bytes`. The *key*
type must match the *data* type. The value of *type* argument determines
the hashing algorithm that will be used. Valid algorithms are: ``md5``,
``sha1``, ``sha224``, ``sha256``, ``sha384`` and ``sha512``. Also, any
digest algorithm OpenSSL supports is automatically picked up as well.
The result is always a binary hash.
The main difference between :eql:func:`ext::pgcrypto::digest` and this
function is that it's impossible to recalculate the hash without the key.
.. code-block:: edgeql-repl
db> select ext::pgcrypto::hmac('encrypt this', 'my key', 'sha1');
{b'\x01G\x12\xb7\xe76H\x8b\xa4T1\x0fj\x87\xdf\x86n\x8f\xed\x15'}
db> select ext::pgcrypto::hmac(b'encrypt this', b'my key', 'md5');
{b'\xa9{\xc7\x9e\xc9"7e\xab\x83\xeb\x0c\xde\x02Nn'}
------------
.. eql:function:: ext::pgcrypto::gen_salt() -> str
ext::pgcrypto::gen_salt(type: str) -> str
ext::pgcrypto::gen_salt(type: str, iter_count: int64) -> str
Generates a new random salt string.
When generating the salt string *type* may be specified. Valid salt types
are: ``des``, ``xdes``, ``md5``, and ``bf`` (default).
.. code-block:: edgeql-repl
db> select ext::pgcrypto::gen_salt();
{'$2a$06$5D2rBj3UY5/UYvPIUNILvu'}
db> select ext::pgcrypto::gen_salt('des');
{'o9'}
db> select ext::pgcrypto::gen_salt('xdes');
{'_J9..efC8'}
The *iter_count* specifies the number of iterations for algorithms that
allow iterations (``xdes`` and ``bf``). The ``xdes`` algorithm has an
additional requirement that *iter_count* must be odd. The higher the
iteration count the longer it takes to compute the hash and therefore it
also takes longer to break the encryption. However, if the count is too
high, it can take impractically long.
.. code-block:: edgeql-repl
db> select ext::pgcrypto::gen_salt('bf', 10);
{'$2a$10$fAQS9/UKS42OI.ftjHkj2O'}
db> select ext::pgcrypto::gen_salt('xdes', 5);
{'_3...oN2c'}
------------
.. eql:function:: ext::pgcrypto::crypt(password: str, salt: str) -> str
Calculates a crypt(3)-style hash of password.
Typically you would use :eql:func:`ext::pgcrypto::gen_salt` to generate a
salt value for a new password:
.. code-block:: edgeql-repl
db> with module ext::pgcrypto
... select crypt('new password', gen_salt('des'));
{'0ddkJUiOnUFq6'}
To check the password against a stored encrypted value use the hash value
itself as salt and see if the result matches:
.. code-block:: edgeql-repl
db> with hash := '0ddkJUiOnUFq6'
... select hash = ext::pgcrypto::crypt(
... 'new password',
... hash,
... );
{true}
================================================================================
.. File: pgvector.rst
.. _ref_ext_pgvector:
=============
ext::pgvector
=============
This can be used to store and efficiently retrieve text embeddings,
such as those produced by OpenAI.
The Postgres that comes packaged with the Gel server includes
``pgvector``, as does Gel Cloud. It you are using a separate
Postgres backend, you will need to arrange for it to be installed.
To activate this new functionality you can use the :ref:`extension
<ref_datamodel_extensions>` mechanism:
.. code-block:: sdl
using extension pgvector;
That will give you access to the ``ext::pgvector`` module where you may find
the ``ext::pgvector::vector`` type as well as the following functions:
* ``euclidean_distance(a: vector, b: vector) -> std::float64``
* ``neg_inner_product(a: vector, b: vector) -> std::float64``
* ``cosine_distance(a: vector, b: vector) -> std::float64``
* ``euclidean_norm(a: vector, b: vector) -> std::float64``
You also get access to the following three indexes, each corresponding to one
of the vector distance functions:
* ``index ivfflat_euclidean(named only lists: int64)``
* ``index ivfflat_ip(named only lists: int64)``
* ``index ivfflat_cosine(named only lists: int64)``
.. versionadded:: 5.0
``ext::pgvector`` now also includes Hierarchical Navigable Small Worlds
(HNSW) indexes:
* ``index hnsw_euclidean``
* ``index hnsw_ip``
* ``index hnsw_cosine``
When defining a new type, you can now add vector properties. However, in order
to be able to use indexes, the vectors in question need to be of fixed
length. This can be achieved by creating a custom scalar ``extending`` the
vector and specifying the desired length in angle brackets:
.. code-block:: sdl
scalar type v3 extending ext::pgvector::vector<3>;
type Item {
embedding: v3
}
To populate your data, you can cast an array of any of the numeric types into
``ext::pgvector::vector`` or simply assign that array directly:
.. code-block:: edgeql-repl
gel> insert Item {embedding := <v3>[1.2, 3, 4.5]};
{default::Item {id: f119d64e-0995-11ee-8804-ff8cd739d8b7}}
gel> insert Item {embedding := [-0.1, 7, 0]};
{default::Item {id: f410c844-0995-11ee-8804-176f28167dd1}}
You can also cast the vectors into an ``array<float32>>``:
.. code-block:: edgeql-repl
gel> select <array<float32>>Item.embedding;
{[1.2, 3, 4.5], [-0.1, 7, 0]}
You can query the nearest neighbour by ordering based on
``euclidean_distance``:
.. code-block:: edgeql-repl
gel> select Item {*}
.... order by ext::pgvector::euclidean_distance(
.... .embedding, <v3>[3, 1, 2])
.... empty last
.... limit 1;
{
default::Item {
id: f119d64e-0995-11ee-8804-ff8cd739d8b7,
embedding: [1.2, 3, 4.5],
},
}
You can also just retrieve all results within a certain distance:
.. code-block:: edgeql-repl
gel> select Item {*}
.... filter ext::pgvector::euclidean_distance(
.... .embedding, <v3>[3, 1, 2]) < 5;
{
default::Item {
id: f119d64e-0995-11ee-8804-ff8cd739d8b7,
embedding: [1.2, 3, 4.5],
},
}
The functions mentioned earlier can be used to calculate various useful vector
distances:
.. code-block:: edgeql-repl
gel> select Item {
.... id,
.... distance := ext::pgvector::euclidean_distance(
.... .embedding, <v3>[3, 1, 2]),
.... inner_product := -ext::pgvector::neg_inner_product(
.... .embedding, <v3>[3, 1, 2]),
.... cosine_similarity := 1 - ext::pgvector::cosine_distance(
.... .embedding, <v3>[3, 1, 2]),
.... };
{
default::Item {
id: f119d64e-0995-11ee-8804-ff8cd739d8b7,
distance: 3.6728735110725803,
inner_product: 15.600000143051147,
cosine_similarity: 0.7525964057358976,
},
default::Item {
id: f410c844-0995-11ee-8804-176f28167dd1,
distance: 7.043436619202443,
inner_product: 6.699999988079071,
cosine_similarity: 0.2557810894509498,
},
}
To speed up queries three slightly different IVFFlat indexes can be added to
the type, each of them optimizing one of the distance calculating functions:
.. code-block:: sdl
type Item {
embedding: v3;
index ext::pgvector::ivfflat_euclidean(lists := 10) on (.embedding);
index ext::pgvector::ivfflat_ip(lists := 10) on (.embedding);
index ext::pgvector::ivfflat_cosine(lists := 10) on (.embedding);
}
In order to take advantage of an index, your query must:
1) Use ``order by`` using the function that corresponds to the index
2) Specify ``empty last`` as part of the ``order by`` clause
3) Provide a ``limit`` clause specifying how many results to return
Note that unlike normal indexes, hitting an IVFFlat index changes the
query behavior: it does a (hopefully fast) approximate search instead
of (usually slow) exact one.
As per the `pgvector <pgvector_>`_ recommendations, the keys to achieving good
recall are:
1) Create the index after the table has some data
2) Choose an appropriate number of lists - a good place to start is objects /
1000 for up to 1M objects and sqrt(objects) for over 1M objects
3) When querying, specify an appropriate number of probes (higher is better
for recall, lower is better for speed) - a good place to start is sqrt(
lists). The number of probes can be set by ``ext::pgvector::set_probes()``
function.
Use our newly introduced ``analyze`` feature to debug query performance and
make sure that the indexes are being used.
The ``ext::pgvector::set_probes()`` function configures the number of
probes to use in approximate index searches. It is scoped to the
current transaction, so if you call it from within a transaction, it
persists until the transaction is finished. The recommended way to use
it, however, is to take advantage of the implicit transactions provided
by multi-statement queries:
.. code-block:: python
result = client.query("""
select set_probes(10);
select Item { id, name }
order by ext::pgvector::euclidean_distance(
.embedding, <v3>$vector)
empty last
limit 1;
""", vector=vector)
.. versionadded:: 5.0
We have updated the mechanism for tuning all of the indexes provided in
this extension. The ``probes`` (for IVFFlat) and ``ef_search`` (for HNSW)
parameters can now be accessed via the ``ext::pgvector::Config`` object.
Examine the ``extensions`` link of the ``cfg::Config`` object to check the
current config values:
.. code-block:: edgeql-repl
db> select cfg::Config.extensions[is ext::pgvector::Config]{*};
{
ext::pgvector::Config {
id: 12b5c70f-0bb8-508a-845f-ca3d41103b6f,
probes: 1,
ef_search: 40,
},
}
.. note::
In order to see the specific extension config properties you need to
use the type filter :eql:op:`[is ext::pgvector::Config] <isintersect>`
Update the value using the ``configure session`` or the ``configure current
branch`` command depending on the scope you prefer:
.. code-block:: edgeql-repl
db> configure session
... set ext::pgvector::Config::probes := 5;
OK: CONFIGURE SESSION
You may also restore the default config value using ``configure session
reset`` if you set it on the session or ``configure current branch reset``
if you set it on the branch:
.. code-block:: edgeql-repl
db> configure session reset ext::pgvector::Config::probes;
OK: CONFIGURE SESSION
.. _pgvector:
https://github.com/pgvector/pgvector
================================================================================
.. File: range.rst
.. _ref_std_range:
======
Ranges
======
:edb-alt-title: Range Functions and Operators
Ranges represent some interval of values. The intervals can include or exclude
their boundaries or can even omit one or both boundaries. Only some scalar
types have corresponding range types:
- ``range<int32>``
- ``range<int64>``
- ``range<float32>``
- ``range<float64>``
- ``range<decimal>``
- ``range<datetime>``
- ``range<cal::local_datetime>``
- ``range<cal::local_date>``
Constructing ranges
^^^^^^^^^^^^^^^^^^^
There's a special :eql:func:`range` constructor function for making range
values. This is a little different from how scalars, arrays and tuples are
created typically in Gel.
For example:
.. code-block:: edgeql-repl
db> select range(1, 10);
{range(1, 10)}
db> select range(2.2, 3.3);
{range(2.2, 3.3)}
Broadly there are two kinds of ranges: :eql:type:`discrete <anydiscrete>` and
:eql:type:`contiguous <anycontiguous>`. The discrete ranges are
``range<int32>``, ``range<int64>``, and ``range<cal::local_date>``. All ranges
over discrete types get normalized such that the lower bound is included
(if present) and the upper bound is excluded:
.. code-block:: edgeql-repl
db> select range(1, 10) = range(1, 9, inc_upper := true);
{true}
db> select range(1, 10) = range(0, 10, inc_lower := false);
{true}
Ranges over contiguous types don't have the same normalization mechanism
because the underlying types don't have granularity which could be used to
easily include or exclude a boundary value.
Sometimes a range cannot contain any values, this is called an *empty* range.
These kinds of ranges can arise from performing various operations on them,
but they can also be constructed. There are basically two equivalent ways of
constructing an *empty* range. It can be explicitly constructed by providing
the same upper and lower bounds and specifying that at least one of them is
not *inclusive* (which is the default for all range constructors):
.. code-block:: edgeql-repl
db> select range(1, 1);
{range({}, inc_lower := false, empty := true)}
Alternatively, it's possible to specify ``{}`` as a boundary and also provide
the ``empty := true`` named-only argument. If the empty set is provided as a
literal, it also needs to have a type cast, to specify which type of the range
is being constructed:
.. code-block:: edgeql-repl
db> select range(<int64>{}, empty := true);
{range({}, inc_lower := false, empty := true)}
Since empty ranges contain no values, they are all considered to be equal to
each other (as long as the types are compatible):
.. code-block:: edgeql-repl
db> select range(1, 1) = range(<int64>{}, empty := true);
{true}
db> select range(1, 1) = range(42.0, 42.0);
{true}
db> select range(1, 1) = range(<cal::local_date>{}, empty := true);
error: InvalidTypeError: operator '=' cannot be applied to operands of
type 'range<std::int64>' and 'range<cal::local_date>'
┌─ query:1:8
│
1 │ select range(1, 1) = range(<cal::local_date>{}, empty := true);
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Consider using an explicit type cast or a conversion function.
JSON representation
^^^^^^^^^^^^^^^^^^^
Much like :ref:`arrays<ref_std_array>` and :ref:`tuples<ref_std_tuple>`, the
range types cannot be directly cast to a :eql:type:`str`, but instead can be
cast into a :eql:type:`json` structure:
.. code-block:: edgeql-repl
db> select <json>range(1, 10);
{"inc_lower": true, "inc_upper": false, "lower": 1, "upper": 10}
It's also possible to cast in the other direction - from :eql:type:`json` to a
specific range type:
.. code-block:: edgeql-repl
db> select <range<int64>>to_json('{
... "lower": 1,
... "inc_lower": true,
... "upper": 10,
... "inc_upper": false
... }');
{range(1, 10)}
Empty ranges have a shorthand :eql:type:`json` representation:
.. code-block:: edgeql-repl
db> select <json>range(<int64>{}, empty := true);
{"empty": true}
When casting from :eql:type:`json` to an empty range, all other fields may be
omitted, but if they are present, they must be consistent with an empty range:
.. code-block:: edgeql-repl
db> select <range<int64>>to_json('{"empty": true}');
{range({}, inc_lower := false, empty := true)}
db> select <range<int64>>to_json('{
... "lower": 1,
... "inc_lower": true,
... "upper": 1,
... "inc_upper": false
... }');
{range({}, inc_lower := false, empty := true)}
db> select <range<int64>>to_json('{
... "lower": 1,
... "inc_lower": true,
... "upper": 1,
... "inc_upper": false,
... "empty": true
... }');
{range({}, inc_lower := false, empty := true)}
db> select <range<int64>>to_json('{
... "lower": 1,
... "inc_lower": true,
... "upper": 2,
... "inc_upper": false,
... "empty": true
... }');
gel error: InvalidValueError: conflicting arguments in range
constructor: "empty" is ``true`` while the specified bounds suggest
otherwise
.. note::
When casting from :eql:type:`json` to a range the ``lower`` and ``upper``
fields are optional, but the *inclusivity* fields ``inc_lower`` and
``inc_upper`` are *mandatory*. This is to address the fact that whether the
range boundaries are included by default can vary based on system or context
and being explicit avoids subtle errors. The only exception to this are
empty ranges that can have just the ``"empty": true`` field.
.. _ref_std_multirange:
Multiranges
^^^^^^^^^^^
.. versionadded:: 4.0
Intermittent availability or ranges with gaps can be naturally represented by
a set of ranges. However, using a :eql:func:`multirange` for this purpose is
even better. At its core a multirange is a set of ranges packaged together
so that it's easy to perform range operations on the whole set:
.. code-block:: edgeql-repl
db> select multirange([range(1, 5), range(8,10)]);
{[range(1, 5), range(8, 10)]}
db> select contains(multirange([range(1, 5), range(8,10)]), 9);
true
Another advantage of a multirange is that its components are always
automatically ordered and normalized to be non-overlapping, even if it's
constructed from an array of ranges that don't satisfy either of these
conditions:
.. code-block:: edgeql-repl
db> select multirange([range(8, 10), range(1, 4), range(2, 5)]);
{[range(1, 5), range(8, 10)]}
Multiranges are compatible with ranges for the purpose of most operations,
making it more conveninet to manipulate them whenever you have more than one
range to work with:
.. code-block:: edgeql-repl
db> select multirange([range(8, 10)]) + range(1, 5) - range(3, 4);
{[range(1, 3), range(4, 5), range(8, 10)]}
Functions and operators
^^^^^^^^^^^^^^^^^^^^^^^
.. list-table::
:class: funcoptable
* - :eql:op:`range \< range <rangelt>`
- :eql:op-desc:`rangelt`
* - :eql:op:`range \> range <rangegt>`
- :eql:op-desc:`rangegt`
* - :eql:op:`range \<= range <rangelteq>`
- :eql:op-desc:`rangelteq`
* - :eql:op:`range \>= range <rangegteq>`
- :eql:op-desc:`rangegteq`
* - :eql:op:`range + range <rangeplus>`
- :eql:op-desc:`rangeplus`
* - :eql:op:`range - range <rangeminus>`
- :eql:op-desc:`rangeminus`
* - :eql:op:`range * range <rangemult>`
- :eql:op-desc:`rangemult`
* - :eql:func:`range`
- :eql:func-desc:`range`
* - :eql:func:`range_get_lower`
- :eql:func-desc:`range_get_lower`
* - :eql:func:`range_get_upper`
- :eql:func-desc:`range_get_upper`
* - :eql:func:`range_is_inclusive_lower`
- :eql:func-desc:`range_is_inclusive_lower`
* - :eql:func:`range_is_inclusive_upper`
- :eql:func-desc:`range_is_inclusive_upper`
* - :eql:func:`range_is_empty`
- :eql:func-desc:`range_is_empty`
* - :eql:func:`range_unpack`
- :eql:func-desc:`range_unpack`
* - :eql:func:`contains`
- Check if an element or a range is within another range.
* - :eql:func:`overlaps`
- :eql:func-desc:`overlaps`
* - :eql:func:`adjacent`
- :eql:func-desc:`adjacent`
* - :eql:func:`strictly_above`
- :eql:func-desc:`strictly_above`
* - :eql:func:`strictly_below`
- :eql:func-desc:`strictly_below`
* - :eql:func:`bounded_above`
- :eql:func-desc:`bounded_above`
* - :eql:func:`bounded_below`
- :eql:func-desc:`bounded_below`
* - :eql:func:`multirange`
- :eql:func-desc:`multirange`
* - :eql:func:`multirange_unpack`
- :eql:func-desc:`multirange_unpack`
Reference
^^^^^^^^^
.. eql:operator:: rangelt: range<anypoint> < range<anypoint> -> bool
multirange<anypoint> < multirange<anypoint> -> bool
:index: <, multirange, less than, before, comparison, compare
One range or multirange is before the other.
Returns ``true`` if the lower bound of the first range or multirange is
smaller than the lower bound of the second range or multirange. The
unspecified lower bound is considered to be smaller than any specified
lower bound. If the lower bounds are equal then the upper bounds are
compared. Unspecified upper bound is considered to be greater than any
specified upper bound.
.. code-block:: edgeql-repl
db> select range(1, 10) < range(2, 5);
{true}
db> select range(1, 10) < range(1, 15);
{true}
db> select range(1, 10) < range(1);
{true}
db> select range(1, 10) < range(<int64>{}, 10);
{false}
db> select multirange([range(2, 4), range(5, 7)]) <
... multirange([range(7, 10), range(20)]);
{true}
An empty range is considered to come before any non-empty range.
.. code-block:: edgeql-repl
db> select range(1, 10) < range(10, 10);
{false}
db> select range(1, 10) < range(<int64>{}, empty := true);
{false}
db> select multirange(<array<range<int64>>>[]) <
... multirange([range(7, 10), range(20)]);
{true}
This is also how the ``order by`` clauses compares ranges.
----------
.. eql:operator:: rangegt: range<anypoint> > range<anypoint> -> bool
multirange<anypoint> > multirange<anypoint> -> bool
:index: >, multirange, greater than, after, comparison, compare
One range or multirange is after the other.
Returns ``true`` if the lower bound of the first range or multirange is
greater than the lower bound of the second range or multirange. The
unspecified lower bound is considered to be smaller than any specified
lower bound. If the lower bounds are equal then the upper bounds are
compared. Unspecified upper bound is considered to be greater than any
specified upper bound.
.. code-block:: edgeql-repl
db> select range(1, 10) > range(2, 5);
{false}
db> select range(1, 10) > range(1, 5);
{true}
db> select range(1, 10) > range(1);
{false}
db> select range(1, 10) > range(<int64>{}, 10);
{true}
db> select multirange([range(2, 4), range(5, 7)]) >
... multirange([range(7, 10), range(20)]);
{false}
An empty range is considered to come before any non-empty range.
.. code-block:: edgeql-repl
db> select range(1, 10) > range(10, 10);
{true}
db> select range(1, 10) > range(<int64>{}, empty := true);
{true}
db> select multirange(<array<range<int64>>>[]) >
... multirange([range(7, 10), range(20)]);
{false}
This is also how the ``order by`` clauses compares ranges.
----------
.. eql:operator:: rangelteq: range<anypoint> <= range<anypoint> -> bool
multirange<anypoint> <= multirange<anypoint> -> bool
:index: <=, multirange, less than or equal, before, comparison, compare
One range or multirange is before or same as the other.
Returns ``true`` if the ranges or multiranges are identical or if the
lower bound of the first one is smaller than the lower bound of the second
one. The unspecified lower bound is considered to be smaller than any
specified lower bound. If the lower bounds are equal then the upper bounds
are compared. Unspecified upper bound is considered to be greater than any
specified upper bound.
.. code-block:: edgeql-repl
db> select range(1, 10) <= range(1, 10);
{true}
db> select range(1, 10) <= range(2, 5);
{true}
db> select range(1, 10) <= range(1, 15);
{true}
db> select range(1, 10) <= range(1);
{true}
db> select range(1, 10) <= range(<int64>{}, 10);
{false}
db> select multirange([range(2, 4), range(5, 7)]) <=
... multirange([range(7, 10), range(20)]);
{true}
db> select multirange([range(2, 4), range(5, 7)]) <=
... multirange([range(5, 7), range(2, 4)]);
{true}
An empty range is considered to come before any non-empty range.
.. code-block:: edgeql-repl
db> select range(1, 10) <= range(10, 10);
{false}
db> select range(1, 1) <= range(10, 10);
{true}
db> select range(1, 10) <= range(<int64>{}, empty := true);
{false}
db> select multirange(<array<range<int64>>>[]) <=
... multirange([range(7, 10), range(20)]);
{true}
This is also how the ``order by`` clauses compares ranges.
----------
.. eql:operator:: rangegteq: range<anypoint> >= range<anypoint> -> bool
multirange<anypoint> >= multirange<anypoint> -> bool
:index: >=, multirange, greater than or equal, after, comparison, compare
One range or multirange is after or same as the other.
Returns ``true`` if the ranges or multiranges are identical or if the
lower bound of the first one is greater than the lower bound of the second
one. The unspecified lower bound is considered to be smaller than any
specified lower bound. If the lower bounds are equal then the upper bounds
are compared. Unspecified upper bound is considered to be greater than any
specified upper bound.
.. code-block:: edgeql-repl
db> select range(1, 10) >= range(2, 5);
{false}
db> select range(1, 10) >= range(1, 10);
{true}
db> select range(1, 10) >= range(1, 5);
{true}
db> select range(1, 10) >= range(1);
{false}
db> select range(1, 10) >= range(<int64>{}, 10);
{true}
db> select multirange([range(2, 4), range(5, 7)]) >=
... multirange([range(7, 10), range(20)]);
{false}
db> select multirange([range(2, 4), range(5, 7)]) >=
... multirange([range(5, 7), range(2, 4)]);
{true}
An empty range is considered to come before any non-empty range.
.. code-block:: edgeql-repl
db> select range(1, 10) >= range(10, 10);
{true}
db> select range(1, 1) >= range(10, 10);
{true}
db> select range(1, 10) >= range(<int64>{}, empty := true);
{true}
db> select multirange(<array<range<int64>>>[]) >=
... multirange([range(7, 10), range(20)]);
{false}
This is also how the ``order by`` clauses compares ranges.
.. eql:operator:: rangeplus: range<anypoint> + range<anypoint> \
-> range<anypoint>
multirange<anypoint> + multirange<anypoint> \
-> multirange<anypoint>
:index: +, multirange, plus, addition, union
Range or multirange union.
Find the union of two ranges as long as the result is a single range
without any discontinuities inside.
.. code-block:: edgeql-repl
db> select range(1, 10) + range(5, 15);
{range(1, 15)}
db> select range(1, 10) + range(5);
{range(1, {})}
If one of the arguments is a multirange, find the union and normalize the
result as a multirange.
.. code-block:: edgeql-repl
db> select range(1, 3) + multirange([
... range(7, 10), range(20),
... ]);
{[range(1, 3), range(7, 10), range(20, {})]}
db> select multirange([range(2, 4), range(5, 8)]) +
... multirange([range(6, 10), range(20)]);
{[range(2, 4), range(5, 10), range(20, {})]}
----------
.. eql:operator:: rangeminus: range<anypoint> - range<anypoint> \
-> range<anypoint>
multirange<anypoint> - multirange<anypoint> \
-> multirange<anypoint>
:index: -, multirange, minus, subtraction
Range or multirange subtraction.
Subtract one range from another. This is only valid if the resulting range
does not have any discontinuities inside.
.. code-block:: edgeql-repl
db> select range(1, 10) - range(5, 15);
{range(1, 5)}
db> select range(1, 10) - range(<int64>{}, 5);
{range(5, 10)}
db> select range(1, 10) - range(0, 15);
{range({}, inc_lower := false, empty := true)}
If one of the arguments is a multirange, treat both arguments as
multiranges and perform the multirange subtraction.
.. code-block:: edgeql-repl
db> select multirange([range(1, 10)]) -
... range(4, 6);
{[range(1, 4), range(6, 10)]}
db> select multirange([range(1, 10)]) -
... multirange([range(2, 3), range(5, 6), range(9)]);
{[range(1, 2), range(3, 5), range(6, 9)]}
db> select multirange([range(2, 3), range(5, 6), range(9, 10)]) -
... multirange([range(-10, 0), range(4, 8)]);
{[range(2, 3), range(9, 10)]}
----------
.. eql:operator:: rangemult: range<anypoint> * range<anypoint> \
-> range<anypoint>
multirange<anypoint> * multirange<anypoint> \
-> multirange<anypoint>
:index: \*, multirange, intersection
Range or multirnage intersection.
Find the intersection of two ranges or multiranges.
.. code-block:: edgeql-repl
db> select range(1, 10) * range(5, 15);
{range(5, 10)}
db> select range(1, 10) * range(-15, 15);
{range(1, 10)}
db> select range(1) * range(-15, 15);
{range(1, 15)}
db> select range(10) * range(<int64>{}, 1);
{range({}, inc_lower := false, empty := true)}
db> select multirange([range(1, 10)]) *
... multirange([range(0, 3), range(5, 6), range(9)]);
{[range(1, 3), range(5, 6), range(9, 10)]}
db> select multirange([range(2, 3), range(5, 6), range(9, 10)]) *
... multirange([range(-10, 0), range(4, 8)]);
{[range(5, 6)]}
----------
.. eql:function:: std::range(lower: optional anypoint = {}, \
upper: optional anypoint = {}, \
named only inc_lower: bool = true, \
named only inc_upper: bool = false, \
named only empty: bool = false) \
-> range<anypoint>
Construct a range.
Either one of *lower* or *upper* bounds can be set to ``{}`` to indicate
an unbounded interval.
By default the *lower* bound is included and the *upper* bound is excluded
from the range, but this can be controlled explicitly via the *inc_lower*
and *inc_upper* named-only arguments.
.. code-block:: edgeql-repl
db> select range(1, 10);
{range(1, 10)}
db> select range(1.5, 7.5, inc_lower := false);
{range(1.5, 7.5, inc_lower := false)}
Finally, an empty range can be created by using the *empty* named-only
flag. The first argument still needs to be passed as an ``{}`` so that the
type of the range can be inferred from it.
.. code-block:: edgeql-repl
db> select range(<int64>{}, empty := true);
{range({}, inc_lower := false, empty := true)}
----------
.. eql:function:: std::range_get_lower(r: range<anypoint>) \
-> optional anypoint
std::range_get_lower(r: multirange<anypoint>) \
-> optional anypoint
Return lower bound value.
Return the lower bound of the specified range or multirange.
.. code-block:: edgeql-repl
db> select range_get_lower(range(1, 10));
{1}
db> select range_get_lower(range(1.5, 7.5));
{1.5}
db> select range_get_lower(
... multirange([range(5, 10), range(2, 3)]));
{2}
----------
.. eql:function:: std::range_is_inclusive_lower(r: range<anypoint>) \
-> bool
std::range_is_inclusive_lower(r: multirange<anypoint>) \
-> bool
Check whether lower bound is inclusive.
Return ``true`` if the lower bound is inclusive and ``false`` otherwise.
If there is no lower bound, then it is never considered inclusive.
.. code-block:: edgeql-repl
db> select range_is_inclusive_lower(range(1, 10));
{true}
db> select range_is_inclusive_lower(
... range(1.5, 7.5, inc_lower := false));
{false}
db> select range_is_inclusive_lower(range(<int64>{}, 10));
{false}
db> select range_is_inclusive_lower(
... multirange([
... range(2, 3),
... range(5, 10),
... ])
... );
{true}
----------
.. eql:function:: std::range_get_upper(r: range<anypoint>) \
-> optional anypoint
std::range_get_upper(r: multirange<anypoint>) \
-> optional anypoint
Return upper bound value.
Return the upper bound of the specified range or multirange.
.. code-block:: edgeql-repl
db> select range_get_upper(range(1, 10));
{10}
db> select range_get_upper(range(1.5, 7.5));
{7.5}
db> select range_get_upper(
... multirange([range(5, 10), range(2, 3)]));
{10}
----------
.. eql:function:: std::range_is_inclusive_upper(r: range<anypoint>) \
-> bool
std::range_is_inclusive_upper(r: multirange<anypoint>) \
-> bool
Check whether upper bound is inclusive.
Return ``true`` if the upper bound is inclusive and ``false`` otherwise.
If there is no upper bound, then it is never considered inclusive.
.. code-block:: edgeql-repl
db> select range_is_inclusive_upper(range(1, 10));
{false}
db> select range_is_inclusive_upper(
... range(1.5, 7.5, inc_upper := true));
{true}
db> select range_is_inclusive_upper(range(1));
{false}
db> select range_is_inclusive_upper(
... multirange([
... range(2.0, 3.0),
... range(5.0, 10.0, inc_upper := true),
... ])
... );
{true}
----------
.. eql:function:: std::range_is_empty(val: range<anypoint>) \
-> bool
std::range_is_empty(val: multirange<anypoint>) \
-> bool
Check whether a range is empty.
Return ``true`` if the range or multirange contains no values and
``false`` otherwise.
.. code-block:: edgeql-repl
db> select range_is_empty(range(1, 10));
{false}
db> select range_is_empty(range(1, 1));
{true}
db> select range_is_empty(range(<int64>{}, empty := true));
{true}
db> select range_is_empty(multirange(<array<range<int64>>>[]));
{true}
db> select range_is_empty(multirange([range(1, 10)]));
{false}
----------
.. eql:function:: std::range_unpack(val: range<anydiscrete>) \
-> set of anydiscrete
std::range_unpack(val: range<anypoint>, step: anypoint) \
-> set of anypoint
Return values from a range.
For a range of discrete values this function when called without
indicating a *step* value simply produces a set of all the values within
the range, in order.
.. code-block:: edgeql-repl
db> select range_unpack(range(1, 10));
{1, 2, 3, 4, 5, 6, 7, 8, 9}
db> select range_unpack(range(
... <cal::local_date>'2022-07-01',
... <cal::local_date>'2022-07-10'));
{
<cal::local_date>'2022-07-01',
<cal::local_date>'2022-07-02',
<cal::local_date>'2022-07-03',
<cal::local_date>'2022-07-04',
<cal::local_date>'2022-07-05',
<cal::local_date>'2022-07-06',
<cal::local_date>'2022-07-07',
<cal::local_date>'2022-07-08',
<cal::local_date>'2022-07-09',
}
For any range type a *step* value can be specified. Then the values will
be picked from the range, starting at the lower boundary (skipping the
boundary value itself if it's not included in the range) and then
producing the next value by adding the *step* to the previous one.
.. code-block:: edgeql-repl
db> select range_unpack(range(1.5, 7.5), 0.7);
{1.5, 2.2, 2.9, 3.6, 4.3, 5, 5.7, 6.4}
db> select range_unpack(
... range(
... <cal::local_datetime>'2022-07-01T00:00:00',
... <cal::local_datetime>'2022-12-01T00:00:00'
... ),
... <cal::relative_duration>'25 days 5 hours');
{
<cal::local_datetime>'2022-07-01T00:00:00',
<cal::local_datetime>'2022-07-26T05:00:00',
<cal::local_datetime>'2022-08-20T10:00:00',
<cal::local_datetime>'2022-09-14T15:00:00',
<cal::local_datetime>'2022-10-09T20:00:00',
<cal::local_datetime>'2022-11-04T01:00:00',
}
----------
.. eql:function:: std::overlaps(l: range<anypoint>, r: range<anypoint>) \
-> bool
std::overlaps(l: multirange<anypoint>, \
r: multirange<anypoint>, \
) -> bool
Check whether ranges or multiranges overlap.
Return ``true`` if the ranges or multiranges have any elements in common
and ``false`` otherwise.
.. code-block:: edgeql-repl
db> select overlaps(range(1, 10), range(5));
{true}
db> select overlaps(range(1, 10), range(10));
{false}
db> select overlaps(
... multirange([
... range(1, 4), range(7),
... ]),
... multirange([
... range(-1, 2), range(8, 10),
... ]),
... );
{true}
db> select overlaps(
... multirange([
... range(1, 4), range(7),
... ]),
... multirange([
... range(-1, 1), range(5, 6),
... ]),
... );
{false}
----------
.. eql:function:: std::adjacent( \
l: range<anypoint>, \
r: range<anypoint>, \
) -> bool
std::adjacent( \
l: multirange<anypoint>, \
r: multirange<anypoint>, \
) -> bool
.. versionadded:: 4.0
Check whether ranges or multiranges share a boundary without overlapping.
.. code-block:: edgeql-repl
db> select adjacent(range(1, 3), range(3, 4));
{true}
db> select adjacent(range(1.0, 3.0), range(3.0, 4.0));
{true}
db> select adjacent(
... range(1.0, 3.0, inc_upper := true), range(3.0, 4.0));
{false}
db> select adjacent(
... multirange([
... range(2, 4), range(5, 7),
... ]),
... multirange([
... range(7, 10), range(20),
... ]),
... );
{true}
Since range values can be implicitly cast into multiranges, you can mix
the two types:
.. code-block:: edgeql-repl
db> select adjacent(
... range(7),
... multirange([
... range(1, 2), range(3, 7),
... ]),
... );
{true}
----------
.. eql:function:: std::strictly_above( \
l: range<anypoint>, \
r: range<anypoint>, \
) -> bool
std::strictly_above( \
l: multirange<anypoint>, \
r: multirange<anypoint>, \
) -> bool
.. versionadded:: 4.0
All values of the first range or multirange appear after the second.
.. code-block:: edgeql-repl
db> select strictly_above(
... range(7), range(1, 5)
... );
{true}
db> select strictly_above(
... range(3, 7), range(1, 5)
... );
{false}
db> select strictly_above(
... multirange([
... range(2, 4), range(5, 7),
... ]),
... multirange([
... range(-5, -2), range(-1, 1),
... ]),
... );
{true}
Since range values can be implicitly cast into multiranges, you can mix
the two types:
.. code-block:: edgeql-repl
db> select strictly_above(
... range(8),
... multirange([
... range(1, 2), range(3, 7),
... ]),
... );
{true}
----------
.. eql:function:: std::strictly_below( \
l: range<anypoint>, \
r: range<anypoint>, \
) -> bool
std::strictly_below( \
l: multirange<anypoint>, \
r: multirange<anypoint>, \
) -> bool
.. versionadded:: 4.0
All values of the first range or multirange appear before the second.
.. code-block:: edgeql-repl
db> select strictly_below(
... range(1, 3), range(7)
... );
{true}
db> select strictly_below(
... range(1, 7), range(3)
... );
{false}
db> select strictly_below(
... multirange([
... range(-1, 0), range(-5, -3),
... ]),
... multirange([
... range(1, 4), range(7),
... ]),
... );
{true}
Since range values can be implicitly cast into multiranges, you can mix
the two types:
.. code-block:: edgeql-repl
db> select strictly_below(
... range(-1, 0),
... multirange([
... range(1, 4), range(7),
... ]),
... );
{true}
----------
.. eql:function:: std::bounded_above( \
l: range<anypoint>, \
r: range<anypoint>, \
) -> bool
std::bounded_above( \
l: multirange<anypoint>, \
r: multirange<anypoint>, \
) -> bool
.. versionadded:: 4.0
The first argument is bounded above by the upper bound of the second.
.. code-block:: edgeql-repl
db> select bounded_above(
... range(1, 7), range(3, 7)
... );
{true}
db> select bounded_above(
... range(1, 7), range(3, 6)
... );
{false}
db> select bounded_above(
... range(1, 7), range(3)
... );
{true}
db> select bounded_above(
... multirange([
... range(-1, 0), range(5, 7),
... ]),
... multirange([
... range(1, 2), range(3, 7),
... ]),
... );
{true}
Since range values can be implicitly cast into multiranges, you can mix
the two types:
.. code-block:: edgeql-repl
db> select bounded_above(
... range(-1, 10),
... multirange([
... range(1, 4), range(7),
... ]),
... );
{true}
----------
.. eql:function:: std::bounded_below( \
l: range<anypoint>, \
r: range<anypoint>, \
) -> bool
std::bounded_below( \
l: multirange<anypoint>, \
r: multirange<anypoint>, \
) -> bool
.. versionadded:: 4.0
The first argument is bounded below by the lower bound of the second.
.. code-block:: edgeql-repl
db> select bounded_below(
... range(1, 7), range(3, 6)
... );
{false}
db> select bounded_below(
... range(1, 7), range(0, 6)
... );
{true}
db> select bounded_below(
... multirange([
... range(-1, 0), range(5, 7),
... ]),
... multirange([
... range(1, 2), range(3, 7),
... ]),
... );
{false}
Since range values can be implicitly cast into multiranges, you can mix
the two types:
.. code-block:: edgeql-repl
db> select bounded_below(
... range(5, 7),
... multirange([
... range(1, 2), range(3, 7),
... ]),
... );
{true}
----------
.. eql:function:: std::multirange(ranges: array<range<anypoint>>) \
-> multirange<anypoint>
.. versionadded:: 4.0
Construct a multirange.
Construct a multirange from the *ranges* array. Normalize the sub-ranges
so that they become ordered and non-overlapping.
.. code-block:: edgeql-repl
db> select multirange([range(8, 10), range(1, 4), range(2, 5)]);
{[range(1, 5), range(8, 10)]}
If either an empty array or an empty range is used to construct a
multirange, the resulting multirange will be empty. An empty multirange is
semantically similar to an empty range.
.. code-block:: edgeql-repl
db> with
... a := multirange(<array<range<int64>>>[]),
... b := multirange([range(<int64>{}, empty := true)]),
... c := range(<int64>{}, empty := true),
... select (a = b, b = c);
{(true, true)}
----------
.. eql:function:: std::multirange_unpack(val: multirange<anypoint>) \
-> set of range<anypoint>
.. versionadded:: 4.0
Returns the sub-ranges of a multirange as a set or ranges.
.. code-block:: edgeql-repl
db> select multirange_unpack(
... multirange([
... range(1, 4), range(7), range(3, 5)
... ]),
... );
{range(1, 5), range(7, {})}
db> select multirange_unpack(
... multirange(<array<range<int64>>>[]));
{}
================================================================================
.. File: sequence.rst
.. _ref_std_sequence:
=========
Sequences
=========
.. list-table::
:class: funcoptable
* - :eql:type:`sequence`
- Auto-incrementing sequence of :eql:type:`int64`.
* - :eql:func:`sequence_next`
- :eql:func-desc:`sequence_next`
* - :eql:func:`sequence_reset`
- :eql:func-desc:`sequence_reset`
----------
.. eql:type:: std::sequence
An auto-incrementing sequence of :eql:type:`int64`.
This type can be used to create auto-incrementing :ref:`properties
<ref_datamodel_props>`:
.. code-block:: sdl
scalar type TicketNo extending sequence;
type Ticket {
number: TicketNo {
constraint exclusive;
}
}
A sequence is bound to the scalar type, not to the property, so
if multiple properties use the same sequence, they will
share the same counter. For each distinct counter, a separate
scalar type that is extending ``sequence`` should be used.
---------
.. eql:function:: std::sequence_next(seq: schema::ScalarType) -> int64
Increments the given sequence to its next value and returns that value.
See the note on :ref:`specifying your sequence
<ref_std_specifying_sequence>` for best practices on supplying the *seq*
parameter.
Sequence advancement is done atomically; each concurrent session and
transaction will receive a distinct sequence value.
.. code-block:: edgeql-repl
db> select sequence_next(introspect MySequence);
{11}
---------
.. eql:function:: std::sequence_reset(seq: schema::ScalarType) -> int64
std::sequence_reset( \
seq: schema::ScalarType, val: int64) -> int64
Resets a sequence to initial state or a given value, returning the value.
See the note on :ref:`specifying your sequence
<ref_std_specifying_sequence>` for best practices on supplying the *seq*
parameter.
The single-parameter form resets the sequence to its initial state, where
the next :eql:func:`sequence_next` call will return the first value in
sequence. The two-parameter form allows you to set the current value of the
sequence. The next :eql:func:`sequence_next` call will return the value
after the one you passed to :eql:func:`sequence_reset`.
.. code-block:: edgeql-repl
db> select sequence_reset(introspect MySequence);
{1}
db> select sequence_next(introspect MySequence);
{1}
db> select sequence_reset(introspect MySequence, 22);
{22}
db> select sequence_next(introspect MySequence);
{23}
---------
.. _ref_std_specifying_sequence:
.. note::
To specify the sequence to be operated on by either
:eql:func:`sequence_next` or :eql:func:`sequence_reset`, you must pass a
``schema::ScalarType`` object. If the sequence argument is known ahead of
time and does not change, we recommend passing it by using the
:eql:op:`introspect` operator:
.. code-block:: edgeql
select sequence_next(introspect MySequenceType);
# or
select sequence_next(introspect typeof MyObj.seq_prop);
This style of execution will ensure that the reference to a sequential
type from a given expression is tracked properly to guarantee schema
referential integrity.
It doesn't work in every use case, though. If in your use case, the
sequence type must be determined at run time via a query argument,
you will need to query it from the ``schema::ScalarType`` set directly:
.. code-block:: edgeql
with
SeqType := (
select schema::ScalarType
filter .name = <str>$seq_type_name
)
select
sequence_next(SeqType);
.. warning::
**Caution**
To work efficiently in high concurrency without lock contention, a
:eql:func:`sequence_next` execution is never rolled back, even if the
containing transaction is aborted. This may result in gaps in the
generated sequence. Likewise, the result of a :eql:func:`sequence_reset`
call is not undone if the transaction is rolled back.
================================================================================
.. File: set.rst
.. _ref_std_set:
====
Sets
====
:edb-alt-title: Set Functions and Operators
:index: set aggregate
.. list-table::
:class: funcoptable
* - :eql:op:`distinct set <distinct>`
- :eql:op-desc:`distinct`
* - :eql:op:`anytype in set <in>`
- :eql:op-desc:`in`
* - :eql:op:`set union set <union>`
- :eql:op-desc:`union`
* - :eql:op:`set intersect set <intersect>`
- :eql:op-desc:`intersect`
* - :eql:op:`set except set <except>`
- :eql:op-desc:`except`
* - :eql:op:`exists set <exists>`
- :eql:op-desc:`exists`
* - :eql:op:`set if bool else set <if..else>`
- :eql:op-desc:`if..else`
* - :eql:op:`optional anytype ?? set <coalesce>`
- :eql:op-desc:`coalesce`
* - :eql:op:`detached`
- :eql:op-desc:`detached`
* - :eql:op:`anytype [is type] <isintersect>`
- :eql:op-desc:`isintersect`
* - :eql:func:`assert_distinct`
- :eql:func-desc:`assert_distinct`
* - :eql:func:`assert_single`
- :eql:func-desc:`assert_single`
* - :eql:func:`assert_exists`
- :eql:func-desc:`assert_exists`
* - :eql:func:`count`
- :eql:func-desc:`count`
* - :eql:func:`array_agg`
- :eql:func-desc:`array_agg`
* - :eql:func:`sum`
- :eql:func-desc:`sum`
* - :eql:func:`all`
- :eql:func-desc:`all`
* - :eql:func:`any`
- :eql:func-desc:`any`
* - :eql:func:`enumerate`
- :eql:func-desc:`enumerate`
* - :eql:func:`min`
- :eql:func-desc:`min`
* - :eql:func:`max`
- :eql:func-desc:`max`
* - :eql:func:`math::mean`
- :eql:func-desc:`math::mean`
* - :eql:func:`math::stddev`
- :eql:func-desc:`math::stddev`
* - :eql:func:`math::stddev_pop`
- :eql:func-desc:`math::stddev_pop`
* - :eql:func:`math::var`
- :eql:func-desc:`math::var`
* - :eql:func:`math::var_pop`
- :eql:func-desc:`math::var_pop`
----------
.. eql:operator:: distinct: distinct set of anytype -> set of anytype
:index: distinct, unique, set of
Produces a set of all unique elements in the given set.
``distinct`` is a set operator that returns a new set where
no member is equal to any other member.
.. code-block:: edgeql-repl
db> select distinct {1, 2, 2, 3};
{1, 2, 3}
----------
.. eql:operator:: in: anytype in set of anytype -> bool
anytype not in set of anytype -> bool
:index: in, not in, intersection, contains, set of
Checks if a given element is a member of a given set.
Set membership operators ``in`` and ``not in`` test whether each element
of the left operand is present in the right operand. This means supplying
a set as the left operand will produce a set of boolean results, one for
each element in the left operand.
.. code-block:: edgeql-repl
db> select 1 in {1, 3, 5};
{true}
db> select 'Alice' in User.name;
{true}
db> select {1, 2} in {1, 3, 5};
{true, false}
This operator can also be used to implement set intersection:
.. code-block:: edgeql-repl
db> with
... A := {1, 2, 3, 4},
... B := {2, 4, 6}
... select A filter A in B;
{2, 4}
----------
.. eql:operator:: union: set of anytype union set of anytype -> set of anytype
:index: union, merge, join, set of
Merges two sets.
Since Gel sets are formally multisets, ``union`` is a *multiset sum*,
so effectively it merges two multisets keeping all of their members.
For example, applying ``union`` to ``{1, 2, 2}`` and
``{2}``, results in ``{1, 2, 2, 2}``.
If you need a distinct union, wrap it with the :eql:op:`distinct`
operator.
----------
.. eql:operator:: intersect: set of anytype intersect set of anytype \
-> set of anytype
:index: intersect, common, set of
Produces a set containing the common items between the given sets.
.. note::
The ordering of the returned set may not match that of the operands.
If you need a distinct intersection, wrap it with the :eql:op:`distinct`
operator.
----------
.. eql:operator:: except: set of anytype except set of anytype \
-> set of anytype
:index: except, set of
Produces a set of all items in the first set which are not in the second.
.. note::
The ordering of the returned set may not match that of the operands.
If you need a distinct set of exceptions, wrap it with the
:eql:op:`distinct` operator.
----------
.. eql:operator:: if..else: set of anytype if bool else set of anytype \
-> set of anytype
:index: if else, ifelse, elif, ternary, conditional
Produces one of two possible results based on a given condition.
.. eql:synopsis::
<left_expr> if <condition> else <right_expr>
If the :eql:synopsis:`<condition>` is ``true``, the ``if...else``
expression produces the value of the :eql:synopsis:`<left_expr>`. If the
:eql:synopsis:`<condition>` is ``false``, however, the ``if...else``
expression produces the value of the :eql:synopsis:`<right_expr>`.
.. code-block:: edgeql-repl
db> select 'real life' if 2 * 2 = 4 else 'dream';
{'real life'}
``if..else`` expressions can be chained when checking multiple conditions
is necessary:
.. code-block:: edgeql-repl
db> with color := 'yellow'
... select 'Apple' if color = 'red' else
... 'Banana' if color = 'yellow' else
... 'Orange' if color = 'orange' else
... 'Other';
{'Banana'}
It can be used to create, update, or delete different objects based on
some condition:
.. code-block:: edgeql
with
name := <str>$0,
admin := <bool>$1
select (insert AdminUser { name := name }) if admin
else (insert User { name := name });
-----------
.. eql:operator:: if..then..else: if bool then set of anytype else set of \
anytype -> set of anytype
:index: if then else, ifelse, elif, conditional
.. versionadded:: 4.0
Produces one of two possible results based on a given condition.
Uses ``then`` for an alternative syntax order to ``if..else`` above.
.. eql:synopsis::
if <condition> then <left_expr> else <right_expr>
If the :eql:synopsis:`<condition>` is ``true``, the ``if...else``
expression produces the value of the :eql:synopsis:`<left_expr>`. If the
:eql:synopsis:`<condition>` is ``false``, however, the ``if...else``
expression produces the value of the :eql:synopsis:`<right_expr>`.
.. code-block:: edgeql-repl
db> select if 2 * 2 = 4 then 'real life' else 'dream';
{'real life'}
``if..else`` expressions can be chained when checking multiple conditions
is necessary:
.. code-block:: edgeql-repl
db> with color := 'yellow', select
... if color = 'red' then
... 'Apple'
... else if color = 'yellow' then
... 'Banana'
... else if color = 'orange' then
... 'Orange'
... else
... 'Other';
{'Banana'}
It can be used to create, update, or delete different objects based on
some condition:
.. code-block:: edgeql
with
name := <str>$0,
admin := <bool>$1
select if admin then (
insert AdminUser { name := name }
) else (
insert User { name := name }
)
-----------
.. eql:operator:: coalesce: optional anytype ?? set of anytype \
-> set of anytype
:index: ??, empty set
Produces the first of its operands that is not an empty set.
This evaluates to ``A`` for an non-empty ``A``, otherwise evaluates to
``B``.
A typical use case of the coalescing operator is to provide default
values for optional properties:
.. code-block:: edgeql
# Get a set of tuples (<issue name>, <priority>)
# for all issues.
select (Issue.name, Issue.priority.name ?? 'n/a');
Without the coalescing operator, the above query will skip any
``Issue`` without priority.
The coalescing operator can be used to express things like
"select or insert if missing":
.. code-block:: edgeql
select
(select User filter .name = 'Alice') ??
(insert User { name := 'Alice' });
----------
.. _ref_stdlib_set_detached:
.. eql:operator:: detached: detached set of anytype -> set of anytype
:index: detached, set of
Detaches the input set reference from the current scope.
A ``detached`` expression allows referring to some set as if it were
defined in the top-level ``with`` block. ``detached``
expressions ignore all current scopes in which they are nested.
This makes it possible to write queries that reference the same set
reference in multiple places.
.. code-block:: edgeql
update User
filter .name = 'Dave'
set {
friends := (select detached User filter .name = 'Alice'),
coworkers := (select detached User filter .name = 'Bob')
};
Without ``detached``, the occurrences of ``User`` inside the ``set`` shape
would be *bound* to the set of users named ``"Dave"``. However, in this
context we want to run an unrelated query on the "unbound" ``User`` set.
.. code-block:: edgeql
# does not work!
update User
filter .name = 'Dave'
set {
friends := (select User filter .name = 'Alice'),
coworkers := (select User filter .name = 'Bob')
};
Instead of explicitly detaching a set, you can create a reference to it in
a ``with`` block. All declarations inside a ``with`` block are implicitly
detached.
.. code-block:: edgeql
with U1 := User,
U2 := User
update User
filter .name = 'Dave'
set {
friends := (select U1 filter .name = 'Alice'),
coworkers := (select U2 filter .name = 'Bob')
};
----------
.. eql:operator:: exists: exists set of anytype -> bool
:index: exists, set of, is empty
Determines whether a set is empty or not.
``exists`` is an aggregate operator that returns a singleton set
``{true}`` if the input set is not empty, and returns ``{false}``
otherwise:
.. code-block:: edgeql-repl
db> select exists {1, 2};
{true}
----------
.. eql:operator:: isintersect: anytype [is type] -> anytype
:index: [is type], type intersection, filter
Filters a set based on its type. Will return back the specified type.
The type intersection operator removes all elements from the input set
that aren't of the specified type. Additionally, since it
guarantees the type of the result set, all the links and properties
associated with the specified type can now be used on the
resulting expression. This is especially useful in combination
with :ref:`backlinks <ref_datamodel_links>`.
Consider the following types:
.. code-block:: sdl
type User {
required name: str;
}
abstract type Owned {
required owner: User;
}
type Issue extending Owned {
required title: str;
}
type Comment extending Owned {
required body: str;
}
The following expression will get all :eql:type:`Objects <Object>`
owned by all users (if there are any):
.. code-block:: edgeql
select User.<owner;
By default, :ref:`backlinks <ref_datamodel_links>` don't infer any
type information beyond the fact that it's an :eql:type:`Object`.
To ensure that this path specifically reaches ``Issue``, the type
intersection operator must then be used:
.. code-block:: edgeql
select User.<owner[is Issue];
# With the use of type intersection it's possible to refer
# to a specific property of Issue now:
select User.<owner[is Issue].title;
----------
.. eql:function:: std::assert_distinct( \
s: set of anytype, \
named only message: optional str = <str>{} \
) -> set of anytype
:index: multiplicity uniqueness
Checks that the input set contains only unique elements.
If the input set contains duplicate elements (i.e. it is not a *proper
set*), ``assert_distinct`` raises a ``ConstraintViolationError``.
Otherwise, this function returns the input set.
This function is useful as a runtime distinctness assertion in queries and
computed expressions that should always return proper sets, but where
static multiplicity inference is not capable enough or outright
impossible. An optional *message* named argument can be used to customize
the error message:
.. code-block:: edgeql-repl
db> select assert_distinct(
... (select User filter .groups.name = "Administrators")
... union
... (select User filter .groups.name = "Guests")
... )
{default::User {id: ...}}
db> select assert_distinct(
... (select User filter .groups.name = "Users")
... union
... (select User filter .groups.name = "Guests")
... )
ERROR: ConstraintViolationError: assert_distinct violation: expression
returned a set with duplicate elements.
db> select assert_distinct(
... (select User filter .groups.name = "Users")
... union
... (select User filter .groups.name = "Guests"),
... message := "duplicate users!"
... )
ERROR: ConstraintViolationError: duplicate users!
----------
.. eql:function:: std::assert_single( \
s: set of anytype, \
named only message: optional str = <str>{} \
) -> set of anytype
:index: cardinality singleton
Checks that the input set contains no more than one element.
If the input set contains more than one element, ``assert_single`` raises
a ``CardinalityViolationError``. Otherwise, this function returns the
input set.
This function is useful as a runtime cardinality assertion in queries and
computed expressions that should always return sets with at most a single
element, but where static cardinality inference is not capable enough or
outright impossible. An optional *message* named argument can be used to
customize the error message.
.. code-block:: edgeql-repl
db> select assert_single((select User filter .name = "Unique"))
{default::User {id: ...}}
db> select assert_single((select User))
ERROR: CardinalityViolationError: assert_single violation: more than
one element returned by an expression
db> select assert_single((select User), message := "too many users!")
ERROR: CardinalityViolationError: too many users!
.. note::
``assert_single`` can be useful in many of the same contexts as ``limit
1`` with the key difference being that ``limit 1`` doesn't produce an
error if more than a single element exists in the set.
----------
.. eql:function:: std::assert_exists( \
s: set of anytype, \
named only message: optional str = <str>{} \
) -> set of anytype
:index: cardinality existence empty
Checks that the input set contains at least one element.
If the input set is empty, ``assert_exists`` raises a
``CardinalityViolationError``. Otherwise, this function returns the input
set.
This function is useful as a runtime existence assertion in queries and
computed expressions that should always return sets with at least a single
element, but where static cardinality inference is not capable enough or
outright impossible. An optional *message* named argument can be used to
customize the error message.
.. code-block:: edgeql-repl
db> select assert_exists((select User filter .name = "Administrator"))
{default::User {id: ...}}
db> select assert_exists((select User filter .name = "Nonexistent"))
ERROR: CardinalityViolationError: assert_exists violation: expression
returned an empty set.
db> select assert_exists(
... (select User filter .name = "Nonexistent"),
... message := "no users!"
... )
ERROR: CardinalityViolationError: no users!
----------
.. eql:function:: std::count(s: set of anytype) -> int64
:index: aggregate
Returns the number of elements in a set.
.. code-block:: edgeql-repl
db> select count({2, 3, 5});
{3}
db> select count(User); # number of User objects in db
{4}
----------
.. eql:function:: std::sum(s: set of int32) -> int64
std::sum(s: set of int64) -> int64
std::sum(s: set of float32) -> float32
std::sum(s: set of float64) -> float64
std::sum(s: set of bigint) -> bigint
std::sum(s: set of decimal) -> decimal
:index: aggregate
Returns the sum of the set of numbers.
The result type depends on the input set type. The general rule of thumb
is that the type of the input set is preserved (as if a simple
:eql:op:`+<plus>` was used) while trying to reduce the chance of an
overflow (so all integers produce :eql:type:`int64` sum).
.. code-block:: edgeql-repl
db> select sum({2, 3, 5});
{10}
db> select sum({0.2, 0.3, 0.5});
{1.0}
----------
.. eql:function:: std::all(values: set of bool) -> bool
:index: aggregate
Returns ``true`` if none of the values in the given set are ``false``.
The result is ``true`` if all of the *values* are ``true`` or the set of
*values* is ``{}``, with ``false`` returned otherwise.
.. code-block:: edgeql-repl
db> select all(<bool>{});
{true}
db> select all({1, 2, 3, 4} < 4);
{false}
----------
.. eql:function:: std::any(values: set of bool) -> bool
:index: aggregate
Returns ``true`` if any of the values in the given set is ``true``.
The result is ``true`` if any of the *values* are ``true``, with ``false``
returned otherwise.
.. code-block:: edgeql-repl
db> select any(<bool>{});
{false}
db> select any({1, 2, 3, 4} < 4);
{true}
----------
.. eql:function:: std::enumerate(values: set of anytype) -> \
set of tuple<int64, anytype>
:index: enumerate
Returns a set of tuples in the form of ``(index, element)``.
The ``enumerate()`` function takes any set and produces a set of
tuples containing the zero-based index number and the value for each
element.
.. note::
The ordering of the returned set is not guaranteed, however,
the assigned indexes are guaranteed to be in order of the
original set.
.. code-block:: edgeql-repl
db> select enumerate({2, 3, 5});
{(1, 3), (0, 2), (2, 5)}
.. code-block:: edgeql-repl
db> select enumerate(User.name);
{(0, 'Alice'), (1, 'Bob'), (2, 'Dave')}
----------
.. eql:function:: std::min(values: set of anytype) -> optional anytype
:index: aggregate
Returns the smallest value in the given set.
.. code-block:: edgeql-repl
db> select min({-1, 100});
{-1}
----------
.. eql:function:: std::max(values: set of anytype) -> optional anytype
:index: aggregate
Returns the largest value in the given set.
.. code-block:: edgeql-repl
db> select max({-1, 100});
{100}
================================================================================
.. File: string.rst
.. _ref_std_string:
=======
Strings
=======
:edb-alt-title: String Functions and Operators
.. list-table::
:class: funcoptable
* - :eql:type:`str`
- String
* - :eql:op:`str[i] <stridx>`
- :eql:op-desc:`stridx`
* - :eql:op:`str[from:to] <strslice>`
- :eql:op-desc:`strslice`
* - :eql:op:`str ++ str <strplus>`
- :eql:op-desc:`strplus`
* - :eql:op:`str like pattern <like>`
- :eql:op-desc:`like`
* - :eql:op:`str ilike pattern <ilike>`
- :eql:op-desc:`ilike`
* - :eql:op:`= <eq>` :eql:op:`\!= <neq>` :eql:op:`?= <coaleq>`
:eql:op:`?!= <coalneq>` :eql:op:`\< <lt>` :eql:op:`\> <gt>`
:eql:op:`\<= <lteq>` :eql:op:`\>= <gteq>`
- Comparison operators
* - :eql:func:`to_str`
- :eql:func-desc:`to_str`
* - :eql:func:`len`
- Returns a string's length.
* - :eql:func:`contains`
- Tests if a string contains a substring.
* - :eql:func:`find`
- Finds the index of a substring.
* - :eql:func:`str_lower`
- :eql:func-desc:`str_lower`
* - :eql:func:`str_upper`
- :eql:func-desc:`str_upper`
* - :eql:func:`str_title`
- :eql:func-desc:`str_title`
* - :eql:func:`str_pad_start`
- :eql:func-desc:`str_pad_start`
* - :eql:func:`str_pad_end`
- :eql:func-desc:`str_pad_end`
* - :eql:func:`str_trim`
- :eql:func-desc:`str_trim`
* - :eql:func:`str_trim_start`
- :eql:func-desc:`str_trim_start`
* - :eql:func:`str_trim_end`
- :eql:func-desc:`str_trim_end`
* - :eql:func:`str_repeat`
- :eql:func-desc:`str_repeat`
* - :eql:func:`str_replace`
- :eql:func-desc:`str_replace`
* - :eql:func:`str_reverse`
- :eql:func-desc:`str_reverse`
* - :eql:func:`str_split`
- Splits a string into an array using a delimiter.
* - :eql:func:`re_match`
- :eql:func-desc:`re_match`
* - :eql:func:`re_match_all`
- :eql:func-desc:`re_match_all`
* - :eql:func:`re_replace`
- :eql:func-desc:`re_replace`
* - :eql:func:`re_test`
- :eql:func-desc:`re_test`
----------
.. eql:type:: std::str
:index: continuation cont
A unicode string of text.
Most primitive types (except :eql:type:`bytes`) can be
:eql:op:`cast <cast>` to and from a string:
.. code-block:: edgeql-repl
db> select <str>42;
{'42'}
db> select <bool>'true';
{true}
db> select "I ❤️ Gel";
{'I ❤️ Gel'}
Note that when a :eql:type:`str` is cast into a :eql:type:`json`,
the result is a JSON string value. The same applies for casting back
from :eql:type:`json` - only a JSON string value can be cast into
a :eql:type:`str`:
.. code-block:: edgeql-repl
db> select <json>'Hello, world';
{'"Hello, world"'}
There are two kinds of string literals in EdgeQL: regular and *raw*.
Raw string literals do not evaluate ``\``, so ``\n`` in in a raw string
is two characters ``\`` and ``n``.
The regular string literal syntax is ``'a string'`` or a ``"a string"``.
Two *raw* string syntaxes are illustrated below:
.. code-block:: edgeql-repl
db> select r'A raw \n string';
{'A raw \\n string'}
db> select 'Not a raw \n string';
{
'Not a raw
string',
}
db> select $$something$$;
{'something'}
db> select $marker$something $$
... nested \!$$$marker$;
{'something $$
nested \!$$'}
Regular strings use ``\`` to indicate line continuation. When a
line continuation symbol is encountered, the symbol itself as well
as all the whitespace characters up to the next non-whitespace
character are omitted from the string:
.. code-block:: edgeql-repl
db> select 'Hello, \
... world';
{'"Hello, world"'}
.. note::
This type is subject to `the Postgres maximum field size`_
of 1GB.
.. versionadded:: 6.0
Regular strings may use ``\(expr)`` to interpolate the value of
``expr`` into the string. The value will be cast to ``str`` if it
is not already. For example:
.. code-block:: edgeql-repl
db> select '1 + 1 = \(1+1)';
{'1 + 1 = 2'}
db> select User { name := '\(.first_name) \(.last_name)' };
{
default::User {
name := 'Keanu Reeves',
},
...
}
.. lint-off
.. _the Postgres maximum field size: https://wiki.postgresql.org/wiki/FAQ#What_is_the_maximum_size_for_a_row.2C_a_table.2C_and_a_database.3F>
.. lint-on
----------
.. eql:operator:: stridx: str [ int64 ] -> str
:index: [int], index access
String indexing.
Indexing starts at 0. Negative indexes are also valid and count from
the *end* of the string.
.. code-block:: edgeql-repl
db> select 'some text'[1];
{'o'}
db> select 'some text'[-1];
{'t'}
It is an error to attempt to extract a character at an index
outside the bounds of the string:
.. code-block:: edgeql-repl
db> select 'some text'[8];
{'t'}
db> select 'some text'[9];
InvalidValueError: string index 9 is out of bounds
A slice up to the next index can be used if an empty string is
preferred to an error when outside the bounds of the string:
.. code-block:: edgeql-repl
db> select 'some text'[8:9];
{'t'}
db> select 'some text'[9:10];
{''}
----------
.. eql:operator:: strslice: str [ int64 : int64 ] -> str
:index: [int:int]
String slicing.
Indexing starts at 0. Negative indexes are also valid and count from
the *end* of the string.
.. code-block:: edgeql-repl
db> select 'some text'[1:3];
{'om'}
db> select 'some text'[-4:];
{'text'}
db> select 'some text'[:-5];
{'some'}
db> select 'some text'[5:-2];
{'te'}
It is perfectly acceptable to use indexes outside the bounds of a
string in a *slice*:
.. code-block:: edgeql-repl
db> select 'some text'[-4:100];
{'text'}
db> select 'some text'[-100:-5];
{'some'}
----------
.. eql:operator:: strplus: str ++ str -> str
:index: ++, string, concatenate, join, add
String concatenation.
.. code-block:: edgeql-repl
db> select 'some' ++ ' text';
{'some text'}
----------
.. eql:operator:: like: str like str -> bool
str not like str -> bool
:index: like, not like, case sensitive, string matching, comparison, compare
Case-sensitive simple string matching.
Returns ``true`` if the *value* (the ``str`` on the left) matches the
*pattern* (the ``str`` on the right) and ``false`` otherwise. The operator
``not like`` is the negation of ``like``.
The pattern matching rules are as follows:
.. list-table::
:widths: auto
:header-rows: 1
* - pattern
- interpretation
* - ``%``
- matches zero or more characters
* - ``_``
- matches exactly one character
* - ``\%``
- matches a literal "%"
* - ``\_``
- matches a literal "_"
* - any other character
- matches itself
In particular, this means that if there are no special symbols in
the *pattern*, the operators ``like`` and ``not
like`` work identical to :eql:op:`= <eq>` and :eql:op:`\!= <neq>`,
respectively.
.. code-block:: edgeql-repl
db> select 'abc' like 'abc';
{true}
db> select 'abc' like 'a%';
{true}
db> select 'abc' like '_b_';
{true}
db> select 'abc' like 'c';
{false}
db> select 'a%%c' not like r'a\%c';
{true}
----------
.. eql:operator:: ilike: str ilike str -> bool
str not ilike str -> bool
:index: ilike, not ilike, case insensitive, string matching, comparison, compare
Case-insensitive simple string matching.
The operators ``ilike`` and ``not ilike`` work
the same way as :eql:op:`like` and :eql:op:`not like<like>`,
except that the *pattern* is matched in a case-insensitive manner.
.. code-block:: edgeql-repl
db> select 'Abc' ilike 'a%';
{true}
----------
.. eql:function:: std::str_lower(string: str) -> str
Returns a lowercase copy of the input string.
.. code-block:: edgeql-repl
db> select str_lower('Some Fancy Title');
{'some fancy title'}
----------
.. eql:function:: std::str_upper(string: str) -> str
Returns an uppercase copy of the input string.
.. code-block:: edgeql-repl
db> select str_upper('Some Fancy Title');
{'SOME FANCY TITLE'}
----------
.. eql:function:: std::str_title(string: str) -> str
Returns a titlecase copy of the input string.
Every word in the string will have the first letter capitalized
and the rest converted to lowercase.
.. code-block:: edgeql-repl
db> select str_title('sOmE fAnCy TiTlE');
{'Some Fancy Title'}
----------
.. eql:function:: std::str_pad_start(string: str, n: int64, fill: str = ' ') \
-> str
Returns the input string padded at the start to the length *n*.
If the string is longer than *n*, then it is truncated to the
first *n* characters. Otherwise, the string is padded on the
left up to the total length *n* using *fill* characters (space by
default).
.. code-block:: edgeql-repl
db> select str_pad_start('short', 10);
{' short'}
db> select str_pad_start('much too long', 10);
{'much too l'}
db> select str_pad_start('short', 10, '.:');
{'.:.:.short'}
----------
.. eql:function:: std::str_pad_end(string: str, n: int64, fill: str = ' ') \
-> str
Returns the input string padded at the end to the length *n*.
If the string is longer than *n*, then it is truncated to the
first *n* characters. Otherwise, the string is padded on the
right up to the total length *n* using *fill* characters (space by
default).
.. code-block:: edgeql-repl
db> select str_pad_end('short', 10);
{'short '}
db> select str_pad_end('much too long', 10);
{'much too l'}
db> select str_pad_end('short', 10, '.:');
{'short.:.:.'}
----------
.. eql:function:: std::str_trim_start(string: str, trim: str = ' ') -> str
Returns the input string with all *trim* characters removed
from the start.
If *trim* specifies more than one character they will be removed from
the beginning of the string regardless of the order in which they appear.
.. code-block:: edgeql-repl
db> select str_trim_start(' data');
{'data'}
db> select str_trim_start('.....data', '.:');
{'data'}
db> select str_trim_start(':::::data', '.:');
{'data'}
db> select str_trim_start(':...:data', '.:');
{'data'}
db> select str_trim_start('.:.:.data', '.:');
{'data'}
----------
.. eql:function:: std::str_trim_end(string: str, trim: str = ' ') -> str
Returns the input string with all *trim* characters removed from the end.
If *trim* specifies more than one character they will be removed from
the end of the string regardless of the order in which they appear.
.. code-block:: edgeql-repl
db> select str_trim_end('data ');
{'data'}
db> select str_trim_end('data.....', '.:');
{'data'}
db> select str_trim_end('data:::::', '.:');
{'data'}
db> select str_trim_end('data:...:', '.:');
{'data'}
db> select str_trim_end('data.:.:.', '.:');
{'data'}
----------
.. eql:function:: std::str_trim(string: str, trim: str = ' ') -> str
Returns the input string with *trim* characters removed from both ends.
If *trim* specifies more than one character they will be removed from
both ends of the string regardless of the order in which they appear.
This is the same as applying
:eql:func:`str_trim_start` and :eql:func:`str_trim_end`.
.. code-block:: edgeql-repl
db> select str_trim(' data ');
{'data'}
db> select str_trim('::data.....', '.:');
{'data'}
db> select str_trim('..data:::::', '.:');
{'data'}
db> select str_trim('.:data:...:', '.:');
{'data'}
db> select str_trim(':.:.data.:.', '.:');
{'data'}
----------
.. eql:function:: std::str_repeat(string: str, n: int64) -> str
Repeats the input string *n* times.
An empty string is returned if *n* is zero or negative.
.. code-block:: edgeql-repl
db> select str_repeat('.', 3);
{'...'}
db> select str_repeat('foo', -1);
{''}
----------
.. eql:function:: std::str_replace(s: str, old: str, new: str) -> str
Replaces all occurrences of a substring with a new one.
Given a string *s*, finds all non-overlapping occurrences of the
substring *old* and replaces them with the substring *new*.
.. code-block:: edgeql-repl
db> select str_replace('hello world', 'h', 'H');
{'Hello world'}
db> select str_replace('hello world', 'l', '[L]');
{'he[L][L]o wor[L]d'}
db> select str_replace('hello world', 'o', '😄');
{'hell😄 w😄rld'}
----------
.. eql:function:: std::str_reverse(string: str) -> str
Reverses the order of the characters in the string.
.. code-block:: edgeql-repl
db> select str_reverse('Hello world');
{'dlrow olleH'}
db> select str_reverse('Hello 👋 world 😄');
{'😄 dlrow 👋 olleH'}
----------
.. eql:function:: std::str_split(s: str, delimiter: str) -> array<str>
:index: split str_split explode
Splits a string into array elements using the supplied delimiter.
.. code-block:: edgeql-repl
db> select str_split('1, 2, 3', ', ');
{['1', '2', '3']}
.. code-block:: edgeql-repl
db> select str_split('123', '');
{['1', '2', '3']}
----------
.. eql:function:: std::re_match(pattern: str, \
string: str) -> array<str>
:index: regex regexp regular
Finds the first regular expression match in a string.
Given an input string and a regular expression :ref:`pattern
<string_regexp>`, finds the first match for the regular expression
within the string. Each match returned is represented by an
:eql:type:`array\<str\>` of matched groups.
.. code-block:: edgeql-repl
db> select re_match(r'\w{4}ql', 'I ❤️ edgeql');
{['edgeql']}
----------
.. eql:function:: std::re_match_all(pattern: str, \
string: str) -> set of array<str>
:index: regex regexp regular
Finds all regular expression matches in a string.
Given an input string and a regular expression :ref:`pattern
<string_regexp>`, repeatedly matches the regular expression within
the string. Returns the set of all matches, with each match
represented by an :eql:type:`array\<str\>` of matched groups.
.. code-block:: edgeql-repl
db> select re_match_all(r'a\w+', 'an abstract concept');
{['an'], ['abstract']}
----------
.. eql:function:: std::re_replace(pattern: str, sub: str, \
string: str, \
named only flags: str='') \
-> str
:index: regex regexp regular replace
Replaces matching substrings in a given string.
Takes an input string and a regular expression :ref:`pattern
<string_regexp>`, replacing matching substrings with the replacement
string *sub*. Optional :ref:`flag arguments<string_regexp_flags>`
can be used to specify additional regular expression flags.
.. code-block:: edgeql-repl
db> select re_replace('a', 'A', 'Alabama');
{'AlAbama'}
db> select re_replace('a', 'A', 'Alabama', flags := 'g');
{'AlAbAmA'}
db> select re_replace('A', 'A', 'Alabama', flags := 'ig');
{'AlAbAmA'}
----------
.. eql:function:: std::re_test(pattern: str, string: str) -> bool
:index: regex regexp regular match
Tests if a regular expression has a match in a string.
Given an input string and a regular expression :ref:`pattern
<string_regexp>`, tests whether there is a match for the regular
expression within the string. Returns ``true`` if there is a
match, ``false`` otherwise.
.. code-block:: edgeql-repl
db> select re_test(r'a', 'abc');
{true}
------------
.. eql:function:: std::to_str(val: datetime, fmt: optional str={}) -> str
std::to_str(val: duration, fmt: optional str={}) -> str
std::to_str(val: int64, fmt: optional str={}) -> str
std::to_str(val: float64, fmt: optional str={}) -> str
std::to_str(val: bigint, fmt: optional str={}) -> str
std::to_str(val: decimal, fmt: optional str={}) -> str
std::to_str(val: json, fmt: optional str={}) -> str
std::to_str(val: bytes) -> str
std::to_str(val: cal::local_datetime, \
fmt: optional str={}) -> str
std::to_str(val: cal::local_date, \
fmt: optional str={}) -> str
std::to_str(val: cal::local_time, \
fmt: optional str={}) -> str
:index: stringify dumps join array_to_string decode TextDecoder
Returns the string representation of the input value.
A versatile polymorphic function defined for different input types,
``to_str`` uses corresponding converter functions from :eql:type:`str`
to specific types via the format argument *fmt*.
When converting :eql:type:`bytes`, :eql:type:`datetime`,
:eql:type:`cal::local_datetime`, :eql:type:`cal::local_date`,
:eql:type:`cal::local_time`, :eql:type:`duration` this function
is the inverse of :eql:func:`to_bytes`, :eql:func:`to_datetime`,
:eql:func:`cal::to_local_datetime`, :eql:func:`cal::to_local_date`,
:eql:func:`cal::to_local_time`, :eql:func:`to_duration`, correspondingly.
For valid date and time formatting patterns see
:ref:`here <ref_std_converters_datetime_fmt>`.
.. code-block:: edgeql-repl
db> select to_str(<datetime>'2018-05-07 15:01:22.306916-05',
... 'FMDDth "of" FMMonth, YYYY');
{'7th of May, 2018'}
db> select to_str(<cal::local_date>'2018-05-07', 'CCth "century"');
{'21st century'}
.. note::
If you want to use literal text in your format string, it's best to
enclose it in double quotes as shown above with ``of`` and
``century``.
When converting one of the numeric types, this function is the
reverse of: :eql:func:`to_bigint`, :eql:func:`to_decimal`,
:eql:func:`to_int16`, :eql:func:`to_int32`, :eql:func:`to_int64`,
:eql:func:`to_float32`, :eql:func:`to_float64`.
For valid number formatting patterns see
:ref:`here <ref_std_converters_number_fmt>`.
See also :eql:func:`to_json`.
.. code-block:: edgeql-repl
db> select to_str(123, '999999');
{' 123'}
db> select to_str(123, '099999');
{' 000123'}
db> select to_str(123.45, 'S999.999');
{'+123.450'}
db> select to_str(123.45e-20, '9.99EEEE');
{' 1.23e-18'}
db> select to_str(-123.45n, 'S999.99');
{'-123.45'}
When converting :eql:type:`json`, this function can take
``'pretty'`` as an optional *fmt* argument to produce a
pretty-formatted JSON string.
See also :eql:func:`to_json`.
.. code-block:: edgeql-repl
db> select to_str(<json>2);
{'2'}
db> select to_str(<json>['hello', 'world']);
{'["hello", "world"]'}
db> select to_str(<json>(a := 2, b := 'hello'), 'pretty');
{'{
"a": 2,
"b": "hello"
}'}
When converting :eql:type:`arrays <array>`, a *delimiter* argument
is required:
.. code-block:: edgeql-repl
db> select to_str(['one', 'two', 'three'], ', ');
{'one, two, three'}
.. warning::
A version of ``std::to_str`` exists which operates on arrays but has
been deprecated; :eql:func:`array_join` should be used instead.
A :eql:type:`bytes` value can be converted to a :eql:type:`str` using
UTF-8 encoding. Returns an InvalidValueError if input UTF-8 is invalid.
.. code-block:: edgeql-repl
db> select to_str(b'\xe3\x83\x86');
{'テ'}
db> select to_str(b'\xe3\x83');
gel error: InvalidValueError: invalid byte sequence for
encoding "UTF8": 0xe3 0x83
----------
.. _string_regexp:
Regular Expressions
-------------------
|Gel| supports Regular expressions (REs), as defined in POSIX 1003.2.
They come in two forms: BRE (basic RE) and ERE (extended RE). In
addition, Gel supports certain common extensions to the POSIX
standard commonly known as ARE (advanced RE). More details about
BRE, ERE, and ARE support can be found in `PostgreSQL documentation`_.
.. _`PostgreSQL documentation`:
https://www.postgresql.org/docs/10/static/
functions-matching.html#POSIX-SYNTAX-DETAILS
The table below outlines the different options accepted as the ``flags``
argument to various regular expression functions, or as
`embedded options`_ in the pattern itself, e.g. ``'(?i)fooBAR'``:
.. _`embedded options`:
https://www.postgresql.org/docs/10/functions-matching.html#POSIX-METASYNTAX
.. _string_regexp_flags:
Option Flags
^^^^^^^^^^^^
====== ==================================================================
Option Description
====== ==================================================================
``b`` rest of RE is a BRE
``c`` case-sensitive matching (overrides operator type)
``e`` rest of RE is an ERE
``i`` case-insensitive matching (overrides operator type)
``m`` historical synonym for n
``n`` newline-sensitive matching
``p`` partial newline-sensitive matching
``q`` rest of RE is a literal ("quoted") string, all ordinary characters
``s`` non-newline-sensitive matching (default)
``t`` tight syntax (default)
``w`` inverse partial newline-sensitive ("weird") matching
``x`` expanded syntax ignoring whitespace characters
====== ==================================================================
----------
Formatting
----------
..
Portions Copyright (c) 2019 MagicStack Inc. and the Gel authors.
Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
Portions Copyright (c) 1994, The Regents of the University of California
Permission to use, copy, modify, and distribute this software and its
documentation for any purpose, without fee, and without a written agreement
is hereby granted, provided that the above copyright notice and this
paragraph and the following two paragraphs appear in all copies.
IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
INCLUDING, BUT not LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO
PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
Some of the type converter functions take an extra argument specifying
formatting (either for converting to a :eql:type:`str` or parsing from one).
The different formatting options are collected in this section.
.. _ref_std_converters_datetime_fmt:
Date and time formatting options
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+-------------------------+----------------------------------------+
| Pattern | Description |
+=========================+========================================+
| HH | hour of day (01-12) |
+-------------------------+----------------------------------------+
| HH12 | hour of day (01-12) |
+-------------------------+----------------------------------------+
| HH24 | hour of day (00-23) |
+-------------------------+----------------------------------------+
| MI | minute (00-59) |
+-------------------------+----------------------------------------+
| SS | second (00-59) |
+-------------------------+----------------------------------------+
| MS | millisecond (000-999) |
+-------------------------+----------------------------------------+
| US | microsecond (000000-999999) |
+-------------------------+----------------------------------------+
| SSSS | seconds past midnight (0-86399) |
+-------------------------+----------------------------------------+
| AM, am, PM or pm | meridiem indicator (without periods) |
+-------------------------+----------------------------------------+
| A.M., a.m., P.M. or | meridiem indicator (with periods) |
| p.m. | |
+-------------------------+----------------------------------------+
| Y,YYY | year (4 or more digits) with comma |
+-------------------------+----------------------------------------+
| YYYY | year (4 or more digits) |
+-------------------------+----------------------------------------+
| YYY | last 3 digits of year |
+-------------------------+----------------------------------------+
| YY | last 2 digits of year |
+-------------------------+----------------------------------------+
| Y | last digit of year |
+-------------------------+----------------------------------------+
| IYYY | ISO 8601 week-numbering year (4 or |
| | more digits) |
+-------------------------+----------------------------------------+
| IYY | last 3 digits of ISO 8601 week- |
| | numbering year |
+-------------------------+----------------------------------------+
| IY | last 2 digits of ISO 8601 week- |
| | numbering year |
+-------------------------+----------------------------------------+
| I | last digit of ISO 8601 week-numbering |
| | year |
+-------------------------+----------------------------------------+
| BC, bc, AD or ad | era indicator (without periods) |
+-------------------------+----------------------------------------+
| B.C., b.c., A.D. or | era indicator (with periods) |
| a.d. | |
+-------------------------+----------------------------------------+
| MONTH | full upper case month name (blank- |
| | padded to 9 chars) |
+-------------------------+----------------------------------------+
| Month | full capitalized month name (blank- |
| | padded to 9 chars) |
+-------------------------+----------------------------------------+
| month | full lower case month name (blank- |
| | padded to 9 chars) |
+-------------------------+----------------------------------------+
| MON | abbreviated upper case month name (3 |
| | chars in English, localized lengths |
| | vary) |
+-------------------------+----------------------------------------+
| Mon | abbreviated capitalized month name (3 |
| | chars in English, localized lengths |
| | vary) |
+-------------------------+----------------------------------------+
| mon | abbreviated lower case month name (3 |
| | chars in English, localized lengths |
| | vary) |
+-------------------------+----------------------------------------+
| MM | month number (01-12) |
+-------------------------+----------------------------------------+
| DAY | full upper case day name (blank-padded |
| | to 9 chars) |
+-------------------------+----------------------------------------+
| Day | full capitalized day name (blank- |
| | padded to 9 chars) |
+-------------------------+----------------------------------------+
| day | full lower case day name (blank-padded |
| | to 9 chars) |
+-------------------------+----------------------------------------+
| DY | abbreviated upper case day name (3 |
| | chars in English, localized lengths |
| | vary) |
+-------------------------+----------------------------------------+
| Dy | abbreviated capitalized day name (3 |
| | chars in English, localized lengths |
| | vary) |
+-------------------------+----------------------------------------+
| dy | abbreviated lower case day name (3 |
| | chars in English, localized lengths |
| | vary) |
+-------------------------+----------------------------------------+
| DDD | day of year (001-366) |
+-------------------------+----------------------------------------+
| IDDD | day of ISO 8601 week-numbering year |
| | (001-371; day 1 of the year is Monday |
| | of the first ISO week) |
+-------------------------+----------------------------------------+
| DD | day of month (01-31) |
+-------------------------+----------------------------------------+
| D | day of the week, Sunday (1) to |
| | Saturday (7) |
+-------------------------+----------------------------------------+
| ID | ISO 8601 day of the week, Monday (1) |
| | to Sunday (7) |
+-------------------------+----------------------------------------+
| W | week of month (1-5) (the first week |
| | starts on the first day of the month) |
+-------------------------+----------------------------------------+
| WW | week number of year (1-53) (the first |
| | week starts on the first day of the |
| | year) |
+-------------------------+----------------------------------------+
| IW | week number of ISO 8601 week-numbering |
| | year (01-53; the first Thursday of the |
| | year is in week 1) |
+-------------------------+----------------------------------------+
| CC | century (2 digits) (the twenty-first |
| | century starts on 2001-01-01) |
+-------------------------+----------------------------------------+
| J | Julian Day (integer days since |
| | November 24, 4714 BC at midnight UTC) |
+-------------------------+----------------------------------------+
| Q | quarter |
+-------------------------+----------------------------------------+
| RM | month in upper case Roman numerals |
| | (I-XII; I=January) |
+-------------------------+----------------------------------------+
| rm | month in lower case Roman numerals |
| | (i-xii; i=January) |
+-------------------------+----------------------------------------+
| TZ | upper case time-zone abbreviation |
| | (only supported in :eql:func:`to_str`) |
+-------------------------+----------------------------------------+
| tz | lower case time-zone abbreviation |
| | (only supported in :eql:func:`to_str`) |
+-------------------------+----------------------------------------+
| TZH | time-zone hours |
+-------------------------+----------------------------------------+
| TZM | time-zone minutes |
+-------------------------+----------------------------------------+
| OF | time-zone offset from UTC (only |
| | supported in :eql:func:`to_str`) |
+-------------------------+----------------------------------------+
Some additional formatting modifiers:
+---------------+-----------------------------------+---------------+
| Modifier | Description | Example |
+===============+===================================+===============+
| FM prefix | fill mode (suppress leading | FMMonth |
| | zeroes and padding blanks) | |
+---------------+-----------------------------------+---------------+
| TH suffix | upper case ordinal number suffix | DDTH, e.g., |
| | | 12TH |
+---------------+-----------------------------------+---------------+
| th suffix | lower case ordinal number suffix | DDth, e.g., |
| | | 12th |
+---------------+-----------------------------------+---------------+
| FX prefix | fixed format global option (see | FX Month DD |
| | usage notes) | Day |
+---------------+-----------------------------------+---------------+
Whitespace is normally ignored when parsing string input, unless
the *FX* prefix modifier is used. For example:
.. code-block:: edgeql-repl
db> select cal::to_local_date(
... '2000 JUN', 'YYYY MON');
{<cal::local_date>'2000-06-01'}
db> select cal::to_local_date(
... '2000 JUN', 'FXYYYY MON');
InternalServerError: invalid value " " for "MON"
Ordinary text is allowed in :eql:func:`to_str` format strings and will be
output literally. For better compatibility you should put a plain substring
in double quotes to force it to be interpreted as literal text even if it
contains template patterns now or could in the future.
.. _ref_std_converters_number_fmt:
Number formatting options
^^^^^^^^^^^^^^^^^^^^^^^^^
+------------+-----------------------------------------------------+
| Pattern | Description |
+============+=====================================================+
| 9 | digit position (can be dropped if insignificant) |
+------------+-----------------------------------------------------+
| 0 | digit position (will not be dropped even if |
| | insignificant) |
+------------+-----------------------------------------------------+
| . | (period) decimal point |
+------------+-----------------------------------------------------+
| , | (comma) group (thousands) separator |
+------------+-----------------------------------------------------+
| PR | negative value in angle brackets |
+------------+-----------------------------------------------------+
| S | sign anchored to number (uses locale) |
+------------+-----------------------------------------------------+
| L | currency symbol (uses locale) |
+------------+-----------------------------------------------------+
| D | decimal point (uses locale) |
+------------+-----------------------------------------------------+
| G | group separator (uses locale) |
+------------+-----------------------------------------------------+
| MI | minus sign in specified position (if number < 0) |
+------------+-----------------------------------------------------+
| PL | plus sign in specified position (if number > 0) |
+------------+-----------------------------------------------------+
| SG | plus/minus sign in specified position |
+------------+-----------------------------------------------------+
| RN | Roman numeral (input between 1 and 3999) |
+------------+-----------------------------------------------------+
| TH or th | ordinal number suffix |
+------------+-----------------------------------------------------+
| V | shift specified number of digits (see notes) |
+------------+-----------------------------------------------------+
| EEEE | exponent for scientific notation |
+------------+-----------------------------------------------------+
Some additional formatting modifiers:
+---------------+-----------------------------------+---------------+
| Modifier | Description | Example |
+===============+===================================+===============+
| FM prefix | fill mode (suppress leading | FM99.99 |
| | zeroes and padding blanks) | |
+---------------+-----------------------------------+---------------+
| TH suffix | upper case ordinal number suffix | 999TH |
+---------------+-----------------------------------+---------------+
| th suffix | lower case ordinal number suffix | 999th |
+---------------+-----------------------------------+---------------+
================================================================================
.. File: sys.rst
.. _ref_std_sys:
======
System
======
:edb-alt-title: System Functions and Types
.. list-table::
:class: funcoptable
* - :eql:func:`sys::get_version`
- :eql:func-desc:`sys::get_version`
* - :eql:func:`sys::get_version_as_str`
- :eql:func-desc:`sys::get_version_as_str`
* - :eql:func:`sys::get_current_database`
- :eql:func-desc:`sys::get_current_database`
* - :eql:func:`sys::get_current_branch`
- :eql:func-desc:`sys::get_current_branch`
* - :eql:type:`sys::Branch`
- A read-only type representing all database branches.
* - :eql:type:`sys::QueryStats`
- A read-only type representing query performance statistics.
* - :eql:func:`sys::reset_query_stats`
- :eql:func-desc:`sys::reset_query_stats`
----------
.. eql:function:: sys::get_version() -> tuple<major: int64, \
minor: int64, \
stage: sys::VersionStage, \
stage_no: int64, \
local: array<str>>
Return the server version as a tuple.
The ``major`` and ``minor`` elements contain the major and the minor
components of the version; ``stage`` is an enumeration value containing
one of ``'dev'``, ``'alpha'``, ``'beta'``, ``'rc'`` or ``'final'``;
``stage_no`` is the stage sequence number (e.g. ``2`` in an alpha 2
release); and ``local`` contains an arbitrary array of local version
identifiers.
.. code-block:: edgeql-repl
db> select sys::get_version();
{(major := 1, minor := 0, stage := <sys::VersionStage>'alpha',
stage_no := 1, local := [])}
----------
.. eql:function:: sys::get_version_as_str() -> str
Return the server version as a string.
.. code-block:: edgeql-repl
db> select sys::get_version_as_str();
{'1.0-alpha.1'}
----------
.. eql:function:: sys::get_transaction_isolation() -> \
sys::TransactionIsolation
Return the isolation level of the current transaction.
Possible return values are given by
:eql:type:`sys::TransactionIsolation`.
.. code-block:: edgeql-repl
db> select sys::get_transaction_isolation();
{sys::TransactionIsolation.Serializable}
----------
.. eql:function:: sys::get_current_database() -> str
Return the name of the current database as a string.
.. note::
This function is deprecated in |Gel|.
Use :eql:func:`sys::get_current_branch` instead
(it works in the same way).
.. code-block:: edgeql-repl
db> select sys::get_current_database();
{'my_database'}
----------
.. eql:function:: sys::get_current_branch() -> str
.. versionadded:: 5.0
Return the name of the current database branch as a string.
.. code-block:: edgeql-repl
db> select sys::get_current_branch();
{'my_branch'}
-----------
.. eql:type:: sys::TransactionIsolation
:index: enum transaction isolation
:eql:type:`Enum <enum>` indicating the possible transaction
isolation modes.
This enum only accepts a value of ``Serializable``.
-----------
.. eql:type:: sys::Branch
.. versionadded:: 6.0
A read-only type representing all database branches.
:eql:synopsis:`name -> str`
The name of the branch.
:eql:synopsis:`last_migration -> str`
The name of the last migration applied to the branch.
-----------
.. eql:type:: sys::QueryStats
.. versionadded:: 6.0
A read-only type representing query performance statistics.
:eql:synopsis:`branch -> sys::Branch`
The :eql:type:`branch <sys::Branch>` this statistics entry was
collected in.
:eql:synopsis:`query -> str`
Text string of a representative query.
:eql:synopsis:`query_type -> sys::QueryType`
The :eql:type:`type <sys::QueryType>` of the query.
:eql:synopsis:`tag -> str`
Query tag, commonly specifies the origin of the query, e.g 'gel/cli'
for queries originating from the CLI. Clients can specify a tag for
easier query identification.
:eql:synopsis:`stats_since -> datetime`
Time at which statistics gathering started for this query.
:eql:synopsis:`minmax_stats_since -> datetime`
Time at which min/max statistics gathering started for this query
(fields ``min_plan_time``, ``max_plan_time``, ``min_exec_time`` and
``max_exec_time``).
All queries have to be planned by the backend before execution. The planned
statements are cached (managed by the Gel server) and reused if the same
query is executed multiple times.
:eql:synopsis:`plans -> int64`
Number of times the query was planned in the backend.
:eql:synopsis:`total_plan_time -> duration`
Total time spent planning the query in the backend.
:eql:synopsis:`min_plan_time -> duration`
Minimum time spent planning the query in the backend. This field will
be zero if the counter has been reset using the
:eql:func:`sys::reset_query_stats` function with the ``minmax_only``
parameter set to ``true`` and never been planned since.
:eql:synopsis:`max_plan_time -> duration`
Maximum time spent planning the query in the backend. This field will
be zero if the counter has been reset using the
:eql:func:`sys::reset_query_stats` function with the ``minmax_only``
parameter set to ``true`` and never been planned since.
:eql:synopsis:`mean_plan_time -> duration`
Mean time spent planning the query in the backend.
:eql:synopsis:`stddev_plan_time -> duration`
Population standard deviation of time spent planning the query in the
backend.
After planning, the query is usually executed in the backend, with the
result being forwarded to the client.
:eql:synopsis:`calls -> int64`
Number of times the query was executed.
:eql:synopsis:`total_exec_time -> duration`
Total time spent executing the query in the backend.
:eql:synopsis:`min_exec_time -> duration`
Minimum time spent executing the query in the backend. This field will
be zero until this query is executed first time after reset performed
by the :eql:func:`sys::reset_query_stats` function with the
``minmax_only`` parameter set to ``true``.
:eql:synopsis:`max_exec_time -> duration`
Maximum time spent executing the query in the backend. This field will
be zero until this query is executed first time after reset performed
by the :eql:func:`sys::reset_query_stats` function with the
``minmax_only`` parameter set to ``true``.
:eql:synopsis:`mean_exec_time -> duration`
Mean time spent executing the query in the backend.
:eql:synopsis:`stddev_exec_time -> duration`
Population standard deviation of time spent executing the query in the
backend.
:eql:synopsis:`rows -> int64`
Total number of rows retrieved or affected by the query.
The following properties are used to identify a unique statistics entry
(together with the query text above):
:eql:synopsis:`compilation_config -> std::json`
The config used to compile the query.
:eql:synopsis:`protocol_version -> tuple<major: int16, minor: int16>`
The version of the binary protocol receiving the query.
:eql:synopsis:`default_namespace -> str`
The default module/schema used to compile the query.
:eql:synopsis:`namespace_aliases -> json`
The module aliases used to compile the query.
:eql:synopsis:`output_format -> sys::OutputFormat`
The :eql:type:`OutputFormat <sys::OutputFormat>` indicated in the
binary protocol receiving the query.
:eql:synopsis:`expect_one -> bool`
Whether the query is expected to return exactly one row.
:eql:synopsis:`implicit_limit -> int64`
The implicit limit set for the query.
:eql:synopsis:`inline_typeids -> bool`
Whether type IDs are inlined in the query result.
:eql:synopsis:`inline_typenames -> bool`
Whether type names are inlined in the query result.
:eql:synopsis:`inline_objectids -> bool`
Whether object IDs are inlined in the query result.
-----------
.. eql:type:: sys::QueryType
.. versionadded:: 6.0
:eql:type:`Enum <enum>` indicating the possible query types.
Possible values are:
* ``EdgeQL``
* ``SQL``
-----------
.. eql:type:: sys::OutputFormat
.. versionadded:: 6.0
:eql:type:`Enum <enum>` indicating the possible output formats in a binary
protocol.
Possible values are:
* ``BINARY``
* ``JSON``
* ``JSON_ELEMENTS``
* ``NONE``
----------
.. eql:function:: sys::reset_query_stats( \
named only branch_name: OPTIONAL str = {}, \
named only id: OPTIONAL uuid = {}, \
named only minmax_only: OPTIONAL bool = false, \
) -> OPTIONAL datetime
.. versionadded:: 6.0
Discard selected query statistics gathered so far.
Discard query statistics gathered so far corresponding to the specified
``branch_name`` and ``id``. If either of the parameters is not specified,
the statistics that match with the other parameter will be reset. If no
parameter is specified, it will discard all statistics. When ``minmax_only``
is ``true``, only the values of minimum and maximum planning and execution
time will be reset (i.e. ``min_plan_time``, ``max_plan_time``,
``min_exec_time`` and ``max_exec_time`` fields). The default value for
``minmax_only`` parameter is ``false``. This function returns the time of a
reset. This time is saved to ``stats_reset`` or ``minmax_stats_since`` field
of :eql:type:`sys::QueryStats` if the corresponding reset was actually
performed.
.. code-block:: edgeql-repl
db> select sys::reset_query_stats();
{'2021-01-01T00:00:00Z'}
db> select sys::reset_query_stats(branch_name := 'my_branch');
{'2021-01-01T00:00:00Z'}
db> select sys::reset_query_stats(id := <uuid>'00000000-0000-0000-0000-000000000000');
{'2021-01-01T00:00:00Z'}
db> select sys::reset_query_stats(minmax_only := true);
{'2021-01-01T00:00:00Z'}
db> select sys::reset_query_stats(branch_name := 'no_such_branch');
{}
================================================================================
.. File: tuple.rst
.. _ref_std_tuple:
======
Tuples
======
A tuple type is a heterogeneous sequence of other types. Tuples can be either
*named* or *unnamed* (the default).
Constructing tuples
-------------------
A tuple constructor is an expression that consists of a sequence of
comma-separated expressions enclosed in parentheses. It produces a
tuple value:
.. eql:synopsis::
"(" <expr> [, ... ] ")"
Declare a *named tuple*:
.. eql:synopsis::
"(" <identifier> := <expr> [, ... ] ")"
*All* elements in a named tuple must have a name.
A tuple constructor automatically creates a corresponding
:ref:`tuple type <ref_eql_types_tuple>`.
.. _ref_std_tuple_accessor:
Accessing elements
------------------
An element of a tuple can be referenced in the form:
.. eql:synopsis::
<expr>.<element-index>
Here, :eql:synopsis:`<expr>` is any expression that has a tuple type,
and :eql:synopsis:`<element-index>` is either the *zero-based index*
of the element or the name of an element in a named tuple.
Examples:
.. code-block:: edgeql-repl
db> select (1, 'Gel').0;
{1}
db> select (number := 1, name := 'Gel').name;
{"Gel"}
db> select (number := 1, name := 'Gel').1;
{"Gel"}
Nesting tuples
--------------
Tuples can be nested:
.. code-block:: edgeql-repl
db> select (nested_tuple := (1, 2)).nested_tuple.0;
{1}
Referencing a non-existent tuple element will result in an error:
.. code-block:: edgeql-repl
db> select (1, 2).5;
EdgeQLError: 5 is not a member of a tuple
---- query context ----
line 1
> select (1, 2).3;
.. _ref_eql_types_tuple:
Type syntax
-----------
A tuple type can be explicitly declared in an expression or schema
declaration using the following syntax:
.. eql:synopsis::
tuple "<" <element-type>, [<element-type>, ...] ">"
A named tuple:
.. eql:synopsis::
tuple "<" <element-name> : <element-type> [, ... ] ">"
Any type can be used as a tuple element type.
Here's an example of using this syntax in a schema definition:
.. code-block:: sdl
type GameElement {
required name: str;
required position: tuple<x: int64, y: int64>;
}
Here's a few examples of using tuple types in EdgeQL queries:
.. code-block:: edgeql-repl
db> select <tuple<int64, str>>('1', 3);
{(1, '3')}
db> select <tuple<x: int64, y: int64>>(1, 2);
{(x := 1, y := 2)}
db> select (1, '3') is (tuple<int64, str>);
{true}
db> select ([1, 2], 'a') is (tuple<array<int64>, str>);
{true}
.. eql:type:: std::tuple
:index: tuple
A tuple type is a heterogeneous sequence of other types.
Tuple elements can optionally have names,
in which case the tuple is called a *named tuple*.
Any type can be used as a tuple element type.
A tuple type is created implicitly when a :ref:`tuple constructor
<ref_std_tuple>` is used:
.. code-block:: edgeql-repl
db> select ('foo', 42);
{('foo', 42)}
Two tuples are equal if all of their elements are equal and in the same
order. Note that element names in named tuples are not significant for
comparison:
.. code-block:: edgeql-repl
db> select (1, 2, 3) = (a := 1, b := 2, c := 3);
{true}
The syntax of a tuple type declaration can be found in :ref:`this
section <ref_eql_types_tuple>`.
================================================================================
.. File: type.rst
.. _ref_std_type:
=====
Types
=====
:edb-alt-title: Type Operators
.. list-table::
:class: funcoptable
* - :eql:op:`is type <is>`
- :eql:op-desc:`is`
* - :eql:op:`type | type <typeor>`
- :eql:op-desc:`typeor`
* - :eql:op:`<type> val <cast>`
- :eql:op-desc:`cast`
* - :eql:op:`typeof anytype <typeof>`
- :eql:op-desc:`typeof`
* - :eql:op:`introspect type <introspect>`
- :eql:op-desc:`introspect`
Finding an object's type
------------------------
You can find the type of an object via that object's ``__type__`` link, which
carries various information about the object's type, including the type's
``name``.
.. code-block:: edgeql-repl
db> select <json>Person {
... __type__: {
... name
... }
... } limit 1;
{Json("{\"__type__\": {\"name\": \"default::Villain\"}}")}
This information can be pulled into the top level by assigning a name to
the ``name`` property inside ``__type__``:
.. code-block:: edgeql-repl
db> select <json>Person { type := .__type__.name } limit 1;
{Json("{\"type\": \"default::Villain\"}")}
.. note::
There's nothing magical about the ``__type__`` link: it's a standard link
that exists on every object due to their inheritance from
:eql:type:`BaseObject`, linking to the current object's type.
----------
.. eql:operator:: is: anytype is type -> bool
anytype is not type -> bool
:index: is, type checking, compare, comparison
Type checking operator.
Check if ``A`` is an instance of ``B`` or any of ``B``'s subtypes.
Type-checking operators ``is`` and ``is not`` that
test whether the left operand is of any of the types given by the
comma-separated list of types provided as the right operand.
Note that ``B`` is special and is not any kind of expression, so
it does not in any way participate in the interactions of sets and
longest common prefix rules.
.. code-block:: edgeql-repl
db> select 1 is int64;
{true}
db> select User is not SystemUser
... filter User.name = 'Alice';
{true}
db> select User is (Text | Named);
{true, ..., true} # one for every user instance
----------
.. eql:operator:: typeor: type | type -> type
:index: \|, type union, polymorphism, polymorphic queries, nested shapes
Type union operator.
This operator is only valid in contexts where type checking is
done. The most obvious use case is with the :eql:op:`is` and
:eql:op:`is not <is>`. The operator allows to refer to a union of
types in order to check whether a value is of any of these
types.
.. code-block:: edgeql-repl
db> select User is (Text | Named);
{true, ..., true} # one for every user instance
It can similarly be used when specifying a link target type. The
same logic then applies: in order to be a valid link target the
object must satisfy ``object is (A | B | C)``.
.. code-block:: sdl
abstract type Named {
required name: str;
}
abstract type Text {
required body: str;
}
type Item extending Named;
type Note extending Text;
type User extending Named {
multi stuff: Named | Text;
}
With the above schema, the following would be valid:
.. code-block:: edgeql-repl
db> insert Item {name := 'cube'};
{Object { id: <uuid>'...' }}
db> insert Note {body := 'some reminder'};
{Object { id: <uuid>'...' }}
db> insert User {
... name := 'Alice',
... stuff := Note, # all the notes
... };
{Object { id: <uuid>'...' }}
db> insert User {
... name := 'Bob',
... stuff := Item, # all the items
... };
{Object { id: <uuid>'...' }}
db> select User {
... name,
... stuff: {
... [is Named].name,
... [is Text].body
... }
... };
{
Object {
name: 'Alice',
stuff: {Object { name: {}, body: 'some reminder' }}
},
Object {
name: 'Bob',
stuff: {Object { name: 'cube', body: {} }}
}
}
-----------
.. eql:operator:: cast: < type > anytype -> anytype
:index: <type>, type conversion, convert type
Type cast operator.
A type cast operator converts the specified value to another value of
the specified type:
.. eql:synopsis::
"<" <type> ">" <expression>
The :eql:synopsis:`<type>` must be a valid :ref:`type expression
<ref_eql_types>` denoting a non-abstract scalar or a container type.
Type cast is a run-time operation. The cast will succeed only if a
type conversion was defined for the type pair, and if the source value
satisfies the requirements of a target type. Gel allows casting any
scalar.
It is illegal to cast one :eql:type:`Object` into another. The
only way to construct a new :eql:type:`Object` is by using
:eql:stmt:`insert`. However, the :eql:op:`type intersection
<isintersect>` can be used to achieve an effect similar to
casting for Objects.
When a cast is applied to an expression of a known type, it represents a
run-time type conversion. The cast will succeed only if a suitable type
conversion operation has been defined.
Examples:
.. code-block:: edgeql-repl
db> # cast a string literal into an integer
... select <int64>"42";
{42}
db> # cast an array of integers into an array of str
... select <array<str>>[1, 2, 3];
{['1', '2', '3']}
db> # cast an issue number into a string
... select <str>example::Issue.number;
{'142'}
Casts also work for converting tuples or declaring different tuple
element names for convenience.
.. code-block:: edgeql-repl
db> select <tuple<int64, str>>(1, 3);
{[1, '3']}
db> with
... # a test tuple set, that could be a result of
... # some other computation
... stuff := (1, 'foo', 42)
... select (
... # cast the tuple into something more convenient
... <tuple<a: int64, name: str, b: int64>>stuff
... ).name; # access the 'name' element
{'foo'}
An important use of *casting* is in defining the type of an empty
set ``{}``, which can be required for purposes of type disambiguation.
.. code-block:: edgeql
with module example
select Text {
name :=
Text[is Issue].name if Text is Issue else
<str>{},
# the cast to str is necessary here, because
# the type of the computed expression must be
# defined
body,
};
Casting empty sets is also the only situation where casting into an
:eql:type:`Object` is valid:
.. code-block:: edgeql
with module example
select User {
name,
friends := <User>{}
# the cast is the only way to indicate that the
# computed link 'friends' is supposed to refer to
# a set of Users
};
For more information about casting between different types consult
the :ref:`casting table <ref_eql_casts_table>`.
-----------
.. eql:operator:: typeof: typeof anytype -> type
:index: typeof, introspection
Static type inference operator.
This operator converts an expression into a type, which can be
used with :eql:op:`is`, :eql:op:`is not<is>`, and
:eql:op:`introspect`.
Currently, ``typeof`` operator only supports :ref:`scalars
<ref_datamodel_scalar_types>` and :ref:`objects
<ref_datamodel_object_types>`, but **not** the :ref:`collections
<ref_datamodel_collection_types>` as a valid operand.
Consider the following types using links and properties with names
that don't indicate their respective target types:
.. code-block:: sdl
type Foo {
bar: int16;
baz: Bar;
}
type Bar extending Foo;
We can use ``typeof`` to determine if certain expression has the
same type as the property ``bar``:
.. code-block:: edgeql-repl
db> insert Foo { bar := 1 };
{Object { id: <uuid>'...' }}
db> select (Foo.bar / 2) is typeof Foo.bar;
{false}
To determine the actual resulting type of an expression we can
use :eql:op:`introspect`:
.. code-block:: edgeql-repl
db> select introspect (typeof Foo.bar).name;
{'std::int16'}
db> select introspect (typeof (Foo.bar / 2)).name;
{'std::float64'}
Similarly, we can use ``typeof`` to discriminate between the
different ``Foo`` objects that can and cannot be targets of link
``baz``:
.. code-block:: edgeql-repl
db> insert Bar { bar := 2 };
{Object { id: <uuid>'...' }}
db> select Foo {
... bar,
... can_be_baz := Foo is typeof Foo.baz
... };
{
Object { bar: 1, can_be_baz: false },
Object { bar: 2, can_be_baz: true }
}
-----------
.. eql:operator:: introspect: introspect type -> schema::Type
:index: introspect, type introspection, typeof
Static type introspection operator.
This operator returns the :ref:`introspection type
<ref_datamodel_introspection>` corresponding to type provided as
operand. It works well in combination with :eql:op:`typeof`.
Currently, the ``introspect`` operator only supports :ref:`scalar
types <ref_datamodel_scalar_types>` and :ref:`object types
<ref_datamodel_object_types>`, but **not** the :ref:`collection
types <ref_datamodel_collection_types>` as a valid operand.
Consider the following types using links and properties with names
that don't indicate their respective target types:
.. code-block:: sdl
type Foo {
bar: int16;
baz: Bar;
}
type Bar extending Foo;
.. code-block:: edgeql-repl
db> select (introspect int16).name;
{'std::int16'}
db> select (introspect Foo).name;
{'default::Foo'}
db> select (introspect typeof Foo.bar).name;
{'std::int16'}
.. note::
For any :ref:`object type <ref_datamodel_object_types>`
``SomeType`` the expressions ``introspect SomeType`` and
``introspect typeof SomeType`` are equivalent as the object
type name is syntactically identical to the *expression*
denoting the set of those objects.
There's an important difference between the combination of
``introspect typeof SomeType`` and ``SomeType.__type__``
expressions when used with objects. ``introspect typeof SomeType``
is statically evaluated and does not take in consideration the
actual objects contained in the ``SomeType`` set. Conversely,
``SomeType.__type__`` is the actual set of all the types reachable
from all the ``SomeType`` objects. Due to inheritance statically
inferred types and actual types may not be the same (although the
actual types will always be a subtype of the statically inferred
types):
.. code-block:: edgeql-repl
db> # first let's make sure we don't have any Foo objects
... delete Foo;
{ there may be some deleted objects here }
db> select (introspect typeof Foo).name;
{'default::Foo'}
db> select Foo.__type__.name;
{}
db> # let's add an object of type Foo
... insert Foo;
{Object { id: <uuid>'...' }}
db> # Bar is also of type Foo
... insert Bar;
{Object { id: <uuid>'...' }}
db> select (introspect typeof Foo).name;
{'default::Foo'}
db> select Foo.__type__.name;
{'default::Bar', 'default::Foo'}
================================================================================
.. File: uuid.rst
.. _ref_std_uuid:
=====
UUIDs
=====
.. list-table::
:class: funcoptable
* - :eql:type:`uuid`
- UUID type
* - :eql:op:`= <eq>` :eql:op:`\!= <neq>` :eql:op:`?= <coaleq>`
:eql:op:`?!= <coalneq>` :eql:op:`\< <lt>` :eql:op:`\> <gt>`
:eql:op:`\<= <lteq>` :eql:op:`\>= <gteq>`
- Comparison operators
* - :eql:func:`uuid_generate_v1mc`
- :eql:func-desc:`uuid_generate_v1mc`
* - :eql:func:`uuid_generate_v4`
- :eql:func-desc:`uuid_generate_v4`
* - :eql:func:`to_uuid`
- :eql:func-desc:`to_uuid`
---------
.. eql:type:: std::uuid
Universally Unique Identifiers (UUID).
For formal definition see RFC 4122 and ISO/IEC 9834-8:2005.
Every :eql:type:`Object` has a globally unique property ``id``
represented by a UUID value.
A UUID can be cast to an object type if an object of that type with a
matching ID exists.
.. code-block:: edgeql-repl
db> select <Hero><uuid>'01d9cc22-b776-11ed-8bef-73f84c7e91e7';
{default::Hero {id: 01d9cc22-b776-11ed-8bef-73f84c7e91e7}}
---------
.. eql:function:: std::uuid_generate_v1mc() -> uuid
Return a version 1 UUID.
The algorithm uses a random multicast MAC address instead of the
real MAC address of the computer.
The UUID will contain 47 random bits, 60 bits representing the
current time, and 14 bits of clock sequence that may be used to
ensure uniqueness. The rest of the bits indicate the version of
the UUID.
This is the default function used to populate the ``id`` column.
.. code-block:: edgeql-repl
db> select uuid_generate_v1mc();
{1893e2b6-57ce-11e8-8005-13d4be166783}
---------
.. eql:function:: std::uuid_generate_v4() -> uuid
Return a version 4 UUID.
The UUID is derived entirely from random numbers: it will contain
122 random bits and 6 version bits.
It is permitted to override the ``default`` of the ``id`` column
with a call to this function, but this should be done with
caution: fully random ids will be less clustered than time-based id,
which may lead to worse index performance.
.. code-block:: edgeql-repl
db> select uuid_generate_v4();
{92673afc-9c4f-42b3-8273-afe0053f0f48}
---------
.. eql:function:: std::to_uuid(val: bytes) -> uuid
:index: parse uuid
Returns a :eql:type:`uuid` value parsed from 128-bit input.
The :eql:type:`bytes` string has to be a valid 128-bit UUID
representation.
.. code-block:: edgeql-repl
db> select to_uuid(
... b'\x92\x67\x3a\xfc\
... \x9c\x4f\
... \x42\xb3\
... \x82\x73\
... \xaf\xe0\x05\x3f\x0f\x48');
{92673afc-9c4f-42b3-8273-afe0053f0f48}
================================================================================
.. File: delete.rst
.. _gel-js-delete:
Delete
------
Delete objects with ``e.delete``.
.. code-block:: typescript
e.delete(e.Movie, movie => ({
filter: e.op(movie.release_year, ">", 2000),
filter_single: { id: "abc..." },
order_by: movie.title,
offset: 10,
limit: 10
}));
The only supported keys are ``filter``, ``filter_single``, ``order_by``,
``offset``, and ``limit``.
================================================================================
.. File: driver.rst
.. _gel-js-driver:
Client
======
The ``Client`` class implements the basic functionality required to establish a
connection to your database and execute queries.
.. _gel-js-create-client:
Creating clients
----------------
A *client* represents a connection to your database and provides methods for
executing queries.
.. note::
In actuality, the client maintains a *pool* of connections under the hood.
When your server is under load, queries will be run in parallel across many
connections, instead of being bottlenecked by a single connection.
To create a client:
.. code-block:: javascript
const gel = require("gel");
const client = gel.createClient();
If you're using TypeScript or have ES modules enabled, you can use
``import`` syntax instead:
.. code-block:: javascript
import * as gel from "gel";
const client = gel.createClient();
Connections
^^^^^^^^^^^
Notice we didn't pass any arguments into ``createClient``. That's intentional.
**In development**, we recommend using :gelcmd:`project init` to create an
instance and link it to your project directory. As long as you're inside this
directory, ``createClient()`` with auto-detect the project and connect to the
associated instance automatically.
**In production** you should use environment variables to provide connection
information to ``createClient``. See the :ref:`Connection parameters
<ref_reference_connection>` docs for details.
Configuring clients
^^^^^^^^^^^^^^^^^^^
Clients can be configured using a set of *immutable* methods that start with
``with``.
.. note::
These methods return a *new Client instance* that *shares a connection pool*
with the original client! This is important. Each call to ``createClient``
instantiates a new connection pool.
The code example below demonstrates all available configuration settings. The
value specified below is the *default value* for that setting.
.. code-block:: typescript
import {createClient, Duration, IsolationLevel} from "gel";
const baseClient = createClient();
const client = baseClient
.withConfig({
// 10 seconds
session_idle_transaction_timeout: Duration.from({seconds: 10}),
// 0 seconds === no timeout
query_execution_timeout: Duration.from({seconds: 0}),
allow_bare_ddl: "NeverAllow",
allow_user_specified_id: false,
apply_access_policies: true,
})
.withRetryOptions({
attempts: 3,
backoff: (attemptNo: number) => {
// exponential backoff
return 2 ** attemptNo * 100 + Math.random() * 100;
},
})
.withTransactionOptions({
isolation: IsolationLevel.Serializable, // only supported value
deferrable: false,
readonly: false,
});
Running queries
---------------
To execute a basic query:
.. code-block:: javascript
const gel = require("gel");
const client = gel.createClient();
async function main() {
const result = await client.query(`select 2 + 2;`);
console.log(result); // [4]
}
.. _gel-js-typescript:
In TypeScript, you can supply a type hint to receive a strongly typed result.
.. code-block:: javascript
const result = await client.query<number>(`select 2 + 2;`);
// number[]
``.query`` method
^^^^^^^^^^^^^^^^^
The ``.query`` method always returns an array of results. It places no
constraints on cardinality.
.. code-block:: javascript
await client.query(`select 2 + 2;`); // [4]
await client.query(`select [1, 2, 3];`); // [[1, 2, 3]]
await client.query(`select <int64>{};`); // []
await client.query(`select {1, 2, 3};`); // [1, 2, 3]
``.querySingle`` method
^^^^^^^^^^^^^^^^^^^^^^^
If you know your query will only return a single element, you can tell |Gel|
to expect a *singleton result* by using the ``.querySingle`` method. This is
intended for queries that return *zero or one* elements. If the query returns
a set with more than one elements, the ``Client`` will throw a runtime error.
.. note::
Note that if you're selecting an array or tuple, the returned value may
still be an array.
.. code-block:: javascript
await client.querySingle(`select 2 + 2;`); // 4
await client.querySingle(`select [1, 2, 3];`); // [1, 2, 3]
await client.querySingle(`select <int64>{};`); // null
await client.querySingle(`select {1, 2, 3};`); // Error
``.queryRequiredSingle`` method
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Use ``queryRequiredSingle`` for queries that return *exactly one* element. If
the query returns an empty set or a set with multiple elements, the ``Client``
will throw a runtime error.
.. code-block:: javascript
await client.queryRequiredSingle(`select 2 + 2;`); // 4
await client.queryRequiredSingle(`select [1, 2, 3];`); // [1, 2, 3]
await client.queryRequiredSingle(`select <int64>{};`); // Error
await client.queryRequiredSingle(`select {1, 2, 3};`); // Error
TypeScript
^^^^^^^^^^
The TypeScript signatures of these methods reflects their behavior.
.. code-block:: typescript
await client.query<number>(`select 2 + 2;`);
// number[]
await client.querySingle<number>(`select 2 + 2;`);
// number | null
await client.queryRequiredSingle<number>(`select 2 + 2;`);
// number
Type conversion
---------------
The client converts |Gel| types into a corresponding JavaScript data
structure. Some Gel types like ``duration`` don't have a corresponding type
in the JavaScript type system, so we've implemented classes like
:js:class:`Duration` to represent them.
.. list-table::
* - **Gel type**
- **JavaScript type**
* - Sets
- ``Array``
* - Arrays
- ``Array``
* - Tuples ``tuple<x, y, ...>``
- ``Array``
* - Named tuples ``tuple<foo: x, bar: y, ...>``
- ``object``
* - Enums
- ``string``
* - ``Object``
- ``object``
* - ``str``
- ``string``
* - ``bool``
- ``boolean``
* - ``float32`` ``float64`` ``int16`` ``int32`` ``int64``
- ``number``
* - ``json``
- ``string``
* - ``uuid``
- ``string``
* - ``bigint``
- ``BigInt``
* - ``decimal``
- ``string``
* - ``bytes``
- ``Uint8Array``
* - ``datetime``
- ``Date``
* - ``duration``
- :js:class:`Duration`
* - ``e.cal.relative_duration``
- :js:class:`RelativeDuration`
* - ``e.cal.date_duration``
- :js:class:`DateDuration`
* - ``cal::local_date``
- :js:class:`LocalDate`
* - ``cal::local_time``
- :js:class:`LocalTime`
* - ``cal::local_datetime``
- :js:class:`LocalDateTime`
* - ``cfg::memory``
- :js:class:`ConfigMemory`
* - Ranges ``range<x>``
- :js:class:`Range`
To learn more about the client's built-in type classes, refer to the reference
documentation.
- :js:class:`Duration`
- :js:class:`RelativeDuration`
- :js:class:`DateDuration`
- :js:class:`LocalDate`
- :js:class:`LocalTime`
- :js:class:`LocalDateTime`
- :js:class:`ConfigMemory`
- :js:class:`Range`
JSON results
------------
Client provide additional methods for running queries and retrieving results
as a *serialized JSON string*. This serialization happens inside the database
and is typically more performant than running ``JSON.stringify`` yourself.
.. code-block:: javascript
await client.queryJSON(`select {1, 2, 3};`);
// "[1, 2, 3]"
await client.querySingleJSON(`select <int64>{};`);
// "null"
await client.queryRequiredSingleJSON(`select 3.14;`);
// "3.14"
Non-returning queries
---------------------
To execute a query without retrieving a result, use the ``.execute`` method.
This is especially useful for mutations, where there's often no need for the
query to return a value.
.. code-block:: javascript
await client.execute(`insert Movie {
title := "Avengers: Endgame"
};`);
You can also execute a "script" consisting of multiple
semicolon-separated statements in a single ``.execute`` call.
.. code-block:: javascript
await client.execute(`
insert Person { name := "Robert Downey Jr." };
insert Person { name := "Scarlett Johansson" };
insert Movie {
title := <str>$title,
actors := (
select Person filter .name in {
"Robert Downey Jr.",
"Scarlett Johansson"
}
)
}
`, { title: "Iron Man 2" });
Parameters
----------
If your query contains parameters (e.g. ``$foo``), you can pass in values as
the second argument. This is true for all ``query*`` methods and ``execute``.
.. code-block:: javascript
const INSERT_MOVIE = `insert Movie {
title := <str>$title
}`
const result = await client.querySingle(INSERT_MOVIE, {
title: "Iron Man"
});
console.log(result);
// {id: "047c5893..."}
Remember that :ref:`parameters <ref_eql_params>` can only be *scalars* or
*arrays of scalars*.
Scripts
-------
Both ``execute`` and the ``query*`` methods support scripts (queries
containing multiple statements). The statements are run in an implicit
transaction (unless already in an explicit transaction), so the whole script
remains atomic. For the ``query*`` methods only the result of the final
statement in the script will be returned.
.. code-block:: javascript
const result = await client.query(`
insert Movie {
title := <str>$title
};
insert Person {
name := <str>$name
};
`, {
title: "Thor: Ragnarok",
name: "Anson Mount"
});
// [{id: "5dd2557b..."}]
For more fine grained control of atomic exectution of multiple statements, use
the ``transaction()`` API.
Checking connection status
--------------------------
The client maintains a dynamically sized *pool* of connections under the hood.
These connections are initialized *lazily*, so no connection will be
established until the first time you execute a query.
If you want to explicitly ensure that the client is connected without running
a query, use the ``.ensureConnected()`` method.
.. code-block:: javascript
const gel = require("gel");
const client = gel.createClient();
async function main() {
await client.ensureConnected();
}
.. _gel-js-api-transaction:
Transactions
------------
The most robust way to execute transactional code is to use
the ``transaction()`` API:
.. code-block:: javascript
await client.transaction(tx => {
await tx.execute("insert User {name := 'Don'}");
});
Note that we execute queries on the ``tx`` object in the above
example, rather than on the original ``client`` object.
The ``transaction()`` API guarantees that:
1. Transactions are executed atomically;
2. If a transaction fails due to retryable error (like
a network failure or a concurrent update error), the transaction
would be retried;
3. If any other, non-retryable error occurs, the transaction is rolled
back and the ``transaction()`` block throws.
The *transaction* object exposes ``query()``, ``execute()``, ``querySQL()``,
``executeSQL()``, and other ``query*()`` methods that *clients* expose, with
the only difference that queries will run within the current transaction
and can be retried automatically.
The key implication of retrying transactions is that the entire
nested code block can be re-run, including any non-querying
JavaScript code. Here is an example:
.. code-block:: javascript
const email = "timmy@example.com"
await client.transaction(async tx => {
await tx.execute(
`insert User { email := <str>$email }`,
{ email },
)
await sendWelcomeEmail(email);
await tx.execute(
`insert LoginHistory {
user := (select User filter .email = <str>$email),
timestamp := datetime_current()
}`,
{ email },
)
})
In the above example, the welcome email may be sent multiple times if the
transaction block is retried. Generally, the code inside the transaction block
shouldn't have side effects or run for a significant amount of time.
.. note::
Transactions allocate expensive server resources and having
too many concurrently running long-running transactions will
negatively impact the performance of the DB server.
.. note::
* RFC1004_
* :js:meth:`Client.transaction\<T\>`
.. _RFC1004: https://github.com/geldata/rfcs/blob/master/text/1004-transactions-api.rst
Next up
-------
If you're a TypeScript user and want autocompletion and type inference, head
over to the :ref:`Query Builder docs <gel-js-qb>`. If you're using plain
JavaScript that likes writing queries with composable code-first syntax, you
should check out the query builder too! If you're content writing queries as
strings, the vanilla Client API will meet your needs.
================================================================================
.. File: for.rst
.. _gel-js-for:
For Loops
=========
``for`` loops let you iterate over any set of values.
.. code-block:: typescript
const query = e.for(e.set(1, 2, 3, 4), (number) => {
return e.op(2, '^', number);
});
const result = query.run(client);
// [2, 4, 8, 16]
.. _gel-js-for-bulk-inserts:
Bulk inserts
------------
It's common to use ``for`` loops to perform bulk inserts. The raw data is
passed in as a ``json`` parameter, converted to a set of ``json`` objects with
``json_array_unpack``, then passed into a ``for`` loop for insertion.
.. code-block:: typescript
const query = e.params({ items: e.json }, (params) => {
return e.for(e.json_array_unpack(params.items), (item) => {
return e.insert(e.Movie, {
title: e.cast(e.str, item.title),
release_year: e.cast(e.int64, item.release_year),
});
});
});
const result = await query.run(client, {
items: [
{ title: "Deadpool", release_year: 2016 },
{ title: "Deadpool 2", release_year: 2018 },
{ title: "Deadpool 3", release_year: 2024 },
{ title: "Deadpool 4", release_year: null },
],
});
Note that any optional properties values must be explicitly set to ``null``.
They cannot be set to ``undefined`` or omitted; doing so will cause a runtime
error.
.. _gel-js-for-bulk-inserts-conflicts:
Handling conflicts in bulk inserts
----------------------------------
Here's a more complex example, demonstrating how to complete a nested insert
with conflicts on the inner items. First, take a look at the schema for this
database:
.. code-block:: sdl
module default {
type Character {
required name: str {
constraint exclusive;
}
portrayed_by: str;
multi movies: Movie;
}
type Movie {
required title: str {
constraint exclusive;
};
release_year: int64;
}
}
Note that the ``Movie`` type's ``title`` property has an exclusive constraint.
Here's the data we want to bulk insert:
.. code-block:: javascript
[
{
portrayed_by: "Robert Downey Jr.",
name: "Iron Man",
movies: ["Iron Man", "Iron Man 2", "Iron Man 3"]
},
{
portrayed_by: "Chris Evans",
name: "Captain America",
movies: [
"Captain America: The First Avenger",
"The Avengers",
"Captain America: The Winter Soldier",
]
},
{
portrayed_by: "Mark Ruffalo",
name: "The Hulk",
movies: ["The Avengers", "Iron Man 3", "Avengers: Age of Ultron"]
}
]
This is potentially a problem because some of the characters appear in the same
movies. We can't just naively insert all the movies because we'll eventually
hit a conflict. Since we're going to write this as a single query, chaining
``.unlessConflict`` on our query won't help. It only handles conflicts with
objects that existed *before* the current query.
Let's look at a query that can accomplish this insert, and then we'll break it
down.
.. code-block:: typescript
const query = e.params(
{
characters: e.array(
e.tuple({
portrayed_by: e.str,
name: e.str,
movies: e.array(e.str),
})
),
},
(params) => {
const movies = e.for(
e.op(
"distinct",
e.array_unpack(e.array_unpack(params.characters).movies)
),
(movieTitle) => {
return e
.insert(e.Movie, {
title: movieTitle,
})
.unlessConflict((movie) => ({
on: movie.title,
else: movie,
}));
}
);
return e.with(
[movies],
e.for(e.array_unpack(params.characters), (character) => {
return e.insert(e.Character, {
name: character.name,
portrayed_by: character.portrayed_by,
movies: e.assert_distinct(
e.select(movies, (movie) => ({
filter: e.op(movie.title, "in", e.array_unpack(character.movies)),
}))
),
});
})
);
}
);
.. _gel-js-for-bulk-inserts-conflicts-params:
Structured params
~~~~~~~~~~~~~~~~~
.. code-block:: typescript
const query = e.params(
{
characters: e.array(
e.tuple({
portrayed_by: e.str,
name: e.str,
movies: e.array(e.str),
})
),
},
(params) => { ...
In raw EdgeQL, you can only have scalar types as parameters. We could mirror
that here with something like this: ``e.params({characters: e.json})``, but
this would then require us to cast all the values inside the JSON like
``portrayed_by`` and ``name``.
By doing it this way — typing ``characters`` with ``e.array`` and the character
objects as named tuples by passing an object to ``e.tuple`` — all the data in
the array will be properly cast for us. It will also better type check the data
you pass to the query's ``run`` method.
.. _gel-js-for-bulk-inserts-conflicting-data:
Inserting the inner conflicting data
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: typescript
...
(params) => {
const movies = e.for(
e.op("distinct", e.array_unpack(e.array_unpack(params.characters).movies)),
(movie) => {
return e
.insert(e.Movie, {
title: movie,
})
.unlessConflict((movie) => ({
on: movie.title,
else: movie,
}));
}
);
...
We need to separate this movie insert query so that we can use ``distinct`` on
it. We could just nest an insert inside our character insert if movies weren't
duplicated across characters (e.g., two characters have "The Avengers" in
``movies``). Even though the query is separated from the character inserts
here, it will still be built as part of a single |Gel| query using ``with``
which we'll get to a bit later.
The ``distinct`` operator can only operate on sets. We use ``array_unpack`` to
make these arrays into sets. We need to call it twice because
``params.characters`` is an array and ``.movies`` is an array nested inside
each character.
Chaining ``unlessConflict`` takes care of any movies that already exist in the
database *before* we run this query, but it won't handle conflicts that come
about over the course of this query. The ``distinct`` operator we used earlier
pro-actively eliminates any conflicts we might have had among this data.
.. _gel-js-for-bulk-inserts-outer-data:
Inserting the outer data
~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: typescript
...
return e.with(
[movies],
e.for(e.array_unpack(params.characters), (character) => {
return e.insert(e.Character, {
name: character.name,
portrayed_by: character.portrayed_by,
movies: e.assert_distinct(
e.select(movies, (movie) => ({
filter: e.op(movie.title, "in", e.array_unpack(character.movies)),
}))
),
});
})
);
...
The query builder will try to automatically use EdgeQL's ``with``, but in this
instance, it doesn't know where to place the ``with``. By using ``e.with``
explicitly, we break our movie insert out to the top-level of the query. By
default, it would be scoped *inside* the query, so our ``distinct`` operator
would be applied only to each character's movies instead of to all of the
movies. This would have caused the query to fail.
The rest of the query is relatively straightforward. We unpack
``params.characters`` to a set so that we can pass it to ``e.for`` to iterate
over the characters. For each character, we build an ``insert`` query with
their ``name`` and ``portrayed_by`` values.
For the character's ``movies``, we ``select`` everything in the
``movies`` insert query we wrote previously, filtering for those with titles
that match values in the ``character.movies`` array.
All that's left is to run the query, passing the data to the query's ``run``
method!
.. _gel-js-for-bulk-updates:
Bulk updates
^^^^^^^^^^^^
Just like with inserts, you can run bulk updates using a ``for`` loop. Pass in
your data, iterate over it, and build an ``update`` query for each item.
In this example, we use ``name`` to filter for the character to be updated
since ``name`` has an exclusive constraint in the schema (meaning a given name
will correspond to, at most, a single object). That filtering is done using the
``filter_single`` property of the object returned from your ``update``
callback. Then the ``last_appeared`` value is updated by including it in the
nested ``set`` object.
.. code-block:: typescript
const query = e.params(
{
characters: e.array(
e.tuple({
name: e.str,
last_appeared: e.int64,
})
),
},
(params) => {
return e.for(e.array_unpack(params.characters), (character) => {
return e.update(e.Character, () => ({
filter_single: { name: character.name },
set: {
last_appeared: character.last_appeared,
},
}));
});
}
);
await query.run(client, {
characters: [
{ name: "Iron Man", last_appeared: 2019 },
{ name: "Captain America", last_appeared: 2019 },
{ name: "The Hulk", last_appeared: 2021 },
],
});
e.for vs JS for or .forEach
^^^^^^^^^^^^^^^^^^^^^^^^^^^
You may be tempted to use JavaScript's ``for`` or the JavaScript array's
``.forEach`` method to avoid having to massage your data into a set for
consumption by ``e.for``. This approach comes at a cost of performance.
If you use ``for`` or ``.forEach`` to iterate over a standard JavaScript data
structure and run separate queries for each item in your iterable, you're doing
just that: running separate queries for each item in your iterable. By
iterating inside your query using ``e.for``, you're guaranteed everything will
happen in a single query.
In addition to the performance implications, a single query means that either
everything succeeds or everything fails. You will never end up with only some
of your data inserted. This ensures your data integrity is maintained. You
could achieve this yourself by wrapping your batch queryies with :ref:`a
transaction <gel-js-qb-transaction>`, but a single query is already atomic
without any additional work on your part.
Using ``e.for`` to run a single query is generally the best approach. When
dealing with extremely large datasets, it may become more practical to batch
queries and run them individually.
================================================================================
.. File: funcops.rst
.. _gel-js-funcops:
Functions and Operators
-----------------------
Function syntax
^^^^^^^^^^^^^^^
All built-in standard library functions are reflected as functions in ``e``.
.. code-block:: typescript
e.str_upper(e.str("hello"));
// str_upper("hello")
e.op(e.int64(2), '+', e.int64(2));
// 2 + 2
const nums = e.set(e.int64(3), e.int64(5), e.int64(7))
e.op(e.int64(4), 'in', nums);
// 4 in {3, 5, 7}
e.math.mean(nums);
// math::mean({3, 5, 7})
.. _gel-js-funcops-prefix:
Prefix operators
^^^^^^^^^^^^^^^^
Unlike functions, operators do *not* correspond to a top-level function on the
``e`` object. Instead, they are expressed with the ``e.op`` function.
Prefix operators operate on a single argument: ``OPERATOR <arg>``.
.. code-block:: typescript
e.op('not', e.bool(true)); // not true
e.op('exists', e.set('hi')); // exists {'hi'}
e.op('distinct', e.set('hi', 'hi')); // distinct {'hi', 'hi'}
.. list-table::
* - ``"exists"`` ``"distinct"`` ``"not"``
.. _gel-js-funcops-infix:
Infix operators
^^^^^^^^^^^^^^^
Infix operators operate on two arguments: ``<arg> OPERATOR <arg>``.
.. code-block:: typescript
e.op(e.str('Hello '), '++', e.str('World!'));
// 'Hello ' ++ 'World!'
.. list-table::
* - ``"="`` ``"?="`` ``"!="`` ``"?!="`` ``">="`` ``">"`` ``"<="`` ``"<"``
``"or"`` ``"and"`` ``"+"`` ``"-"`` ``"*"`` ``"/"`` ``"//"`` ``"%"``
``"^"`` ``"in"`` ``"not in"`` ``"union"`` ``"??"`` ``"++"`` ``"like"``
``"ilike"`` ``"not like"`` ``"not ilike"``
.. _gel-js-funcops-ternary:
Ternary operators
^^^^^^^^^^^^^^^^^
Ternary operators operate on three arguments: ``<arg> OPERATOR <arg> OPERATOR
<arg>``. Currently there's only one ternary operator: the ``if else``
statement.
.. code-block:: typescript
e.op(e.str('😄'), 'if', e.bool(true), 'else', e.str('😢'));
// 😄 if true else 😢
================================================================================
.. File: generation.rst
.. _gel-js-generators:
Generators
==========
The ``@gel/generate`` package provides a set of code generation tools that
are useful when developing an Gel-backed applications with
TypeScript/JavaScript.
To get started with generators, first initialize an :ref:`Gel project
<ref_guide_using_projects>` in the root of your application. Generators will
look for an |gel.toml| file to determine the root of your application. See
the :ref:`Overview <gel-js-installation>` page for details on installing.
.. note::
Generators work by connecting to the database to get information about the current state of the schema. Make sure you run the generators again any time the schema changes so that the generated code is in-sync with the current state of the schema.
Run a generator with the following command.
.. tabs::
.. code-tab:: bash
:caption: npm
$ npx @gel/generate <generator> [options]
.. code-tab:: bash
:caption: yarn
$ yarn run -B generate <generator> [options]
.. code-tab:: bash
:caption: pnpm
$ pnpm exec generate <generator> [options]
.. code-tab:: bash
:caption: Deno
$ deno run \
--allow-all \
--unstable \
https://deno.land/x/gel/generate.ts <generator> [options]
.. code-tab:: bash
:caption: bun
$ bunx @gel/generate <generator> [options]
The value of ``<generator>`` should be one of the following:
.. list-table::
:class: funcoptable
* - ``edgeql-js``
- Generates the query builder which provides a **code-first** way to write
**fully-typed** EdgeQL queries with TypeScript. We recommend it for
TypeScript users, or anyone who prefers writing queries with code.
- :ref:`docs <gel-js-qb>`
* - ``queries``
- Scans your project for ``*.edgeql`` files and generates functions that
allow you to execute these queries in a typesafe way.
- :ref:`docs <gel-js-queries>`
* - ``interfaces``
- Introspects your schema and generates file containing *TypeScript
interfaces* that correspond to each object type. This is useful for
writing typesafe code to interact with |Gel|.
- :ref:`docs <gel-js-interfaces>`
Connection
^^^^^^^^^^
The generators require a connection to an active |Gel| database. It does
**not** simply read your local |.gel| schema files. Generators rely on the
database to introspect the schema and analyze queries. Doing so without a
database connection would require implementing a full EdgeQL parser and static
analyzer in JavaScript—which we don't intend to do anytime soon.
.. note::
Make sure your development database is up-to-date with your latest schema
before running a generator!
If you're using :gelcmd:`project init`, the connection is automatically handled
for you. Otherwise, you'll need to explicitly pass connection information via
environment variables or CLI flags, just like any other CLI command. See
:ref:`Client Libraries > Connection <gel_client_connection>` for guidance.
.. _gel_qb_target:
Targets
^^^^^^^
All generators look at your environment and guess what kind of files to generate
(``.ts`` vs ``.js + .d.ts``) and what module system to use (CommonJS vs ES
modules). You can override this with the ``--target`` flag.
.. list-table::
* - ``--target ts``
- Generate TypeScript files (``.ts``)
* - ``--target mts``
- Generate TypeScript files (``.mts``) with extensioned ESM imports
* - ``--target esm``
- Generate ``.js`` with ESM syntax and ``.d.ts`` declaration files
* - ``--target cjs``
- Generate JavaScript with CommonJS syntax and and ``.d.ts`` declaration
files
* - ``--target deno``
- Generate TypeScript files with Deno-style ESM imports
Help
^^^^
To see helptext for the ``@gel/generate`` command, run the following.
.. code-block:: bash
$ npx @gel/generate --help
================================================================================
.. File: group.rst
.. _gel-js-group:
Group
=====
The ``group`` statement provides a powerful mechanism for categorizing a set
of objects (e.g., movies) into *groups*. You can group by properties,
expressions, or combinatations thereof.
.. note::
This page does not aim to describe how the ``group`` statement works, merely
the syntax for writing ``e.group`` statements with the query builder. For
full documentation, refer to :ref:`EdgeQL > Group <ref_eql_group>`.
Simple grouping
---------------
Sort a set of objects by a simple property.
.. tabs::
.. code-tab:: typescript
e.group(e.Movie, movie => {
return {
by: {release_year: movie.release_year}
}
});
/*
[
{
key: {release_year: 2008},
grouping: ["release_year"],
elements: [{id: "..."}, {id: "..."}]
},
{
key: { release_year: 2009 },
grouping: ["release_year"],
elements: [{id: "..."}, {id: "..."}]
},
// ...
]
*/
.. code-tab:: edgeql
group Movie
by .release_year
Add a shape that will be applied to ``elements``. The ``by`` key is a special
key, similar to ``filter``, etc. in ``e.select``. All other keys are
interpreted as *shape elements* and support the same functionality as
``e.select`` (nested shapes, computeds, etc.).
.. tabs::
.. code-tab:: typescript
e.group(e.Movie, movie => {
return {
title: true,
actors: {name: true},
num_actors: e.count(movie.characters),
by: {release_year: movie.release_year}
}
});
/* [
{
key: {release_year: 2008},
grouping: ["release_year"],
elements: [{
title: "Iron Man",
actors: [...],
num_actors: 5
}, {
title: "The Incredible Hulk",
actors: [...],
num_actors: 3
}]
},
// ...
] */
.. code-tab:: edgeql
group Movie {
title,
num_actors := count(.actors)
}
by .release_year
Group by a tuple of properties.
.. tabs::
.. code-tab:: typescript
e.group(e.Movie, movie => {
const release_year = movie.release_year;
const first_letter = movie.title[0];
return {
title: true,
by: {release_year, first_letter}
};
});
/*
[
{
key: {release_year: 2008, first_letter: "I"},
grouping: ["release_year", "first_letter"],
elements: [{title: "Iron Man"}]
},
{
key: {release_year: 2008, first_letter: "T"},
grouping: ["release_year", "first_letter"],
elements: [{title: "The Incredible Hulk"}]
},
// ...
]
*/
.. code-tab:: edgeql
group Movie { title }
using first_letter := .title[0]
by .release_year, first_letter
Using grouping sets to group by several expressions simultaneously.
.. tabs::
.. code-tab:: typescript
e.group(e.Movie, movie => {
const release_year = movie.release_year;
const first_letter = movie.title[0];
return {
title: true,
by: e.group.set({release_year, first_letter})
};
});
/* [
{
key: {release_year: 2008},
grouping: ["release_year"],
elements: [{title: "Iron Man"}, {title: "The Incredible Hulk"}]
},
{
key: {first_letter: "I"},
grouping: ["first_letter"],
elements: [{title: "Iron Man"}, {title: "Iron Man 2"}, {title: "Iron Man 3"}],
},
// ...
] */
.. code-tab:: edgeql
group Movie { title }
using first_letter := .title[0]
by {.release_year, first_letter}
Using a combination of tuples and grouping sets.
.. tabs::
.. code-tab:: typescript
e.group(e.Movie, movie => {
const release_year = movie.release_year;
const first_letter = movie.title[0];
const cast_size = e.count(movie.actors);
return {
title: true,
by: e.group.tuple(release_year, e.group.set({first_letter, cast_size}))
// by .release_year, { first_letter, cast_size }
// equivalent to
// by (.release_year, first_letter), (.release_year, cast_size),
};
});
/* [
{
key: {release_year: 2008, first_letter: "I"},
grouping: ["release_year", "first_letter"],
elements: [{title: "Iron Man"}]
},
{
key: {release_year: 2008, cast_size: 3},
grouping: ["release_year", "cast_size"],
elements: [{title: "The Incredible Hulk"}]
},
// ...
] */
.. code-tab:: edgeql
group Movie { title }
using
first_letter := .title[0],
cast_size := count(.actors)
by .release_year, {first_letter, cast_size}
The ``group`` statement provides a syntactic sugar for defining certain common
grouping sets: ``cube`` and ``rollup``. Here's a quick primer on how they work:
.. code-block::
ROLLUP (a, b, c)
is equivalent to
{(), (a), (a, b), (a, b, c)}
CUBE (a, b)
is equivalent to
{(), (a), (b), (a, b)}
To use these in the query builder use the ``e.group.cube`` and
``e.group.rollup`` functions.
.. tabs::
.. code-tab:: typescript
e.group(e.Movie, movie => {
const release_year = movie.release_year;
const first_letter = movie.title[0];
const cast_size = e.count(movie.actors);
return {
title: true,
by: e.group.rollup({release_year, first_letter, cast_size})
};
});
.. code-tab:: edgeql
group Movie { title }
using
first_letter := .title[0],
cast_size := count(.actors)
by rollup(.release_year, first_letter, cast_size)
.. tabs::
.. code-tab:: typescript
e.group(e.Movie, movie => {
const release_year = movie.release_year;
const first_letter = movie.title[0];
const cast_size = e.count(movie.actors);
return {
title: true,
by: e.group.cube({release_year, first_letter, cast_size})
};
});
.. code-tab:: edgeql
group Movie { title }
using
first_letter := .title[0],
cast_size := count(.actors)
by cube(.release_year, first_letter, cast_size)
================================================================================
.. File: index.rst
.. _gel-js-intro:
========================
Gel TypeScript/JS Client
========================
.. toctree::
:maxdepth: 3
:hidden:
driver
generation
queries
interfaces
querybuilder
literals
types
funcops
parameters
objects
select
insert
update
delete
with
for
group
reference
.. _gel-js-installation:
Installation
============
You can install the published database driver and optional (but recommended!)
generators from npm using your package manager of choice.
.. tabs::
.. code-tab:: bash
:caption: npm
$ npm install --save-prod gel # database driver
$ npm install --save-dev @gel/generate # generators
.. code-tab:: bash
:caption: yarn
$ yarn add gel # database driver
$ yarn add --dev @gel/generate # generators
.. code-tab:: bash
:caption: pnpm
$ pnpm add --save-prod gel # database driver
$ pnpm add --save-dev @gel/generate # generators
.. code-tab:: typescript
:caption: deno
import * as gel from "http://deno.land/x/gel/mod.ts";
.. code-tab:: bash
:caption: bun
$ bun add gel # database driver
$ bun add --dev @gel/generate # generators
.. note:: Deno users
Create these two files in your project root:
.. code-block:: json
:caption: importMap.json
{
"imports": {
"gel": "https://deno.land/x/gel/mod.ts",
"gel/": "https://deno.land/x/gel/"
}
}
.. code-block:: json
:caption: deno.js
{
"importMap": "./importMap.json"
}
.. _gel-js-quickstart:
Quickstart
==========
Setup
^^^^^
This section assumes you have gone through the :ref:`Quickstart Guide
<ref_quickstart>` and understand how to update schemas, run migrations, and have
a working |Gel| project. Let's update the schema to make the ``title`` property
of the ``Movie`` type exclusive. This will help with filtering by
``Movie.title`` in our queries.
.. code-block:: sdl-diff
:caption: dbschema/default.gel
module default {
type Person {
required name: str;
}
type Movie {
- required title: str;
+ required title: str {
+ constraint exclusive;
+ };
multi actors: Person;
}
}
Generate the new migration and apply them:
.. code-block:: bash
$ gel migration create
$ gel migrate
We'll be using TypeScript and Node for this example, so let's setup a simple
app:
.. code-block:: bash
$ npm init -y # initialize a new npm project
$ npm i gel
$ npm i -D typescript @types/node @gel/generate tsx
$ npx tsc --init # initialize a basic TypeScript project
Client
^^^^^^
The ``Client`` class implements the core functionality required to establish a
connection to your database and execute queries. If you prefer writing queries
as strings, the Client API is all you need.
Let's create a simple Node.js script that seeds the database by running an
insert query directly with the driver:
.. code-block:: typescript
:caption: seed.ts
import * as gel from "gel";
const client = gel.createClient();
async function main() {
await client.execute(`
insert Person { name := "Robert Downey Jr." };
insert Person { name := "Scarlett Johansson" };
insert Movie {
title := <str>$title,
actors := (
select Person filter .name in {
"Robert Downey Jr.",
"Scarlett Johansson"
}
)
}
`, { title: "Iron Man 2" });
}
main();
We can now seed the database by running this script with ``tsx``
.. code-block:: bash
$ npx tsx seed.ts
Feel free to explore the database in the :ref:`Gel UI <ref_cli_gel_ui>`,
where you will find the new data you inserted through this script, as well as
any data you inserted when running the Quickstart.
.. note:: A word on module systems
Different build tools and runtimes have different specifications for how
modules are imported, and we support a wide-range of those styles. For
clarity, we will be sticking to standard TypeScript-style ESM module importing
without a file extension throughout this documentation. Please see your build
or environment tooling's guidance on how to adapt this style.
Querying with plain strings
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Now, let's write a Node.js script that queries the database for details about
Iron Man 2:
.. code-block:: typescript
:caption: query.ts
import * as gel from "gel";
const client = gel.createClient();
async function main() {
const result = await client.querySingle(`
select Movie {
id,
title,
actors: {
id,
name,
}
} filter .title = "Iron Man 2"
`);
console.log(JSON.stringify(result, null, 2));
}
main();
Interfaces
^^^^^^^^^^
Since we're using TypeScript, it would be nice to be able to type the return
value of this query, so let's use our first generator, the :ref:`interfaces
generator <gel-js-interfaces>` to tell TypeScript what the type of our result
is.
First we run the generator:
.. code-block:: bash
$ npx @gel/generate interfaces
This generator introspects your database schema and generates a set of
equivalent TypeScript interfaces.
Now we can annotate our query since we are selecting the whole ``Movie`` type:
.. code-block:: typescript-diff
:caption: query.ts
import * as gel from "gel";
import { Movie } from "./dbschema/interfaces"
const client = gel.createClient();
async function main() {
// result will be inferred as Movie | null
- const result = await client.querySingle(`
+ const result = await client.querySingle<Movie>(`
select Movie {
id,
title,
actors: {
id,
name,
}
} filter .title = "Iron Man 2"
`);
console.log(JSON.stringify(result, null, 2));
}
main();
You can now run the script with ``tsx``:
.. code-block:: bash
$ npx tsx query.ts
Queries generator
^^^^^^^^^^^^^^^^^
Wouldn't it be great if we could write any arbitrary query and get a type-safe
function that we could call? Good news, that's exactly what the next generator
does! The :ref:`queries generator <gel-js-queries>` scans your project for
``*.edgeql`` files and generates a file containing a strongly-typed function.
First, move the query into a separate file called ``getMovie.edgeql``.
.. code-block:: edgeql
:caption: getMovie.edgeql
select Movie {
id,
title,
actors: {
id,
name,
}
};
Next, we'll run the ``queries`` generator, specifying the ``--file`` option
which will compile all the queries it finds into a single TypeScript module:
.. code-block:: bash
$ npx @gel/generate queries --file
Now, let's update our query script to call the generated function, which will
provide us with type-safe querying.
.. code-block:: typescript-diff
:caption: query.ts
import * as gel from "gel";
- import { Movie } from "./dbschema/interfaces"
+ import { getMovie } from "./dbschema/queries"
const client = gel.createClient();
async function main() {
// result will be inferred as Movie | null
- const result = await client.querySingle<Movie>(`
- select Movie {
- id,
- title,
- actors: {
- id,
- name,
- }
- } filter .title = "Iron Man 2"
- `);
+ const result = await getMovie(client);
console.log(JSON.stringify(result, null, 2));
}
main();
Now, if you change the query to return different data, or take parameters, and
run the queries generator again, the type of the newly generated function will
change. It'll be completely type safe!
Query builder
^^^^^^^^^^^^^
At last we've arrived at the most powerful API for querying your |Gel|
instance: the query builder. The Gel query builder provides a **code-first**
way to write **fully-typed** EdgeQL queries with TypeScript. We recommend it for
TypeScript users, or anyone who prefers writing queries with code.
First, we'll run the query builder generator:
.. code-block:: bash
$ npx @gel/generate edgeql-js
.. note:: Version control
The first time you run the generator, you'll be prompted to add the generated
files to your ``.gitignore``. Confirm this prompt to automatically add a line
to your ``.gitignore`` that excludes the generated files.
For consistency, we recommend omitting the generated files from version
control and re-generating them as part of your deployment process. However,
there may be circumstances where checking the generated files into version
control is desirable, e.g. if you are building Docker images that must contain
the full source code of your application.
Now, we can import the generated query builder and express our query completely
in TypeScript, getting editor completion, type checking, and type inferrence:
.. code-block:: typescript-diff
:caption: query.ts
import * as gel from "gel";
- import { getMovie } from "./dbschema/queries";
+ import e from "./dbschema/edgeql-js";
const client = gel.createClient();
async function main() {
- // result will be inferred as Movie | null
+ // result will be inferred based on the query
- const result = await getMovie(client);
+ const result = await e
+ .select(e.Movie, () => ({
+ id: true,
+ title: true,
+ actors: () => ({ id: true, name: true }),
+ filter_single: { title: "Iron Man 2" },
+ }))
+ .run(client);
console.log(JSON.stringify(result, null, 2));
}
main();
What's next
===========
We recommend reading the :ref:`client docs <gel-js-driver>` first and getting
familiar with configuring the client. You'll find important APIs like
``withGlobals`` and connection details there. After that, depending on your
preferences, look through the :ref:`query builder <gel-js-qb>` documentation
and use the other pages as a reference for writing code-first Gel queries.
================================================================================
.. File: insert.rst
.. _gel-js-insert:
Insert
------
Insert new data with ``e.insert``.
.. code-block:: typescript
e.insert(e.Movie, {
title: e.str("Spider-Man: No Way Home"),
release_year: e.int64(2021)
});
For convenience, the second argument of ``e.insert`` function can also accept
plain JS data or a named tuple.
.. code-block:: typescript
e.insert(e.Movie, {
title: "Spider-Man: No Way Home",
actors: e.select(e.Person, person => ({
filter: e.op(person.name, "=", "Robert Downey Jr."),
'@character_name': e.str("Iron Man")
}))
});
.. code-block:: typescript
e.params({
movie: e.tuple({
title: e.str,
release_year: e.int64
})
}, $ =>
e.insert(e.Movie, $.movie)
);
Link properties
^^^^^^^^^^^^^^^
As in EdgeQL, link properties are inserted inside the shape of a subquery.
.. code-block:: typescript
const query = e.insert(e.Movie, {
title: "Iron Man",
actors: e.select(e.Person, person => ({
filter_single: {name: "Robert Downey Jr."},
"@character_name": e.str("Tony Stark")
// link props must correspond to expressions
"@character_name": "Tony Stark" // invalid
))
});
.. note::
For technical reasons, link properties must correspond to query
builder expressions, not plain JS data.
Similarly you can directly include link properties inside nested ``e.insert``
queries:
.. code-block:: typescript
const query = e.insert(e.Movie, {
title: "Iron Man",
release_year: 2008,
actors: e.insert(e.Person, {
name: "Robert Downey Jr.",
"@character_name": e.str("Tony Start")
}),
});
Handling conflicts
^^^^^^^^^^^^^^^^^^
:index: querybuilder unlessconflict unless conflict constraint
In EdgeQL, "upsert" functionality is achieved by handling **conflicts** on
``insert`` statements with the ``unless conflict`` clause. In the query
builder, this is possible with the ``.unlessConflict`` method (available only
on ``insert`` expressions).
In the simplest case, adding ``.unlessConflict`` (no arguments) will prevent
Gel from throwing an error if the insertion would violate an exclusivity
contstraint. Instead, the query returns an empty set (``null``).
.. code-block:: typescript
e.insert(e.Movie, {
title: "Spider-Man: No Way Home",
release_year: 2021
}).unlessConflict();
// => null
Provide an ``on`` clause to "catch" conflicts only on a specific property/link.
.. code-block:: typescript
e.insert(e.Movie, {
title: "Spider-Man: No Way Home",
release_year: 2021
}).unlessConflict(movie => ({
on: movie.title, // can be any expression
}));
You can also provide an ``else`` expression which will be executed and returned
in case of a conflict. You must specify an ``on`` clause in order to use ``else``.
The following query simply returns the pre-existing (conflicting) object.
.. code-block:: typescript
e.insert(e.Movie, {
title: "Spider-Man: Homecoming",
release_year: 2021
}).unlessConflict(movie => ({
on: movie.title,
else: movie
}));
Or you can perform an upsert operation with an ``e.update`` in the ``else``.
.. code-block:: typescript
e.insert(e.Movie, {
title: "Spider-Man: Homecoming",
release_year: 2021
}).unlessConflict(movie => ({
on: movie.title,
else: e.update(movie, () => ({
set: {
release_year: 2021
}
})),
}));
If the constraint you're targeting is a composite constraint, wrap the
properties in a tuple.
.. code-block:: typescript
e.insert(e.Movie, {
title: "Spider-Man: No Way Home",
release_year: 2021
}).unlessConflict(movie => ({
on: e.tuple([movie.title, movie.release_year])
}));
Bulk inserts
^^^^^^^^^^^^
You can use a :ref:`for loop <gel-js-for>` to perform :ref:`bulk inserts
<gel-js-for-bulk-inserts>`.
================================================================================
.. File: interfaces.rst
.. _gel-js-interfaces:
====================
Interfaces Generator
====================
The ``interfaces`` generator introspects your schema and generates file containing *TypeScript interfaces* that correspond to each object type. This is useful for writing typesafe code to interact with |Gel|.
Installation
------------
To get started, install the following packages. (If you're using Deno, you can skip this step.)
Install the ``gel`` package.
.. code-block:: bash
$ npm install gel # npm users
$ yarn add gel # yarn users
$ bun add gel # bun users
Then install ``@gel/generate`` as a dev dependency.
.. code-block:: bash
$ npm install @gel/generate --save-dev # npm users
$ yarn add @gel/generate --dev # yarn users
$ bun add --dev @gel/generate # bun users
Generation
----------
Assume your database contains the following Gel schema.
.. code-block:: sdl
module default {
type Person {
required name: str;
}
scalar type Genre extending enum<Horror, Comedy, Drama>;
type Movie {
required title: str;
genre: Genre;
multi actors: Person;
}
}
The following command will run the ``interfaces`` generator.
.. tabs::
.. code-tab:: bash
:caption: Node.js
$ npx @gel/generate interfaces
.. code-tab:: bash
:caption: Deno
$ deno run --allow-all --unstable https://deno.land/x/gel/generate.ts interfaces
.. code-tab:: bash
:caption: Bun
$ bunx @gel/generate interfaces
.. note:: Deno users
Create these two files in your project root:
.. code-block:: json
:caption: importMap.json
{
"imports": {
"gel": "https://deno.land/x/gel/mod.ts",
"gel/": "https://deno.land/x/gel/"
}
}
.. code-block:: json
:caption: deno.js
{
"importMap": "./importMap.json"
}
This will introspect your schema and generate TypeScript interfaces that correspond to each object type. By default, these interfaces will be written to a single file called ``interfaces.ts`` into the ``dbschema`` directory in your project root. The file will contain the following contents (roughly):
.. code-block:: typescript
export interface Person {
id: string;
name: string;
}
export type Genre = "Horror" | "Comedy" | "Drama";
export interface Movie {
id: string;
title: string;
genre?: Genre | null;
actors: Person[];
}
Any types declared in a non-``default`` module will be generated into an accordingly named ``namespace``.
.. note::
Generators work by connecting to the database to get information about the current state of the schema. Make sure you run the generators again any time the schema changes so that the generated code is in-sync with the current state of the schema.
Customize file path
~~~~~~~~~~~~~~~~~~~
Pass a ``--file`` flag to specify the output file path.
.. code-block:: bash
$ npx @gel/generate interfaces --file schema.ts
If the value passed as ``--file`` is a relative path, it will be evaluated relative to the current working directory (``process.cwd()``). If the value is an absolute path, it will be used as-is.
.. note::
Because this generator is TypeScript-specific, the ``--target`` flag is not supported as in other generators.
Version control
~~~~~~~~~~~~~~~
To exclude the generated file, add the following lines to your ``.gitignore`` file.
.. code-block:: text
dbschema/interfaces.ts
Usage
-----
The generated interfaces can be imported like so.
.. code-block:: typescript
import {Genre, Movie} from "./dbschema/interfaces";
You will need to manipulate the generated interfaces to match your application's needs. For example, you may wish to strip the ``id`` property for a ``createMovie`` mutation.
.. code-block:: typescript
function createMovie(data: Omit<Movie, "id">) {
// ...
}
.. note::
Refer to the `TypeScript docs <https://www.typescriptlang.org/docs/handbook/utility-types.html>`_ for information about built-in utility types like ``Pick``, ``Omit``, and ``Partial``.
For convenience, the file also exports a namespace called ``helper`` containing a couple useful utilities for extracting the properties or links from an object type interface.
.. code-block:: typescript
import {Movie, helper} from "./dbschema/interfaces";
type MovieProperties = helper.Props<Movie>;
// { id: string; title: string; ... }
type MovieLinks = helper.Links<Movie>;
// { actors: Person[]; }
Enums
~~~~~
Note that an ``enum`` in your schema will be represented in the generated code as a union of string literals.
.. code-block:: typescript
export type Genre = "Horror" | "Comedy" | "Drama";
We do *not* generate TypeScript enums for a number of reasons.
- In TypeScript, enums are nominally typed. Two identically named enums are not
considered equal, even if they have the same members.
- Enums are both a runtime and static construct. Hovever, for simplicity we want the ``interfaces`` generator to produce exclusively static (type-level) code.
================================================================================
.. File: literals.rst
.. _gel-js-literals:
Literals
--------
The query builder provides a set of "helper functions" that convert JavaScript
literals into *expressions* that can be used in queries. For the most part,
these helper functions correspond to the *name* of the type.
Primitives
^^^^^^^^^^
Primitive literal expressions are created using constructor functions that
correspond to Gel datatypes. Each expression below is accompanied by the
EdgeQL it produces.
.. code-block:: typescript
e.str("asdf") // "asdf"
e.int64(123) // 123
e.float64(123.456) // 123.456
e.bool(true) // true
e.bigint(12345n) // 12345n
e.decimal("1234.1234n") // 1234.1234n
e.uuid("599236a4...") // <uuid>"599236a4..."
e.bytes(Uint8Array.from('binary data'));
// b'binary data'
Strings
^^^^^^^
String expressions have some special functionality: they support indexing and
slicing, as in EdgeQL.
.. code-block:: typescript
const myString = e.str("hello world");
myString[5]; // "hello world"[5]
myString['2:5']; // "hello world"[2:5]
myString[':5']; // "hello world"[:5]
myString['2:']; // "hello world"[2:]
There are also equivalent ``.index`` and ``.slice`` methods that can accept
integer expressions as arguments.
.. code-block:: typescript
const myString = e.str("hello world");
const start = e.int64(2);
const end = e.int64(5);
myString.index(start); // "hello world"[2]
myString.slice(start, end); // "hello world"[2:5]
myString.slice(null, end); // "hello world"[:5]
myString.slice(start, null); // "hello world"[2:]
Enums
^^^^^
Enum literals are available as properties defined on the enum type.
.. code-block:: typescript
e.Colors.green;
// Colors.green;
e.sys.VersionStage.beta;
// sys::VersionStage.beta
Dates and times
^^^^^^^^^^^^^^^
To create an instance of ``datetime``, pass a JavaScript ``Date`` object into
``e.datetime``:
.. code-block:: typescript
e.datetime(new Date('1999-01-01'));
// <datetime>'1999-01-01T00:00:00.000Z'
Gel's other temporal datatypes don't have equivalents in the JavaScript
type system: ``duration``, ``cal::relative_duration``, ``cal::date_duration``,
``cal::local_date``, ``cal::local_time``, and ``cal::local_datetime``,
To resolve this, each of these datatypes can be represented with an instance
of a corresponding class, as defined in ``gel`` module. Clients use
these classes to represent these values in query results; they are documented
on the :ref:`Client API <gel-js-datatypes>` docs.
.. list-table::
* - ``e.duration``
- :js:class:`Duration`
* - ``e.cal.relative_duration``
- :js:class:`RelativeDuration`
* - ``e.cal.date_duration``
- :js:class:`DateDuration`
* - ``e.cal.local_date``
- :js:class:`LocalDate`
* - ``e.cal.local_time``
- :js:class:`LocalTime`
* - ``e.cal.local_datetime``
- :js:class:`LocalDateTime`
* - ``e.cal.local_datetime``
- :js:class:`LocalDateTime`
* - ``e.cal.local_datetime``
- :js:class:`LocalDateTime`
The code below demonstrates how to declare each kind of temporal literal,
along with the equivalent EdgeQL.
.. code-block:: typescript
import * as gel from "gel";
const myDuration = new gel.Duration(0, 0, 0, 0, 1, 2, 3);
e.duration(myDuration);
const myLocalDate = new gel.LocalDate(1776, 7, 4);
e.cal.local_date(myLocalDate);
const myLocalTime = new gel.LocalTime(13, 15, 0);
e.cal.local_time(myLocalTime);
const myLocalDateTime = new gel.LocalDateTime(1776, 7, 4, 13, 15, 0);
e.cal.local_datetime(myLocalDateTime);
You can also declare these literals by casting an appropriately formatted
``str`` expression, as in EdgeQL. Casting :ref:`is documented
<ref_qb_casting>` in more detail later in the docs.
.. code-block:: typescript
e.cast(e.duration, e.str('5 minutes'));
// <std::duration>'5 minutes'
e.cast(e.cal.local_datetime, e.str('1999-03-31T15:17:00'));
// <cal::local_datetime>'1999-03-31T15:17:00'
e.cast(e.cal.local_date, e.str('1999-03-31'));
// <cal::local_date>'1999-03-31'
e.cast(e.cal.local_time, e.str('15:17:00'));
// <cal::local_time>'15:17:00'
JSON
^^^^
JSON literals are created with the ``e.json`` function. You can pass in any
Gel-compatible data structure.
What does "Gel-compatible" mean? It means any JavaScript data structure
with an equivalent in Gel: strings, number, booleans, ``bigint``\ s,
``Uint8Array``\ s, ``Date``\ s, and instances of Gel's built-in classes:
(``LocalDate`` ``LocalTime``, ``LocalDateTime``, ``DateDuration``,
``Duration``, and ``RelativeDuration``), and any array or object of these
types. Other JavaScript data structures like symbols, instances of custom
classes, sets, maps, and `typed arrays <https://developer.mozilla.org/en-US/
docs/Web/JavaScript/Typed_arrays>`_ are not supported.
.. code-block:: typescript
const query = e.json({ name: "Billie" })
// to_json('{"name": "Billie"}')
const data = e.json({
name: "Billie",
numbers: [1,2,3],
nested: { foo: "bar"},
duration: new gel.Duration(1, 3, 3)
})
JSON expressions support indexing, as in EdgeQL. The returned expression also
has a ``json`` type.
.. code-block:: typescript
const query = e.json({ numbers: [0,1,2] });
query.toEdgeQL(); // to_json((numbers := [0,1,2]))
query.numbers[0].toEdgeQL();
// to_json('{"numbers":[0,1,2]}')['numbers'][0]
.. Keep in mind that JSON expressions are represented as strings when returned from a query.
.. .. code-block:: typescript
.. await e.json({
.. name: "Billie",
.. numbers: [1,2,3]
.. }).run(client)
.. // => '{"name": "Billie", "numbers": [1, 2, 3]}';
The inferred type associated with a ``json`` expression is ``unknown``.
.. code-block:: typescript
const result = await query.run(client)
// unknown
Arrays
^^^^^^
Declare array expressions by passing an array of expressions into ``e.array``.
.. code-block:: typescript
e.array([e.str("a"), e.str("b"), e.str("b")]);
// ["a", "b", "c"]
EdgeQL semantics are enforced by TypeScript, so arrays can't contain elements
with incompatible types.
.. code-block:: typescript
e.array([e.int64(5), e.str("foo")]);
// TypeError!
For convenience, the ``e.array`` can also accept arrays of plain JavaScript
data as well.
.. code-block:: typescript
e.array(['a', 'b', 'c']);
// ['a', 'b', 'c']
// you can intermixing expressions and plain data
e.array([1, 2, e.int64(3)]);
// [1, 2, 3]
Array expressions also support indexing and slicing operations.
.. code-block:: typescript
const myArray = e.array(['a', 'b', 'c', 'd', 'e']);
// ['a', 'b', 'c', 'd', 'e']
myArray[1];
// ['a', 'b', 'c', 'd', 'e'][1]
myArray['1:3'];
// ['a', 'b', 'c', 'd', 'e'][1:3]
There are also equivalent ``.index`` and ``.slice`` methods that can accept
other expressions as arguments.
.. code-block:: typescript
const start = e.int64(1);
const end = e.int64(3);
myArray.index(start);
// ['a', 'b', 'c', 'd', 'e'][1]
myArray.slice(start, end);
// ['a', 'b', 'c', 'd', 'e'][1:3]
Tuples
^^^^^^
Declare tuples with ``e.tuple``. Pass in an array to declare a "regular"
(unnamed) tuple; pass in an object to declare a named tuple.
.. code-block:: typescript
e.tuple([e.str("Peter Parker"), e.int64(18)]);
// ("Peter Parker", 18)
e.tuple({
name: e.str("Peter Parker"),
age: e.int64(18)
});
// (name := "Peter Parker", age := 18)
Tuple expressions support indexing.
.. code-block:: typescript
// Unnamed tuples
const spidey = e.tuple([
e.str("Peter Parker"),
e.int64(18)
]);
spidey[0]; // => ("Peter Parker", 18)[0]
// Named tuples
const spidey = e.tuple({
name: e.str("Peter Parker"),
age: e.int64(18)
});
spidey.name;
// (name := "Peter Parker", age := 18).name
Set literals
^^^^^^^^^^^^
Declare sets with ``e.set``.
.. code-block:: typescript
e.set(e.str("asdf"), e.str("qwer"));
// {'asdf', 'qwer'}
As in EdgeQL, sets can't contain elements with incompatible types. These
semantics are enforced by TypeScript.
.. code-block:: typescript
e.set(e.int64(1234), e.str('sup'));
// TypeError
Empty sets
^^^^^^^^^^
To declare an empty set, cast an empty set to the desired type. As in EdgeQL,
empty sets are not allowed without a cast.
.. code-block:: typescript
e.cast(e.int64, e.set());
// <std::int64>{}
Range literals
^^^^^^^^^^^^^^
As in EdgeQL, declare range literals with the built-in ``range`` function.
.. code-block:: typescript
const myRange = e.range(0, 8);
myRange.toEdgeQL();
// => std::range(0, 8);
Ranges can be created for all numerical types, as well as ``datetime``, ``local_datetime``, and ``local_date``.
.. code-block:: typescript
e.range(e.decimal('100'), e.decimal('200'));
e.range(Date.parse("1970-01-01"), Date.parse("2022-01-01"));
e.range(new LocalDate(1970, 1, 1), new LocalDate(2022, 1, 1));
Supply named parameters as the first argument.
.. code-block:: typescript
e.range({inc_lower: true, inc_upper: true, empty: true}, 0, 8);
// => std::range(0, 8, true, true);
JavaScript doesn't have a native way to represent range values. Any range value returned from a query will be encoded as an instance of the :js:class:`Range` class, which is exported from the ``gel`` package.
.. code-block:: typescript
const query = e.range(0, 8);
const result = await query.run(client);
// => Range<number>;
console.log(result.lower); // 0
console.log(result.upper); // 8
console.log(result.isEmpty); // false
console.log(result.incLower); // true
console.log(result.incUpper); // false
.. Modules
.. -------
.. All *types*, *functions*, and *commands* are available on the ``e`` object, properly namespaced by module.
.. .. code-block:: typescript
.. // commands
.. e.select;
.. e.insert;
.. e.update;
.. e.delete;
.. // types
.. e.std.str;
.. e.std.int64;
.. e.std.bool;
.. e.cal.local_datetime;
.. e.default.User; // user-defined object type
.. e.my_module.Foo; // object type in user-defined module
.. // functions
.. e.std.len;
.. e.std.str_upper;
.. e.math.floor;
.. e.sys.get_version;
.. For convenience, the contents of the ``std`` and ``default`` modules are also exposed at the top-level of ``e``.
.. .. code-block:: typescript
.. e.str;
.. e.int64;
.. e.bool;
.. e.len;
.. e.str_upper;
.. e.User;
.. .. note::
.. If there are any name conflicts (e.g. a user-defined module called ``len``),
.. ``e.len`` will point to the user-defined module; in that scenario, you must
.. explicitly use ``e.std.len`` to access the built-in ``len`` function.
================================================================================
.. File: objects.rst
.. _gel-js-objects:
Objects and Paths
=================
All queries on this page assume the following schema.
.. code-block:: sdl
module default {
type Person {
required name: str;
}
abstract type Content {
required title: str {
constraint exclusive
};
multi actors: Person {
character_name: str;
};
}
type Movie extending Content {
release_year: int64;
}
type TVShow extending Content {
num_seasons: int64;
}
}
Object types
^^^^^^^^^^^^
All object types in your schema are reflected into the query builder, properly
namespaced by module.
.. code-block:: typescript
e.default.Person;
e.default.Movie;
e.default.TVShow;
e.my_module.SomeType;
For convenience, the contents of the ``default`` module are also available at
the top-level of ``e``.
.. code-block:: typescript
e.Person;
e.Movie;
e.TVShow;
.. As in EdgeQL, type names like ``Movie`` serve two purposes.
.. - They can be used to represent the set of all Movie objects: ``select Movie``.
.. - They can be used to represent the Movie *type* in operations like type intersections: ``select Content[is Movie]``
Paths
^^^^^
EdgeQL-style *paths* are supported on object type references.
.. code-block:: typescript
e.Person.name; // Person.name
e.Movie.title; // Movie.title
e.TVShow.actors.name; // Movie.actors.name
Paths can be constructed from any object expression, not just the root types.
.. code-block:: typescript
e.select(e.Person).name;
// (select Person).name
e.op(e.Movie, 'union', e.TVShow).actors;
// (Movie union TVShow).actors
const ironMan = e.insert(e.Movie, {
title: "Iron Man"
});
ironMan.title;
// (insert Movie { title := "Iron Man" }).title
.. _gel-js-objects-type-intersections:
Type intersections
^^^^^^^^^^^^^^^^^^
Use the type intersection operator to narrow the type of a set of objects. For
instance, to represent the elements of an Account's watchlist that are of type
``TVShow``:
.. code-block:: typescript
e.Person.acted_in.is(e.TVShow);
// Person.acted_in[is TVShow]
Backlinks
^^^^^^^^^
All possible backlinks are auto-generated and can be auto-completed by
TypeScript. They behave just like forward links. However, because they contain
special characters, you must use bracket syntax instead of simple dot notation.
.. code-block:: typescript
e.Person["<director[is Movie]"]
// Person.<director[is Movie]
For convenience, these backlinks automatically combine the backlink operator
and type intersection into a single key. However, the query builder also
provides "naked" backlinks; these can be refined with the ``.is`` type
intersection method.
.. code-block:: typescript
e.Person['<director'].is(e.Movie);
// Person.<director[is Movie]
================================================================================
.. File: parameters.rst
.. _gel-js-parameters:
Parameters
----------
You can pass strongly-typed parameters into your query with ``e.params``.
.. code-block:: typescript
const helloQuery = e.params({name: e.str}, (params) =>
e.op('Yer a wizard, ', '++', params.name)
);
/* with name := <str>$name
select name;
*/
The first argument is an object defining the parameter names and their
corresponding types. The second argument is a closure that returns an
expression; use the ``params`` argument to construct the rest of your query.
Passing parameter data
^^^^^^^^^^^^^^^^^^^^^^
To executing a query with parameters, pass the parameter data as the second
argument to ``.run()``; this argument is *fully typed*!
.. code-block:: typescript
await helloQuery.run(client, { name: "Harry Styles" })
// => "Yer a wizard, Harry Styles"
await helloQuery.run(client, { name: 16 })
// => TypeError: number is not assignable to string
Top-level usage
^^^^^^^^^^^^^^^
Note that you must call ``.run`` on the result of ``e.params``; in other
words, you can only use ``e.params`` at the *top level* of your query, not as
an expression inside a larger query.
.. code-block:: typescript
// ❌ TypeError
const wrappedQuery = e.select(helloQuery);
wrappedQuery.run(client, {name: "Harry Styles"});
.. _gel-js-optional-parameters:
Optional parameters
^^^^^^^^^^^^^^^^^^^
A type can be made optional with the ``e.optional`` function.
.. code-block:: typescript
const query = e.params(
{
title: e.str,
duration: e.optional(e.duration),
},
(params) => {
return e.insert(e.Movie, {
title: params.title,
duration: params.duration,
});
}
);
// works with duration
const result = await query.run(client, {
title: 'The Eternals',
duration: Duration.from({hours: 2, minutes: 3})
});
// or without duration
const result = await query.run(client, {title: 'The Eternals'});
Complex types
^^^^^^^^^^^^^
In EdgeQL, parameters can only be primitives or arrays of primitives. That's
not true with the query builder! Parameter types can be arbitrarily complex.
Under the hood, the query builder serializes the parameters to JSON and
deserializes them on the server.
.. code-block:: typescript
const insertMovie = e.params(
{
title: e.str,
release_year: e.int64,
actors: e.array(
e.tuple({
name: e.str,
})
),
},
(params) =>
e.insert(e.Movie, {
title: params.title,
})
);
await insertMovie.run(client, {
title: 'Dune',
release_year: 2021,
actors: [{name: 'Timmy'}, {name: 'JMo'}],
});
================================================================================
.. File: queries.rst
.. _gel-js-queries:
=================
Queries Generator
=================
The ``queries`` generator scans your project for ``*.edgeql`` files and generates functions that allow you to execute these queries in a typesafe way.
Installation
------------
To get started, install the following packages.
.. note::
If you're using Deno, you can skip this step.
Install the ``gel`` package.
.. code-block:: bash
$ npm install gel # npm users
$ yarn add gel # yarn users
$ bun add gel # bun users
Then install ``@gel/generate`` as a dev dependency.
.. code-block:: bash
$ npm install @gel/generate --save-dev # npm users
$ yarn add @gel/generate --dev # yarn users
$ bun add --dev @gel/generate # bun users
Generation
----------
Consider the following file tree.
.. code-block:: text
.
├── package.json
├── gel.toml
├── index.ts
├── dbschema
└── queries
└── getUser.edgeql
The following command will run the ``queries`` generator.
.. tabs::
.. code-tab:: bash
:caption: Node.js
$ npx @gel/generate queries
.. code-tab:: bash
:caption: Deno
$ deno run --allow-all --unstable https://deno.land/x/gel/generate.ts queries
.. code-tab:: bash
:caption: Bun
$ bunx @gel/generate queries
.. note:: Deno users
Create these two files in your project root:
.. code-block:: json
:caption: importMap.json
{
"imports": {
"gel": "https://deno.land/x/gel/mod.ts",
"gel/": "https://deno.land/x/gel/"
}
}
.. code-block:: json
:caption: deno.js
{
"importMap": "./importMap.json"
}
The generator will detect the project root by looking for an ``gel.toml``,
then scan the directory for ``*.edgeql`` files. In this case, there's only one:
``queries/getUser.edgeql``.
.. code-block:: edgeql
:caption: getUser.edgeql
select User { name, email } filter .id = <uuid>$user_id;
For each ``.edgeql`` file, the generator will read the contents and send the
query to the database, which returns type information about its parameters and
return type. The generator uses this information to create a new file
``getUser.query.ts`` alongside the original ``getUser.edgeql`` file.
.. code-block:: text
.
├── package.json
├── gel.toml
├── index.ts
├── dbschema
└── queries
└── getUser.edgeql
└── getUser.query.ts <-- generated file
.. note::
This example assumes you are using TypeScript. The generator tries to
auto-detect the language you're using; you can also specify the language with
the ``--target`` flag. See the :ref:`Targets <gel_qb_target>` section for
more information.
The generated file will look something like this:
.. code-block:: typescript
import type { Client } from "gel";
export type GetUserArgs = {
user_id: string;
};
export type GetUserReturns = {
name: string;
email: string;
} | null;
export async function getUser(
client: Client,
args: GetUserArgs
): Promise<GetUserReturns> {
return await client.querySingle(
`select User { name, email } filter .id = <uuid>$user_id;`,
args
);
}
Some things to note:
- The first argument is a ``Client`` instance. This is the same client you would use to execute a query manually. You can use the same client for both manual and generated queries.
- The second argument is a parameter object. The keys of this object are the names of the parameters in the query.
- The code uses the ``querySingle`` method, since the query is only expected to return a single result.
- We export the type of the parameter object and the return value unwrapped from the promise.
We can now use this function in our code.
.. code-block:: typescript
import { getUser } from "./queries/getUser.query";
import {
createClient,
type GetUserArgs,
type GetUserReturns,
} from "gel";
const client = await createClient();
const newUser: GetUserArgs = {
user_id: "00000000-0000-0000-0000-000000000000"
};
const user = await getUser(client, newUser); // GetUserReturns
if (user) {
user.name; // string
user.email; // string
}
.. note::
Generators work by connecting to the database to get information about the current state of the schema. Make sure you run the generators again any time the schema changes so that the generated code is in-sync with the current state of the schema.
Single-file mode
----------------
Pass the ``--file`` flag to generate a single file that contains functions for all detected ``.edgeql`` files. This lets you import all your queries from a single file.
Let's say we start with the following file tree.
.. code-block:: text
.
├── package.json
├── gel.toml
├── index.ts
├── dbschema
└── queries
└── getUser.edgeql
└── getMovies.edgeql
The following command will run the generator in ``--file`` mode.
.. code-block:: bash
$ npx @gel/generate queries --file
A single file will be generated that exports two functions, ``getUser`` and ``getMovies``. By default this file is generated into the ``dbschema`` directory.
.. code-block:: text
.
├── package.json
├── gel.toml
├── index.ts
├── dbschema
│ └── queries.ts <-- generated file
└── queries
└── getUser.edgeql
└── getMovies.edgeql
We can now use these functions in our code.
.. code-block:: typescript
import * as queries from "./dbschema/queries";
import {createClient} from "gel";
const client = await createClient();
const movies = await queries.getMovies(client);
const user = await queries.getUser(client, {
user_id: "00000000-0000-0000-0000-000000000000"
});
To override the file path and name, you can optionally pass a value to the ``--file`` flag. Note that you should *exclude the extension*.
.. code-block:: bash
$ npx @gel/generate queries --file path/to/myqueries
The file extension is determined by the generator ``--target`` and will be automatically appended to the provided path. Extensionless "absolute" paths will work; relative paths will be resolved relative to the current working directory.
This will result in the following file tree.
.. code-block:: text
.
├── package.json
├── gel.toml
├── path
│ └── to
│ └── myqueries.ts
├── queries
│ └── getUser.edgeql
│ └── getMovies.edgeql
└── index.ts
Version control
---------------
To exclude the generated files, add the following lines to your ``.gitignore`` file.
.. code-block:: text
**/*.edgeql.ts
dbschema/queries.*
Writing Queries with Parameters
-------------------------------
To inject external values into your EdgeQL queries, you can use :ref:`parameters <ref_eql_params>`.
When using the queries generator, you may be tempted to declare the same parameter in multiple places.
However, it's better practice to declare it once by assigning it to a variable in a :ref:`with block <ref_eql_with_params>`
and reference that variable in the rest of your query. This way you avoid mismatched types in your declarations,
such as forgetting to mark them all as :ref:`optional <ref_eql_params_optional>`.
================================================================================
.. File: querybuilder.rst
.. _gel-js-qb:
=======================
Query Builder Generator
=======================
:index: querybuilder generator typescript
The |Gel| query builder provides a **code-first** way to write
**fully-typed** EdgeQL queries with TypeScript. We recommend it for TypeScript
users, or anyone who prefers writing queries with code.
.. code-block:: typescript
import * as gel from "gel";
import e from "./dbschema/edgeql-js";
const client = gel.createClient();
async function run() {
const query = e.select(e.Movie, ()=>({
id: true,
title: true,
actors: { name: true }
}));
const result = await query.run(client)
/*
{
id: string;
title: string;
actors: { name: string; }[];
}[]
*/
}
run();
.. note:: Is it an ORM?
No—it's better! Like any modern TypeScript ORM, the query builder gives you
full typesafety and autocompletion, but without the power and `performance
<https://github.com/geldata/imdbench>`_
tradeoffs. You have access to the **full power** of EdgeQL and can write
EdgeQL queries of arbitrary complexity. And since |Gel| compiles each
EdgeQL query into a single, highly-optimized SQL query, your queries stay
fast, even when they're complex.
Why use the query builder?
--------------------------
*Type inference!* If you're using TypeScript, the result type of *all
queries* is automatically inferred for you. For the first time, you don't
need an ORM to write strongly typed queries.
*Auto-completion!* You can write queries full autocompletion on EdgeQL
keywords, standard library functions, and link/property names.
*Type checking!* In the vast majority of cases, the query builder won't let
you construct invalid queries. This eliminates an entire class of bugs and
helps you write valid queries the first time.
*Close to EdgeQL!* The goal of the query builder is to provide an API that is as
close as possible to EdgeQL itself while feeling like idiomatic TypeScript.
Installation
------------
To get started, install the following packages.
.. note::
If you're using Deno, you can skip this step.
Install the ``gel`` package.
.. code-block:: bash
$ npm install gel # npm users
$ yarn add gel # yarn users
$ bun add gel # bun users
Then install ``@gel/generate`` as a dev dependency.
.. code-block:: bash
$ npm install @gel/generate --save-dev # npm users
$ yarn add @gel/generate --dev # yarn users
$ bun add --dev @gel/generate # bun users
Generation
----------
The following command will run the ``edgeql-js`` query builder generator.
.. tabs::
.. code-tab:: bash
:caption: Node.js
$ npx @gel/generate edgeql-js
.. code-tab:: bash
:caption: Deno
$ deno run --allow-all --unstable https://deno.land/x/gel/generate.ts edgeql-js
.. code-tab:: bash
:caption: Bun
$ bunx @gel/generate edgeql-js
.. note:: Deno users
Create these two files in your project root:
.. code-block:: json
:caption: importMap.json
{
"imports": {
"gel": "https://deno.land/x/gel/mod.ts",
"gel/": "https://deno.land/x/gel/"
}
}
.. code-block:: json
:caption: deno.js
{
"importMap": "./importMap.json"
}
The generation command is configurable in a number of ways.
``--output-dir <path>``
Sets the output directory for the generated files.
``--target <ts|cjs|esm|mts>``
What type of files to generate.
``--force-overwrite``
To avoid accidental changes, you'll be prompted to confirm whenever the
``--target`` has changed from the previous run. To avoid this prompt, pass
``--force-overwrite``.
The generator also supports all the :ref:`connection flags
<ref_cli_gel_connopts>` supported by the |Gel| CLI. These aren't
necessary when using a project or environment variables to configure a
connection.
.. note::
Generators work by connecting to the database to get information about the current state of the schema. Make sure you run the generators again any time the schema changes so that the generated code is in-sync with the current state of the schema.
.. _gel-js-execution:
Expressions
-----------
Throughout the documentation, we use the term "expression" a lot. This is a
catch-all term that refers to *any query or query fragment* you define with
the query builder. They all conform to an interface called ``Expression`` with
some common functionality.
Most importantly, any expression can be executed with the ``.run()`` method,
which accepts a ``Client`` instead as the first argument. The result is
``Promise<T>``, where ``T`` is the inferred type of the query.
.. code-block:: typescript
await e.str("hello world").run(client);
// => "hello world"
await e.set(e.int64(1), e.int64(2), e.int64(3)).run(client);
// => [1, 2, 3]
await e
.select(e.Movie, () => ({
title: true,
actors: { name: true },
}))
.run(client);
// => [{ title: "The Avengers", actors: [...]}]
Note that the ``.run`` method accepts an instance of :js:class:`Client` (or
``Transaction``) as it's first argument. See :ref:`Creating a Client
<gel-js-create-client>` for details on creating clients. The second
argument is for passing :ref:`$parameters <gel-js-parameters>`, more on
that later.
.. code-block:: typescript
.run(client: Client | Transaction, params: Params): Promise<T>
Converting to EdgeQL
--------------------
:index: querybuilder toedgeql
You can extract an EdgeQL representation of any expression calling the
``.toEdgeQL()`` method. Below is a number of expressions and the EdgeQL they
produce. (The actual EdgeQL the create may look slightly different, but it's
equivalent.)
.. code-block:: typescript
e.str("hello world").toEdgeQL();
// => select "hello world"
e.set(e.int64(1), e.int64(2), e.int64(3)).toEdgeQL();
// => select {1, 2, 3}
e.select(e.Movie, () => ({
title: true,
actors: { name: true }
})).toEdgeQL();
// => select Movie { title, actors: { name }}
Extracting the inferred type
----------------------------
The query builder *automatically infers* the TypeScript type that best
represents the result of a given expression. This inferred type can be
extracted with the ``$infer`` type helper.
.. code-block:: typescript
import e, { type $infer } from "./dbschema/edgeql-js";
const query = e.select(e.Movie, () => ({ id: true, title: true }));
type result = $infer<typeof query>;
// { id: string; title: string }[]
Cheatsheet
----------
Below is a set of examples to get you started with the query builder. It is
not intended to be comprehensive, but it should provide a good starting point.
.. note::
Modify the examples below to fit your schema, paste them into ``script.ts``,
and execute them with the ``npx`` command from the previous section! Note
how the signature of ``result`` changes as you modify the query.
Insert an object
^^^^^^^^^^^^^^^^
.. code-block:: typescript
const query = e.insert(e.Movie, {
title: 'Doctor Strange 2',
release_year: 2022
});
const result = await query.run(client);
// { id: string }
// by default INSERT only returns the id of the new object
.. _gel-js-qb-transaction:
Transaction
^^^^^^^^^^^
We can also run the same query as above, build with the query builder, in a
transaction.
.. code-block:: typescript
const query = e.insert(e.Movie, {
title: 'Doctor Strange 2',
release_year: 2022
});
await client.transaction(async (tx) => {
const result = await query.run(tx);
// { id: string }
});
Select objects
^^^^^^^^^^^^^^
.. code-block:: typescript
const query = e.select(e.Movie, () => ({
id: true,
title: true,
}));
const result = await query.run(client);
// { id: string; title: string; }[]
To select all properties of an object, use the spread operator with the
special ``*`` property:
.. code-block:: typescript
const query = e.select(e.Movie, () => ({
...e.Movie['*']
}));
const result = await query.run(client);
/*
{
id: string;
title: string;
release_year: number | null; # optional property
}[]
*/
Nested shapes
^^^^^^^^^^^^^
.. code-block:: typescript
const query = e.select(e.Movie, () => ({
id: true,
title: true,
actors: {
name: true,
}
}));
const result = await query.run(client);
/*
{
id: string;
title: string;
actors: { name: string; }[];
}[]
*/
Filtering
^^^^^^^^^
Pass a boolean expression as the special key ``filter`` to filter the results.
.. code-block:: typescript
const query = e.select(e.Movie, (movie) => ({
id: true,
title: true,
// special "filter" key
filter: e.op(movie.release_year, ">", 1999)
}));
const result = await query.run(client);
// { id: string; title: number }[]
Since ``filter`` is a reserved keyword in EdgeQL, the special ``filter`` key can
live alongside your property keys without a risk of collision.
.. note::
The ``e.op`` function is used to express EdgeQL operators. It is documented in
more detail below and on the :ref:`Functions and operators
<gel-js-funcops>` page.
Select a single object
^^^^^^^^^^^^^^^^^^^^^^
To select a particular object, use the ``filter_single`` key. This tells the
query builder to expect a singleton result.
.. code-block:: typescript
const query = e.select(e.Movie, (movie) => ({
id: true,
title: true,
release_year: true,
filter_single: e.op(
movie.id,
"=",
e.uuid("2053a8b4-49b1-437a-84c8-e1b0291ccd9f")
},
}));
const result = await query.run(client);
// { id: string; title: string; release_year: number | null }
For convenience ``filter_single`` also supports a simplified syntax that
eliminates the need for ``e.op`` when used on exclusive properties:
.. code-block:: typescript
e.select(e.Movie, (movie) => ({
id: true,
title: true,
release_year: true,
filter_single: { id: "2053a8b4-49b1-437a-84c8-e1b0291ccd9f" },
}));
This also works if an object type has a composite exclusive constraint:
.. code-block:: typescript
/*
type Movie {
...
constraint exclusive on (.title, .release_year);
}
*/
e.select(e.Movie, (movie) => ({
title: true,
filter_single: {
title: "The Avengers",
release_year: 2012
},
}));
Ordering and pagination
^^^^^^^^^^^^^^^^^^^^^^^
The special keys ``order_by``, ``limit``, and ``offset`` correspond to
equivalent EdgeQL clauses.
.. code-block:: typescript
const query = e.select(e.Movie, (movie) => ({
id: true,
title: true,
order_by: movie.title,
limit: 10,
offset: 10
}));
const result = await query.run(client);
// { id: true; title: true }[]
Operators
^^^^^^^^^
Note that the filter expression above uses ``e.op`` function, which is how to
use *operators* like ``=``, ``>=``, ``++``, and ``and``.
.. code-block:: typescript
// prefix (unary) operators
e.op("not", e.bool(true)); // not true
e.op("exists", e.set("hi")); // exists {"hi"}
// infix (binary) operators
e.op(e.int64(2), "+", e.int64(2)); // 2 + 2
e.op(e.str("Hello "), "++", e.str("World!")); // "Hello " ++ "World!"
// ternary operator (if/else)
e.op(e.str("😄"), "if", e.bool(true), "else", e.str("😢"));
// "😄" if true else "😢"
Update objects
^^^^^^^^^^^^^^
.. code-block:: typescript
const query = e.update(e.Movie, (movie) => ({
filter_single: { title: "Doctor Strange 2" },
set: {
title: "Doctor Strange in the Multiverse of Madness",
},
}));
const result = await query.run(client);
Delete objects
^^^^^^^^^^^^^^
.. code-block:: typescript
const query = e.delete(e.Movie, (movie) => ({
filter: e.op(movie.title, 'ilike', "the avengers%"),
}));
const result = await query.run(client);
// { id: string }[]
Delete multiple objects using an array of properties:
.. code-block:: typescript
const titles = ["The Avengers", "Doctor Strange 2"];
const query = e.delete(e.Movie, (movie) => ({
filter: e.op(
movie.title,
"in",
e.array_unpack(e.literal(e.array(e.str), titles))
)
}));
const result = await query.run(client);
// { id: string }[]
Note that we have to use ``array_unpack`` to cast our ``array<str>`` into a
``set<str>`` since the ``in`` operator works on sets. And we use ``literal`` to
create a custom literal since we're inlining the titles array into our query.
Here's an example of how to do this with params:
.. code-block:: typescript
const query = e.params({ titles: e.array(e.str) }, ({ titles }) =>
e.delete(e.Movie, (movie) => ({
filter: e.op(movie.title, "in", e.array_unpack(titles)),
}))
);
const result = await query.run(client, {
titles: ["The Avengers", "Doctor Strange 2"],
});
// { id: string }[]
Compose queries
^^^^^^^^^^^^^^^
All query expressions are fully composable; this is one of the major
differentiators between this query builder and a typical ORM. For instance, we
can ``select`` an ``insert`` query in order to fetch properties of the object we
just inserted.
.. code-block:: typescript
const newMovie = e.insert(e.Movie, {
title: "Iron Man",
release_year: 2008
});
const query = e.select(newMovie, () => ({
title: true,
release_year: true,
num_actors: e.count(newMovie.actors)
}));
const result = await query.run(client);
// { title: string; release_year: number; num_actors: number }
Or we can use subqueries inside mutations.
.. code-block:: typescript
// select Doctor Strange
const drStrange = e.select(e.Movie, (movie) => ({
filter_single: { title: "Doctor Strange" }
}));
// select actors
const actors = e.select(e.Person, (person) => ({
filter: e.op(
person.name,
"in",
e.set("Benedict Cumberbatch", "Rachel McAdams")
)
}));
// add actors to cast of drStrange
const query = e.update(drStrange, () => ({
actors: { "+=": actors }
}));
const result = await query.run(client);
Parameters
^^^^^^^^^^
.. code-block:: typescript
const query = e.params({
title: e.str,
release_year: e.int64,
},
(params) => {
return e.insert(e.Movie, {
title: params.title,
release_year: params.release_year,
}))
};
const result = await query.run(client, {
title: "Thor: Love and Thunder",
release_year: 2022,
});
// { id: string }
.. note::
Continue reading for more complete documentation on how to express any
EdgeQL query with the query builder.
.. _ref_geljs_globals:
Globals
^^^^^^^
Reference global variables.
.. code-block:: typescript
e.global.user_id;
e.default.global.user_id; // same as above
e.my_module.global.some_value;
Other modules
^^^^^^^^^^^^^
Reference entities in modules other than ``default``.
The ``Vampire`` type in a module named ``characters``:
.. code-block:: typescript
e.characters.Vampire;
As shown in "Globals," a global ``some_value`` in a module ``my_module``:
.. code-block:: typescript
e.my_module.global.some_value;
================================================================================
.. File: reference.rst
.. _gel-js-api-reference:
#########
Reference
#########
.. _gel-js-api-client:
Client
======
.. js:function:: createClient( \
options: string | ConnectOptions | null \
): Client
Creates a new :js:class:`Client` instance.
:param options:
This is an optional parameter. When it is not specified the client
will connect to the current |Gel| Project instance.
If this parameter is a string it can represent either a
DSN or an instance name:
* when the string does not start with |geluri| it is a
:ref:`name of an instance <ref_reference_connection_instance_name>`;
* otherwise it specifies a single string in the connection URI format:
:geluri:`user:password@host:port/database?option=value`.
See the :ref:`Connection Parameters <ref_reference_connection>`
docs for full details.
Alternatively the parameter can be a ``ConnectOptions`` config;
see the documentation of valid options below.
:param string options.dsn:
Specifies the DSN of the instance.
:param string options.credentialsFile:
Path to a file containing credentials.
:param string options.host:
Database host address as either an IP address or a domain name.
:param number options.port:
Port number to connect to at the server host.
:param string options.user:
The name of the database role used for authentication.
:param string options.database:
The name of the database to connect to.
:param string options.password:
Password to be used for authentication, if the server requires one.
:param string options.tlsCAFile:
Path to a file containing the root certificate of the server.
:param boolean options.tlsSecurity:
Determines whether certificate and hostname verification is enabled.
Valid values are ``'strict'`` (certificate will be fully validated),
``'no_host_verification'`` (certificate will be validated, but
hostname may not match), ``'insecure'`` (certificate not validated,
self-signed certificates will be trusted), or ``'default'`` (acts as
``strict`` by default, or ``no_host_verification`` if ``tlsCAFile``
is set).
The above connection options can also be specified by their corresponding
environment variable. If none of ``dsn``, ``credentialsFile``, ``host`` or
``port`` are explicitly specified, the client will connect to your
linked project instance, if it exists. For full details, see the
:ref:`Connection Parameters <ref_reference_connection>` docs.
:param number options.timeout:
Connection timeout in milliseconds.
:param number options.waitUntilAvailable:
If first connection fails, the number of milliseconds to keep retrying
to connect (Defaults to 30 seconds). Useful if your development
instance and app are started together, to allow the server time to
be ready.
:param number options.concurrency:
The maximum number of connection the ``Client`` will create in it's
connection pool. If not specified the concurrency will be controlled
by the server. This is recommended as it allows the server to better
manage the number of client connections based on it's own available
resources.
:returns:
Returns an instance of :js:class:`Client`.
Example:
.. code-block:: javascript
// Use the Node.js assert library to test results.
const assert = require("assert");
const gel = require("gel");
async function main() {
const client = gel.createClient();
const data = await client.querySingle("select 1 + 1");
// The result is a number 2.
assert(typeof data === "number");
assert(data === 2);
}
main();
.. js:class:: Client
A ``Client`` allows you to run queries on a |Gel| instance.
Since opening connections is an expensive operation, ``Client`` also
maintains a internal pool of connections to the instance, allowing
connections to be automatically reused, and you to run multiple queries
on the client simultaneously, enhancing the performance of
database interactions.
:js:class:`Client` is not meant to be instantiated directly;
:js:func:`createClient` should be used instead.
.. _gel-js-api-async-optargs:
.. note::
Some methods take query arguments as an *args* parameter. The type of
the *args* parameter depends on the query:
* If the query uses positional query arguments, the *args* parameter
must be an ``array`` of values of the types specified by each query
argument's type cast.
* If the query uses named query arguments, the *args* parameter must
be an ``object`` with property names and values corresponding to
the query argument names and type casts.
If a query argument is defined as ``optional``, the key/value can be
either omitted from the *args* object or be a ``null`` value.
.. js:method:: execute(query: string, args?: QueryArgs): Promise<void>
Execute an EdgeQL command (or commands).
:param query: Query text.
This method takes :ref:`optional query arguments
<gel-js-api-async-optargs>`.
Example:
.. code-block:: javascript
await client.execute(`
CREATE TYPE MyType {
CREATE PROPERTY a -> int64
};
for x in {100, 200, 300}
union (insert MyType { a := x });
`)
.. js:method:: query<T>(query: string, args?: QueryArgs): Promise<T[]>
Run an EdgeQL query and return the results as an array.
This method **always** returns an array.
This method takes :ref:`optional query arguments
<gel-js-api-async-optargs>`.
.. js:method:: queryRequired<T>( \
query: string, \
args?: QueryArgs \
): Promise<[T, ...T[]]>
Run a query that returns at least one element and return the result as an
array.
This method takes :ref:`optional query arguments
<gel-js-api-async-optargs>`.
The *query* must return at least one element. If the query less than one
element, a ``ResultCardinalityMismatchError`` error is thrown.
.. js:method:: querySingle<T>( \
query: string, \
args?: QueryArgs \
): Promise<T | null>
Run an optional singleton-returning query and return the result.
This method takes :ref:`optional query arguments
<gel-js-api-async-optargs>`.
The *query* must return no more than one element. If the query returns
more than one element, a ``ResultCardinalityMismatchError`` error is
thrown.
.. js:method:: queryRequiredSingle<T>( \
query: string, \
args?: QueryArgs \
): Promise<T>
Run a singleton-returning query and return the result.
This method takes :ref:`optional query arguments
<gel-js-api-async-optargs>`.
The *query* must return exactly one element. If the query returns
more than one element, a ``ResultCardinalityMismatchError`` error is
thrown. If the query returns an empty set, a ``NoDataError`` error is
thrown.
.. js:method:: queryJSON(query: string, args?: QueryArgs): Promise<string>
Run a query and return the results as a JSON-encoded string.
This method takes :ref:`optional query arguments
<gel-js-api-async-optargs>`.
.. note::
Caution is advised when reading ``decimal`` or ``bigint``
values using this method. The JSON specification does not
have a limit on significant digits, so a ``decimal`` or a
``bigint`` number can be losslessly represented in JSON.
However, JSON decoders in JavaScript will often read all
such numbers as ``number`` values, which may result in
precision loss. If such loss is unacceptable, then
consider casting the value into ``str`` and decoding it on
the client side into a more appropriate type, such as
BigInt_.
.. js:method:: queryRequiredJSON( \
query: string, \
args?: QueryArgs \
): Promise<string>
Run a query that returns at least one element and return the result as a
JSON-encoded string.
This method takes :ref:`optional query arguments
<gel-js-api-async-optargs>`.
The *query* must return at least one element. If the query less than one
element, a ``ResultCardinalityMismatchError`` error is thrown.
.. note::
Caution is advised when reading ``decimal`` or ``bigint``
values using this method. The JSON specification does not
have a limit on significant digits, so a ``decimal`` or a
``bigint`` number can be losslessly represented in JSON.
However, JSON decoders in JavaScript will often read all
such numbers as ``number`` values, which may result in
precision loss. If such loss is unacceptable, then
consider casting the value into ``str`` and decoding it on
the client side into a more appropriate type, such as
BigInt_.
.. js:method:: querySingleJSON( \
query: string, \
args?: QueryArgs \
): Promise<string>
Run an optional singleton-returning query and return its element
as a JSON-encoded string.
This method takes :ref:`optional query arguments
<gel-js-api-async-optargs>`.
The *query* must return at most one element. If the query returns
more than one element, an ``ResultCardinalityMismatchError`` error
is thrown.
.. note::
Caution is advised when reading ``decimal`` or ``bigint``
values using this method. The JSON specification does not
have a limit on significant digits, so a ``decimal`` or a
``bigint`` number can be losslessly represented in JSON.
However, JSON decoders in JavaScript will often read all
such numbers as ``number`` values, which may result in
precision loss. If such loss is unacceptable, then
consider casting the value into ``str`` and decoding it on
the client side into a more appropriate type, such as
BigInt_.
.. js:method:: queryRequiredSingleJSON( \
query: string, \
args?: QueryArgs \
): Promise<string>
Run a singleton-returning query and return its element as a
JSON-encoded string.
This method takes :ref:`optional query arguments
<gel-js-api-async-optargs>`.
The *query* must return exactly one element. If the query returns
more than one element, a ``ResultCardinalityMismatchError`` error
is thrown. If the query returns an empty set, a ``NoDataError`` error
is thrown.
.. note::
Caution is advised when reading ``decimal`` or ``bigint``
values using this method. The JSON specification does not
have a limit on significant digits, so a ``decimal`` or a
``bigint`` number can be losslessly represented in JSON.
However, JSON decoders in JavaScript will often read all
such numbers as ``number`` values, which may result in
precision loss. If such loss is unacceptable, then
consider casting the value into ``str`` and decoding it on
the client side into a more appropriate type, such as
BigInt_.
.. js:method:: executeSQL(query: string, args?: unknown[]): Promise<void>
Execute a SQL command.
:param query: SQL query text.
This method takes optional query arguments.
Example:
.. code-block:: javascript
await client.executeSQL(`
INSERT INTO "MyType"(prop) VALUES ("value");
`)
.. js:method:: querySQL<T>(query: string, args?: unknown[]): Promise<T[]>
Run a SQL query and return the results as an array.
This method **always** returns an array.
The array will contain the returned rows. By default, rows are
``Objects`` with columns addressable by name.
This can controlled with ``client.withSQLRowMode('array' | 'object')``
API.
This method takes optional query arguments.
Example:
.. code-block:: javascript
let vals = await client.querySQL(`SELECT 1 as foo`)
console.log(vals); // [{'foo': 1}]
vals = await client
.withSQLRowMode('array')
.querySQL(`SELECT 1 as foo`);
console.log(vals); // [[1]]
.. js:method:: transaction<T>( \
action: (tx: Transaction) => Promise<T> \
): Promise<T>
Execute a retryable transaction. The ``Transaction`` object passed to
the action function has the same ``execute`` and ``query*`` methods
as ``Client``.
This is the preferred method of initiating and running a database
transaction in a robust fashion. The ``transaction()`` method
will attempt to re-execute the transaction body if a transient error
occurs, such as a network error or a transaction serialization error.
The number of times ``transaction()`` will attempt to execute the
transaction, and the backoff timeout between retries can be
configured with :js:meth:`Client.withRetryOptions`.
See :ref:`gel-js-api-transaction` for more details.
Example:
.. code-block:: javascript
await client.transaction(async tx => {
const value = await tx.querySingle("select Counter.value")
await tx.execute(
`update Counter set { value := <int64>$value }`,
{value: value + 1},
)
});
Note that we are executing queries on the ``tx`` object rather
than on the original ``client``.
.. js:method:: ensureConnected(): Promise<Client>
If the client does not yet have any open connections in its pool,
attempts to open a connection, else returns immediately.
Since the client lazily creates new connections as needed (up to the
configured ``concurrency`` limit), the first connection attempt will
only occur when the first query is run a client. ``ensureConnected``
can be useful to catch any errors resulting from connection
mis-configuration by triggering the first connection attempt
explicitly.
Example:
.. code-block:: javascript
import {createClient} from 'gel';
async function getClient() {
try {
return await createClient('custom_instance').ensureConnected();
} catch (err) {
// handle connection error
}
}
function main() {
const client = await getClient();
await client.query('select ...');
}
.. js:method:: withGlobals(globals: {[name: string]: any}): Client
Returns a new ``Client`` instance with the specified global values.
The ``globals`` argument object is merged with any existing globals
defined on the current client instance.
Equivalent to using the ``set global`` command.
Example:
.. code-block:: javascript
const user = await client.withGlobals({
userId: '...'
}).querySingle(`
select User {name} filter .id = global userId
`);
.. js:method:: withModuleAliases(aliases: {[name: string]: string}): Client
Returns a new ``Client`` instance with the specified module aliases.
The ``aliases`` argument object is merged with any existing module
aliases defined on the current client instance.
If the alias ``name`` is ``module`` this is equivalent to using
the ``set module`` command, otherwise it is equivalent to the
``set alias`` command.
Example:
.. code-block:: javascript
const user = await client.withModuleAliases({
module: 'sys'
}).querySingle(`
select get_version_as_str()
`);
// "2.0"
.. js:method:: withConfig(config: {[name: string]: any}): Client
Returns a new ``Client`` instance with the specified client session
configuration. The ``config`` argument object is merged with any
existing session config defined on the current client instance.
Equivalent to using the ``configure session`` command. For available
configuration parameters refer to the
:ref:`Config documentation <ref_std_cfg>`.
.. js:method:: withRetryOptions(opts: { \
attempts?: number \
backoff?: (attempt: number) => number \
}): Client
Returns a new ``Client`` instance with the specified retry attempts
number and backoff time function (the time that retrying methods will
wait between retry attempts, in milliseconds), where options not given
are inherited from the current client instance.
The default number of attempts is ``3``. The default backoff
function returns a random time between 100 and 200ms multiplied by
``2 ^ attempt number``.
.. note::
The new client instance will share the same connection pool as the
client it's created from, so calling the ``ensureConnected``,
``close`` and ``terminate`` methods will affect all clients
sharing the pool.
Example:
.. code-block:: javascript
import {createClient} from 'gel';
function main() {
const client = createClient();
// By default transactions will retry if they fail
await client.transaction(async tx => {
// ...
});
const nonRetryingClient = client.withRetryOptions({
attempts: 1
});
// This transaction will not retry
await nonRetryingClient.transaction(async tx => {
// ...
});
}
.. js:method:: close(): Promise<void>
Close the client's open connections gracefully. When a client is
closed, all its underlying connections are awaited to complete their
pending operations, then closed. A warning is produced if the pool
takes more than 60 seconds to close.
.. note::
Clients will not prevent Node.js from exiting once all of it's
open connections are idle and Node.js has no further tasks it is
awaiting on, so it is not necessary to explicitly call ``close()``
if it is more convenient for your application.
(This does not apply to Deno, since Deno is missing the
required API's to ``unref`` idle connections)
.. js:method:: isClosed(): boolean
Returns true if ``close()`` has been called on the client.
.. js:method:: terminate(): void
Terminate all connections in the client, closing all connections non
gracefully. If the client is already closed, return without doing
anything.
.. _gel-js-datatypes:
Type conversion
===============
The client automatically converts |Gel| types to the corresponding JavaScript
types and vice versa.
The table below shows the correspondence between Gel and JavaScript types.
.. list-table::
* - **Gel Type**
- **JavaScript Type**
* - ``multi`` set
- ``Array``
* - ``array<anytype>``
- ``Array``
* - ``anytuple``
- ``Array``
* - ``anyenum``
- ``string``
* - ``Object``
- ``object``
* - ``bool``
- ``boolean``
* - ``bytes``
- ``Uint8Array``
* - ``str``
- ``string``
* - ``float32``, ``float64``, ``int16``, ``int32``, ``int64``
- ``number``
* - ``bigint``
- ``BigInt``
* - ``decimal``
- n/a
* - ``json``
- ``unknown``
* - ``uuid``
- ``string``
* - ``datetime``
- ``Date``
* - ``cal::local_date``
- :js:class:`LocalDate`
* - ``cal::local_time``
- :js:class:`LocalTime`
* - ``cal::local_datetime``
- :js:class:`LocalDateTime`
* - ``duration``
- :js:class:`Duration`
* - ``cal::relative_duration``
- :js:class:`RelativeDuration`
* - ``cal::date_duration``
- :js:class:`DateDuration`
* - ``range<anytype>``
- :js:class:`Range`
* - ``cfg::memory``
- :js:class:`ConfigMemory`
.. note::
Inexact single-precision ``float`` values may have a different
representation when decoded into a JavaScript number. This is inherent
to the implementation of limited-precision floating point types.
If you need the decimal representation to match, cast the expression
to ``float64`` in your query.
.. note::
Due to precision limitations the ``decimal`` type cannot be decoded to a
JavaScript number. Use an explicit cast to ``float64`` if the precision
degradation is acceptable or a cast to ``str`` for an exact decimal
representation.
Arrays
======
Gel ``array`` maps onto the JavaScript ``Array``.
.. code-block:: javascript
// Use the Node.js assert library to test results.
const assert = require("assert");
const gel = require("gel");
async function main() {
const client = gel.createClient();
const data = await client.querySingle("select [1, 2, 3]");
// The result is an Array.
assert(data instanceof Array);
assert(typeof data[0] === "number");
assert(data.length === 3);
assert(data[2] === 3);
}
main();
.. _gel-js-types-object:
Objects
=======
``Object`` represents an object instance returned from a query. The value of an
object property or a link can be accessed through a corresponding object key:
.. code-block:: javascript
// Use the Node.js assert library to test results.
const assert = require("assert");
const gel = require("gel");
async function main() {
const client = gel.createClient();
const data = await client.querySingle(`
select schema::Property {
name,
annotations: {name, @value}
}
filter .name = 'listen_port'
and .source.name = 'cfg::Config'
limit 1
`);
// The property 'name' is accessible.
assert(typeof data.name === "string");
// The link 'annotaions' is accessible and is a Set.
assert(typeof data.annotations === "object");
assert(data.annotations instanceof gel.Set);
// The Set of 'annotations' is array-like.
assert(data.annotations.length > 0);
assert(data.annotations[0].name === "cfg::system");
assert(data.annotations[0]["@value"] === "true");
}
main();
Tuples
======
A regular |Gel| ``tuple`` becomes an ``Array`` in JavaScript.
.. code-block:: javascript
// Use the Node.js assert library to test results.
const assert = require("assert");
const gel = require("gel");
async function main() {
const client = gel.createClient();
const data = await client.querySingle(`
select (1, 'a', [3])
`);
// The resulting tuple is an Array.
assert(data instanceof Array);
assert(data.length === 3);
assert(typeof data[0] === "number");
assert(typeof data[1] === "string");
assert(data[2] instanceof Array);
}
main();
Named Tuples
============
A named |Gel| ``tuple`` becomes an ``Array``-like ``object`` in JavaScript,
where the elements are accessible either by their names or indexes.
.. code-block:: javascript
// Use the Node.js assert library to test results.
const assert = require("assert");
const gel = require("gel");
async function main() {
const client = gel.createClient();
const data = await client.querySingle(`
select (a := 1, b := 'a', c := [3])
`);
// The resulting tuple is an Array.
assert(data instanceof Array);
assert(data.length === 3);
assert(typeof data[0] === "number");
assert(typeof data[1] === "string");
assert(data[2] instanceof Array);
// Elements can be accessed by their names.
assert(typeof data.a === "number");
assert(typeof data["b"] === "string");
assert(data.c instanceof Array);
}
main();
Local Date
==========
.. js:class:: LocalDate(\
year: number, \
month: number, \
day: number)
A JavaScript representation of a |Gel| ``local_date`` value. Implements
a subset of the `TC39 Temporal Proposal`_ ``PlainDate`` type.
Assumes the calendar is always `ISO 8601`_.
.. js:attribute:: year: number
The year value of the local date.
.. js:attribute:: month: number
The numerical month value of the local date.
.. note::
Unlike the JS ``Date`` object, months in ``LocalDate`` start at 1.
ie. Jan = 1, Feb = 2, etc.
.. js:attribute:: day: number
The day of the month value of the local date (starting with 1).
.. js:attribute:: dayOfWeek: number
The weekday number of the local date. Returns a value between 1 and 7
inclusive, where 1 = Monday and 7 = Sunday.
.. js:attribute:: dayOfYear: number
The ordinal day of the year of the local date. Returns a value between
1 and 365 (or 366 in a leap year).
.. js:attribute:: weekOfYear: number
The ISO week number of the local date. Returns a value between 1 and
53, where ISO week 1 is defined as the week containing the first
Thursday of the year.
.. js:attribute:: daysInWeek: number
The number of days in the week of the local date. Always returns 7.
.. js:attribute:: daysInMonth: number
The number of days in the month of the local date. Returns a value
between 28 and 31 inclusive.
.. js:attribute:: daysInYear: number
The number of days in the year of the local date. Returns either 365 or
366 if the year is a leap year.
.. js:attribute:: monthsInYear: number
The number of months in the year of the local date. Always returns 12.
.. js:attribute:: inLeapYear: boolean
Return whether the year of the local date is a leap year.
.. js:method:: toString(): string
Get the string representation of the ``LocalDate`` in the
``YYYY-MM-DD`` format.
.. js:method:: toJSON(): number
Same as :js:meth:`~LocalDate.toString`.
.. js:method:: valueOf(): never
Always throws an Error. ``LocalDate`` objects are not comparable.
Local Time
==========
.. js:class:: LocalTime(\
hour: number = 0, \
minute: number = 0, \
second: number = 0, \
millisecond: number = 0, \
microsecond: number = 0, \
nanosecond: number = 0)
A JavaScript representation of an Gel ``local_time`` value. Implements
a subset of the `TC39 Temporal Proposal`_ ``PlainTime`` type.
.. note::
The Gel ``local_time`` type only has microsecond precision, any
nanoseconds specified in the ``LocalTime`` will be ignored when
encoding to an Gel ``local_time``.
.. js:attribute:: hour: number
The hours component of the local time in 0-23 range.
.. js:attribute:: minute: number
The minutes component of the local time in 0-59 range.
.. js:attribute:: second: number
The seconds component of the local time in 0-59 range.
.. js:attribute:: millisecond: number
The millisecond component of the local time in 0-999 range.
.. js:attribute:: microsecond: number
The microsecond component of the local time in 0-999 range.
.. js:attribute:: nanosecond: number
The nanosecond component of the local time in 0-999 range.
.. js:method:: toString(): string
Get the string representation of the ``local_time`` in the ``HH:MM:SS``
24-hour format.
.. js:method:: toJSON(): string
Same as :js:meth:`~LocalTime.toString`.
.. js:method:: valueOf(): never
Always throws an Error. ``LocalTime`` objects are not comparable.
Local Date and Time
===================
.. js:class:: LocalDateTime(\
year: number, \
month: number, \
day: number, \
hour: number = 0, \
minute: number = 0, \
second: number = 0, \
millisecond: number = 0, \
microsecond: number = 0, \
nanosecond: number = 0) extends LocalDate, LocalTime
A JavaScript representation of a |Gel| ``local_datetime`` value.
Implements a subset of the `TC39 Temporal Proposal`_ ``PlainDateTime``
type.
Inherits all properties from the :js:class:`~LocalDate` and
:js:class:`~LocalTime` types.
.. js:method:: toString(): string
Get the string representation of the ``local_datetime`` in the
``YYYY-MM-DDTHH:MM:SS`` 24-hour format.
.. js:method:: toJSON(): string
Same as :js:meth:`~LocalDateTime.toString`.
.. js:method:: valueOf(): never
Always throws an Error. ``LocalDateTime`` objects are not comparable.
Duration
========
.. js:class:: Duration(\
years: number = 0, \
months: number = 0, \
weeks: number = 0, \
days: number = 0, \
hours: number = 0, \
minutes: number = 0, \
seconds: number = 0, \
milliseconds: number = 0, \
microseconds: number = 0, \
nanoseconds: number = 0)
A JavaScript representation of a Gel ``duration`` value. This class
attempts to conform to the `TC39 Temporal Proposal`_ ``Duration`` type as
closely as possible.
No arguments may be infinite and all must have the same sign.
Any non-integer arguments will be rounded towards zero.
.. note::
The Temporal ``Duration`` type can contain both absolute duration
components, such as hours, minutes, seconds, etc. and relative
duration components, such as years, months, weeks, and days, where
their absolute duration changes depending on the exact date they are
relative to (eg. different months have a different number of days).
The Gel ``duration`` type only supports absolute durations, so any
``Duration`` with non-zero years, months, weeks, or days will throw
an error when trying to encode them.
.. note::
The Gel ``duration`` type only has microsecond precision, any
nanoseconds specified in the ``Duration`` will be ignored when
encoding to an Gel ``duration``.
.. note::
Temporal ``Duration`` objects can be unbalanced_, (ie. have a greater
value in any property than it would naturally have, eg. have a seconds
property greater than 59), but Gel ``duration`` objects are always
balanced.
Therefore in a round-trip of a ``Duration`` object to Gel and back,
the returned object, while being an equivalent duration, may not
have exactly the same property values as the sent object.
.. js:attribute:: years: number
The number of years in the duration.
.. js:attribute:: months: number
The number of months in the duration.
.. js:attribute:: weeks: number
The number of weeks in the duration.
.. js:attribute:: days: number
The number of days in the duration.
.. js:attribute:: hours: number
The number of hours in the duration.
.. js:attribute:: minutes: number
The number of minutes in the duration.
.. js:attribute:: seconds: number
The number of seconds in the duration.
.. js:attribute:: milliseconds: number
The number of milliseconds in the duration.
.. js:attribute:: microseconds: number
The number of microseconds in the duration.
.. js:attribute:: nanoseconds: number
The number of nanoseconds in the duration.
.. js:attribute:: sign: number
Returns -1, 0, or 1 depending on whether the duration is negative,
zero or positive.
.. js:attribute:: blank: boolean
Returns ``true`` if the duration is zero.
.. js:method:: toString(): string
Get the string representation of the duration in `ISO 8601 duration`_
format.
.. js:method:: toJSON(): number
Same as :js:meth:`~Duration.toString`.
.. js:method:: valueOf(): never
Always throws an Error. ``Duration`` objects are not comparable.
RelativeDuration
================
.. js:class:: RelativeDuration(\
years: number = 0, \
months: number = 0, \
weeks: number = 0, \
days: number = 0, \
hours: number = 0, \
minutes: number = 0, \
seconds: number = 0, \
milliseconds: number = 0, \
microseconds: number = 0)
A JavaScript representation of an Gel
:eql:type:`cal::relative_duration` value. This type represents a
non-definite span of time such as "2 years 3 days". This cannot be
represented as a :eql:type:`duration` because a year has no absolute
duration; for instance, leap years are longer than non-leap years.
This class attempts to conform to the `TC39 Temporal Proposal`_
``Duration`` type as closely as possible.
Internally, a ``cal::relative_duration`` value is represented as an
integer number of months, days, and seconds. During encoding, other units
will be normalized to these three. Sub-second units like ``microseconds``
will be ignored.
.. js:attribute:: years: number
The number of years in the relative duration.
.. js:attribute:: months: number
The number of months in the relative duration.
.. js:attribute:: weeks: number
The number of weeks in the relative duration.
.. js:attribute:: days: number
The number of days in the relative duration.
.. js:attribute:: hours: number
The number of hours in the relative duration.
.. js:attribute:: minutes: number
The number of minutes in the relative duration.
.. js:attribute:: seconds: number
The number of seconds in the relative duration.
.. js:attribute:: milliseconds: number
The number of milliseconds in the relative duration.
.. js:attribute:: microseconds: number
The number of microseconds in the relative duration.
.. js:method:: toString(): string
Get the string representation of the duration in `ISO 8601 duration`_
format.
.. js:method:: toJSON(): string
Same as :js:meth:`~Duration.toString`.
.. js:method:: valueOf(): never
Always throws an Error. ``RelativeDuration`` objects are not
comparable.
DateDuration
============
.. js:class:: DateDuration( \
years: number = 0, \
months: number = 0, \
weeks: number = 0, \
days: number = 0, \
)
A JavaScript representation of an Gel
:eql:type:`cal::date_duration` value. This type represents a
non-definite span of time consisting of an integer number of *months* and
*days*.
This type is primarily intended to simplify logic involving
:eql:type:`cal::local_date` values.
.. code-block:: edgeql-repl
db> select <cal::date_duration>'5 days';
{<cal::date_duration>'P5D'}
db> select <cal::local_date>'2022-06-25' + <cal::date_duration>'5 days';
{<cal::local_date>'2022-06-30'}
db> select <cal::local_date>'2022-06-30' - <cal::local_date>'2022-06-25';
{<cal::date_duration>'P5D'}
Internally, a ``cal::relative_duration`` value is represented as an
integer number of months and days. During encoding, other units will be
normalized to these two.
.. js:attribute:: years: number
The number of years in the relative duration.
.. js:attribute:: months: number
The number of months in the relative duration.
.. js:attribute:: weeks: number
The number of weeks in the relative duration.
.. js:attribute:: days: number
The number of days in the relative duration.
.. js:method:: toString(): string
Get the string representation of the duration in `ISO 8601 duration`_
format.
.. js:method:: toJSON(): string
Same as :js:meth:`~Duration.toString`.
.. js:method:: valueOf(): never
Always throws an Error. ``DateDuration`` objects are not comparable.
Memory
======
.. js:class:: ConfigMemory(bytes: BigInt)
A JavaScript representation of an Gel ``cfg::memory`` value.
.. js:attribute:: bytes: number
The memory value in bytes (B).
.. note::
The Gel ``cfg::memory`` represents a number of bytes stored as
an ``int64``. Since JS the ``number`` type is a ``float64``, values
above ``~8191TiB`` will lose precision when represented as a JS
``number``. To keep full precision use the ``bytesBigInt``
property.
.. js::attribute:: bytesBigInt: BigInt
The memory value in bytes represented as a ``BigInt``.
.. js:attribute:: kibibytes: number
The memory value in kibibytes (KiB).
.. js:attribute:: mebibytes: number
The memory value in mebibytes (MiB).
.. js:attribute:: gibibytes: number
The memory value in gibibytes (GiB).
.. js:attribute:: tebibytes: number
The memory value in tebibytes (TiB).
.. js:attribute:: pebibytes: number
The memory value in pebibytes (PiB).
.. js:method:: toString(): string
Get the string representation of the memory value. Format is the same
as returned by string casting a ``cfg::memory`` value in Gel.
Range
=====
.. js:class:: Range(\
lower: T | null, \
upper: T | null, \
incLower: boolean = true, \
incUpper: boolean = false \
)
A JavaScript representation of an Gel ``std::range`` value. This is a generic TypeScript class with the following type signature.
.. code-block:: typescript
class Range<
T extends number | Date | LocalDate | LocalDateTime | Duration
>{
// ...
}
.. js:attribute:: lower: T
The lower bound of the range value.
.. js:attribute:: upper: T
The upper bound of the range value.
.. js:attribute:: incLower: boolean
Whether the lower bound is inclusive.
.. js:attribute:: incUpper: boolean
Whether the upper bound is inclusive.
.. js:attribute:: empty: boolean
Whether the range is empty.
.. js:method:: toJSON(): { \
lower: T | null; \
upper: T | null; \
inc_lower: boolean; \
inc_upper: boolean; \
empty?: undefined; \
}
Returns a JSON-encodable representation of the range.
.. js:method:: empty(): Range
A static method to declare an empty range (no bounds).
.. code-block:: typescript
Range.empty();
.. _BigInt:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt
.. _TC39 Temporal Proposal: https://tc39.es/proposal-temporal/docs/
.. _ISO 8601: https://en.wikipedia.org/wiki/ISO_8601#Dates
.. _ISO 8601 duration: https://en.wikipedia.org/wiki/ISO_8601#Durations
.. _unbalanced: https://tc39.es/proposal-temporal/docs/balancing.html
================================================================================
.. File: select.rst
.. _gel-js-select:
Select
======
The full power of the EdgeQL ``select`` statement is available as a top-level
``e.select`` function. All queries on this page assume the Netflix schema
described on the :ref:`Objects page <gel-js-objects>`.
Selecting scalars
-----------------
Any scalar expression be passed into ``e.select``, though it's often
unnecessary, since expressions are ``run``\ able without being wrapped by
``e.select``.
.. code-block:: typescript
e.select(e.str('Hello world'));
// select 1234;
e.select(e.op(e.int64(2), '+', e.int64(2)));
// select 2 + 2;
Selecting objects
-----------------
As in EdgeQL, selecting an set of objects without a shape will return their
``id`` property only. This is reflected in the TypeScript type of the result.
.. code-block:: typescript
const query = e.select(e.Movie);
// select Movie;
const result = await query.run(client);
// {id:string}[]
Shapes
^^^^^^
To specify a shape, pass a function as the second argument. This function
should return an object that specifies which properties to include in the
result. This roughly corresponds to a *shape* in EdgeQL.
.. code-block:: typescript
const query = e.select(e.Movie, ()=>({
id: true,
title: true,
release_year: true,
}));
/*
select Movie {
id,
title,
release_year
}
*/
Note that the type of the query result is properly inferred from the shape.
This is true for all queries on this page.
.. code-block:: typescript
const result = await query.run(client);
/* {
id: string;
title: string;
release_year: number | null;
}[] */
As you can see, the type of ``release_year`` is ``number | null`` since
it's an optional property, whereas ``id`` and ``title`` are required.
Passing a ``boolean`` value (as opposed to a ``true`` literal), which will
make the property optional. Passing ``false`` will exclude that property.
.. code-block:: typescript
e.select(e.Movie, movie => ({
id: true,
title: Math.random() > 0.5,
release_year: false,
}));
const result = await query.run(client);
// {id: string; title: string | undefined; release_year: never}[]
Selecting all properties
^^^^^^^^^^^^^^^^^^^^^^^^
For convenience, the query builder provides a shorthand for selecting all
properties of a given object.
.. code-block:: typescript
e.select(e.Movie, movie => ({
...e.Movie['*']
}));
const result = await query.run(client);
// {id: string; title: string; release_year: number | null}[]
This ``*`` property is just a strongly-typed, plain object:
.. code-block:: typescript
e.Movie['*'];
// => {id: true, title: true, release_year: true}
Select a single object
^^^^^^^^^^^^^^^^^^^^^^
To select a particular object, use the ``filter_single`` key. This tells the query builder to expect a singleton result.
.. code-block:: typescript
e.select(e.Movie, (movie) => ({
id: true,
title: true,
release_year: true,
filter_single: {id: '2053a8b4-49b1-437a-84c8-e1b0291ccd9f'},
}));
This also works if an object type has a composite exclusive constraint:
.. code-block:: typescript
/*
type Movie {
...
constraint exclusive on (.title, .release_year);
}
*/
e.select(e.Movie, (movie) => ({
title: true,
filter_single: {title: 'The Avengers', release_year: 2012},
}));
You can also pass an arbitrary boolean expression to ``filter_single`` if you prefer.
.. code-block:: typescript
const query = e.select(e.Movie, (movie) => ({
id: true,
title: true,
release_year: true,
filter_single: e.op(movie.id, '=', '2053a8b4-49b1-437a-84c8-e1b0291ccd9f'),
}));
const result = await query.run(client);
// {id: string; title: string; release_year: number | null}
Select many objects by ID
^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: typescript
const query = e.params({ ids: e.array(e.uuid) }, ({ ids }) =>
e.select(e.Movie, (movie) => ({
id: true,
title: true,
release_year: true,
filter: e.op(movie.id, 'in', e.array_unpack(ids)),
}))
const result = await query.run(client, {
ids: [
'2053a8b4-49b1-437a-84c8-e1b0291ccd9f',
'2053a8b4-49b1-437a-84c8-af5d3f383484',
],
})
// {id: string; title: string; release_year: number | null}[]
Nesting shapes
^^^^^^^^^^^^^^
As in EdgeQL, shapes can be nested to fetch deeply related objects.
.. code-block:: typescript
const query = e.select(e.Movie, () => ({
id: true,
title: true,
actors: {
name: true
}
}));
const result = await query.run(client);
/* {
id: string;
title: string;
actors: { name: string }[]
}[] */
Portable shapes
^^^^^^^^^^^^^^^
You can use ``e.shape`` to define a "portable shape" that can be defined
independently and used in multiple queries.
.. code-block:: typescript
const baseShape = e.shape(e.Movie, (m) => ({
title: true,
num_actors: e.count(m)
}));
const query = e.select(e.Movie, m => ({
...baseShape(m),
release_year: true,
filter_single: {title: 'The Avengers'}
}))
.. note::
Note that the result of ``e.shape`` is a *function*. When you use the shape
in your final queries, be sure to pass in the *scope variable* (e.g. ``m``
in the example above). This is required for the query builder to correctly
resolve the query.
Why closures?
-------------
In EdgeQL, a ``select`` statement introduces a new *scope*; within the clauses
of a select statement, you can refer to fields of the *elements being
selected* using leading dot notation.
.. code-block:: edgeql
select Movie { id, title }
filter .title = "The Avengers";
Here, ``.title`` is shorthand for the ``title`` property of the selected
``Movie`` elements. All properties/links on the ``Movie`` type can be
referenced using this shorthand anywhere in the ``select`` expression. In
other words, the ``select`` expression is *scoped* to the ``Movie`` type.
To represent this scoping in the query builder, we use function scoping. This
is a powerful pattern that makes it painless to represent filters, ordering,
computed fields, and other expressions. Let's see it in action.
Filtering
---------
To add a filtering clause, just include a ``filter`` key in the returned
params object. This should correspond to a boolean expression.
.. code-block:: typescript
e.select(e.Movie, movie => ({
id: true,
title: true,
filter: e.op(movie.title, 'ilike', "The Matrix%")
}));
/*
select Movie {
id,
title
} filter .title ilike "The Matrix%"
*/
.. note::
Since ``filter`` is a :ref:`reserved keyword <ref_eql_lexical_names>` in
|Gel|, there is minimal danger of conflicting with a property or link named
``filter``. All shapes can contain filter clauses, even nested ones.
If you have many conditions you want to test for, your filter can start to get
difficult to read.
.. code-block:: typescript
e.select(e.Movie, movie => ({
id: true,
title: true,
filter: e.op(
e.op(
e.op(movie.title, 'ilike', "The Matrix%"),
'and',
e.op(movie.release_year, '=', 1999)
),
'or',
e.op(movie.title, '=', 'Iron Man')
)
}));
To improve readability, we recommend breaking these operations out into named
variables and composing them.
.. code-block:: typescript
e.select(e.Movie, movie => {
const isAMatrixMovie = e.op(movie.title, 'ilike', "The Matrix%");
const wasReleased1999 = e.op(movie.release_year, '=', 1999);
const isIronMan = e.op(movie.title, '=', 'Iron Man');
return {
id: true,
title: true,
filter: e.op(
e.op(
isAMatrixMovie,
'and',
wasReleased1999
),
'or',
isIronMan
)
}
});
You can combine compound conditions as much or as little as makes sense for
your application.
.. code-block:: typescript
e.select(e.Movie, movie => {
const isAMatrixMovie = e.op(movie.title, 'ilike', "The Matrix%");
const wasReleased1999 = e.op(movie.release_year, '=', 1999);
const isAMatrixMovieReleased1999 = e.op(
isAMatrixMovie,
'and',
wasReleased1999
);
const isIronMan = e.op(movie.title, '=', 'Iron Man');
return {
id: true,
title: true,
filter: e.op(
isAMatrixMovieReleased1999
'or',
isIronMan
)
}
});
If you need to string together several conditions with ``or``, ``e.any`` may be
a better choice. Be sure to wrap your conditions in ``e.set`` since ``e.any``
takes a set.
.. code-block:: typescript
e.select(e.Movie, movie => ({
id: true,
title: true,
filter: e.any(
e.set(
e.op(movie.title, "=", "Iron Man"),
e.op(movie.title, "ilike", "guardians%"),
e.op(movie.title, "ilike", "captain%")
)
),
}));
Similarly to ``e.any``, ``e.all`` can replace multiple conditions strung
together with ``and``.
.. code-block:: typescript
e.select(e.Movie, movie => ({
id: true,
title: true,
filter: e.all(
e.set(
e.op(movie.title, "ilike", "captain%"),
e.op(movie.title, "ilike", "%america%"),
e.op(movie.title, "ilike", "%:%")
)
),
}));
The conditions passed to ``e.any`` or ``e.all`` can be composed just like
before.
.. code-block:: typescript
e.select(e.Movie, movie => {
const isIronMan = e.op(movie.title, "=", "Iron Man");
const startsWithGuardians = e.op(movie.title, "ilike", "guardians%");
const startsWithCaptain = e.op(movie.title, "ilike", "captain%");
return {
id: true,
title: true,
filter: e.any(
e.set(
isIronMan,
startsWithGuardians,
startsWithCaptain
)
),
}
});
Filters on links
----------------
Links can be filtered using traditional filters.
.. code-block:: typescript
e.select(e.Movie, movie => ({
title: true,
actors: actor => ({
name: true,
filter: e.op(actor.name.slice(0, 1), '=', 'A'),
}),
filter_single: {title: 'Iron Man'}
}));
You can also use the :ref:`type intersection
<gel-js-objects-type-intersections>` operator to filter a link based on its
type. For example, since ``actor.roles`` might be of type ``Movie`` or
``TVShow``, to only return ``roles`` that are ``Movie`` types, you would use
the ``.is`` type intersection operator:
.. code-block:: typescript
e.select(e.Actor, actor => ({
movies: actor.roles.is(e.Movie),
}));
This is how you would use the EdgeQL :eql:op:`[is type] <isintersect>` type
intersection operator via the TypeScript query builder.
Filters on link properties
--------------------------
.. code-block:: typescript
e.select(e.Movie, movie => ({
title: true,
actors: actor => ({
name: true,
filter: e.op(actor['@character_name'], 'ilike', 'Tony Stark'),
}),
filter_single: {title: 'Iron Man'}
}));
Ordering
--------
As with ``filter``, you can pass a value with the special ``order_by`` key. To
simply order by a property:
.. code-block:: typescript
e.select(e.Movie, movie => ({
order_by: movie.title,
}));
.. note::
Unlike ``filter``, ``order_by`` is *not* a reserved word in |Gel|. Using
``order_by`` as a link or property name will create a naming conflict and
likely cause bugs.
The ``order_by`` key can correspond to an arbitrary expression.
.. code-block:: typescript
// order by length of title
e.select(e.Movie, movie => ({
order_by: e.len(movie.title),
}));
/*
select Movie
order by len(.title)
*/
// order by number of actors
e.select(e.Movie, movie => ({
order_by: e.count(movie.actors),
}));
/*
select Movie
order by count(.actors)
*/
You can customize the sort direction and empty-handling behavior by passing an
object into ``order_by``.
.. code-block:: typescript
e.select(e.Movie, movie => ({
order_by: {
expression: movie.title,
direction: e.DESC,
empty: e.EMPTY_FIRST,
},
}));
/*
select Movie
order by .title desc empty first
*/
.. list-table::
* - Order direction
- ``e.DESC`` ``e.ASC``
* - Empty handling
- ``e.EMPTY_FIRST`` ``e.EMPTY_LAST``
Pass an array of objects to do multiple ordering.
.. code-block:: typescript
e.select(e.Movie, movie => ({
title: true,
order_by: [
{
expression: movie.title,
direction: e.DESC,
},
{
expression: e.count(movie.actors),
direction: e.ASC,
empty: e.EMPTY_LAST,
},
],
}));
Pagination
----------
Use ``offset`` and ``limit`` to paginate queries. You can pass an expression
with an integer type or a plain JS number.
.. code-block:: typescript
e.select(e.Movie, movie => ({
offset: 50,
limit: e.int64(10),
}));
/*
select Movie
offset 50
limit 10
*/
Computeds
---------
To add a computed field, just add it to the returned shape alongside the other
elements. All reflected functions are typesafe, so the output type
.. code-block:: typescript
const query = e.select(e.Movie, movie => ({
title: true,
uppercase_title: e.str_upper(movie.title),
title_length: e.len(movie.title),
}));
const result = await query.run(client);
/* =>
[
{
title:"Iron Man",
uppercase_title: "IRON MAN",
title_length: 8
},
...
]
*/
// {name: string; uppercase_title: string, title_length: number}[]
Computed fields can "override" an actual link/property as long as the type
signatures agree.
.. code-block:: typescript
e.select(e.Movie, movie => ({
title: e.str_upper(movie.title), // this works
release_year: e.str("2012"), // TypeError
// you can override links too
actors: e.Person,
}));
.. _ref_qb_polymorphism:
Polymorphism
------------
EdgeQL supports polymorphic queries using the ``[is type]`` prefix.
.. code-block:: edgeql
select Content {
title,
[is Movie].release_year,
[is TVShow].num_seasons
}
In the query builder, this is represented with the ``e.is`` function.
.. code-block:: typescript
e.select(e.Content, content => ({
title: true,
...e.is(e.Movie, { release_year: true }),
...e.is(e.TVShow, { num_seasons: true }),
}));
const result = await query.run(client);
/* {
title: string;
release_year: number | null;
num_seasons: number | null;
}[] */
The ``release_year`` and ``num_seasons`` properties are nullable to reflect the
fact that they will only occur in certain objects.
.. note::
In EdgeQL it is not valid to select the ``id`` property in a polymorphic
field. So for convenience when using the ``['*']`` all properties shorthand
with ``e.is``, the ``id`` property will be filtered out of the polymorphic
shape object.
Detached
--------
Sometimes you need to "detach" a set reference from the current scope. (Read the :ref:`reference docs <ref_edgeql_with_detached>` for details.) You can achieve this in the query builder with the top-level ``e.detached`` function.
.. code-block:: typescript
const query = e.select(e.Person, (outer) => ({
name: true,
castmates: e.select(e.detached(e.Person), (inner) => ({
name: true,
filter: e.op(outer.acted_in, 'in', inner.acted_in)
})),
}));
/*
with outer := Person
select Person {
name,
castmates := (
select detached Person { name }
filter .acted_in in Person.acted_in
)
}
*/
Selecting free objects
----------------------
Select a free object by passing an object into ``e.select``
.. code-block:: typescript
e.select({
name: e.str("Name"),
number: e.int64(1234),
movies: e.Movie,
});
/* select {
name := "Name",
number := 1234,
movies := Movie
} */
================================================================================
.. File: types.rst
.. _gel-js-types-and-casting:
Types
-----
The entire type system of |Gel| is reflected in the ``e`` object, including
scalar types, object types, and enums. These types are used in queries for thinks like *casting* and *declaring parameters*.
.. code-block:: typescript
e.str;
e.bool;
e.int16;
e.int32;
e.int64;
e.float32;
e.float64;
e.bigint;
e.decimal;
e.datetime;
e.duration;
e.bytes;
e.json;
e.cal.local_datetime;
e.cal.local_date;
e.cal.local_time;
e.cal.relative_duration;
e.cal.date_duration;
e.Movie; // user-defined object type
e.Genre; // user-defined enum
You can construct array and tuple types, as in EdgeQL.
.. code-block:: typescript
e.array(e.bool);
// array<bool>
e.tuple([e.str, e.int64]);
// tuple<str, int64>
e.tuple({
name: e.str,
age: e.int64
});
// tuple<name: str, age: int64>
.. _ref_qb_casting:
Casting
^^^^^^^
These types can be used to *cast* one expression to another type.
.. code-block:: typescript
e.cast(e.json, e.int64('123'));
// <json>'123'
e.cast(e.duration, e.str('127 hours'));
// <duration>'127 hours'
.. note::
Scalar types like ``e.str`` serve a dual purpose. They can be used as
functions to instantiate literals (``e.str("hi")``) or used as variables
(``e.cast(e.str, e.int64(123))``).
Custom literals
^^^^^^^^^^^^^^^
You can use ``e.literal`` to create literals corresponding to collection
types like tuples, arrays, and primitives. The first argument expects a type,
the second expects a *value* of that type.
.. code-block:: typescript
e.literal(e.str, "sup");
// equivalent to: e.str("sup")
e.literal(e.array(e.int16), [1, 2, 3]);
// <array<int16>>[1, 2, 3]
e.literal(e.tuple([e.str, e.int64]), ['baz', 9000]);
// <tuple<str, int64>>("Goku", 9000)
e.literal(
e.tuple({name: e.str, power_level: e.int64}),
{name: 'Goku', power_level: 9000}
);
// <tuple<name: str, power_level: bool>>("asdf", false)
Parameters
^^^^^^^^^^
Types are also necessary for declaring *query parameters*.
Pass strongly-typed parameters into your query with ``e.params``.
.. code-block:: typescript
const query = e.params({name: e.str}, params =>
e.op(e.str("Yer a wizard, "), "++", params.name)
);
await query.run(client, {name: "Harry"});
// => "Yer a wizard, Harry"
The full documentation on using parameters is :ref:`here
<gel-js-parameters>`.
Polymorphism
^^^^^^^^^^^^
Types are also used to write polymorphic queries. For full documentation on
this, see :ref:`Polymorphism <ref_qb_polymorphism>` in the ``e.select``
documentation.
.. code-block:: typescript
e.select(e.Content, content => ({
title: true,
...e.is(e.Movie, { release_year: true }),
...e.is(e.TVShow, { num_seasons: true }),
}));
================================================================================
.. File: update.rst
.. _gel-js-update:
Update
------
Update objects with the ``e.update`` function.
.. code-block:: typescript
e.update(e.Movie, () => ({
filter_single: { title: "Avengers 4" },
set: {
title: "Avengers: Endgame"
}
}))
You can reference the current value of the object's properties.
.. code-block:: typescript
e.update(e.Movie, (movie) => ({
filter: e.op(movie.title[0], '=', ' '),
set: {
title: e.str_trim(movie.title)
}
}))
You can conditionally update a property by using an :ref:`optional parameter
<gel-js-optional-parameters>` and the :ref:`coalescing infix operator
<gel-js-funcops-infix>`.
.. code-block:: typescript
e.params({ id: e.uuid, title: e.optional(e.str) }, (params) =>
e.update(e.Movie, (movie) => ({
filter_single: { id: params.id },
set: {
title: e.op(params.title, "??", movie.title),
}
}))
);
Note that ``e.update`` will return just the ``{ id: true }`` of the updated object. If you want to select further properties, you can wrap the update in a ``e.select`` call. This is still just a single query to the database.
.. code-block:: typescript
e.params({ id: e.uuid, title: e.optional(e.str) }, (params) => {
const updated = e.update(e.Movie, (movie) => ({
filter_single: { id: params.id },
set: {
title: e.op(params.title, "??", movie.title),
},
}));
return e.select(updated, (movie) => ({
title: movie.title,
}));
});
Updating links
^^^^^^^^^^^^^^
EdgeQL supports some convenient syntax for appending to, subtracting from, and
overwriting links.
.. code-block:: edgeql
update Movie set {
# overwrite
actors := Person,
# add to link
actors += Person,
# subtract from link
actors -= Person
}
In the query builder this is represented with the following syntax.
**Overwrite a link**
.. code-block:: typescript
const actors = e.select(e.Person, ...);
e.update(e.Movie, movie => ({
filter_single: {title: 'The Eternals'},
set: {
actors: actors,
}
}))
**Add to a link**
.. code-block:: typescript
const actors = e.select(e.Person, ...);
e.update(e.Movie, movie => ({
filter_single: {title: 'The Eternals'},
set: {
actors: { "+=": actors },
}
}))
**Subtract from a link**
.. code-block:: typescript
const actors = e.select(e.Person, ...);
e.update(e.Movie, movie => ({
filter_single: {title: 'The Eternals'},
set: {
actors: { "-=": actors },
}
}))
**Updating a single link property**
.. code-block:: typescript
e.update(e.Movie, (movie) => ({
filter_single: { title: "The Eternals" },
set: {
actors: {
"+=": e.select(movie.actors, (actor) => ({
"@character_name": e.str("Sersi"),
filter: e.op(actor.name, "=", "Gemma Chan")
}))
}
}
}));
**Updating many link properties**
.. code-block:: typescript
const q = e.params(
{
cast: e.array(e.tuple({ name: e.str, character_name: e.str })),
},
(params) =>
e.update(e.Movie, (movie) => ({
filter_single: { title: "The Eternals" },
set: {
actors: {
"+=": e.for(e.array_unpack(params.cast), (cast) =>
e.select(movie.characters, (character) => ({
"@character_name": cast.character_name,
filter: e.op(cast.name, "=", character.name),
})),
),
},
},
})),
).run(client, {
cast: [
{ name: "Gemma Chan", character_name: "Sersi" },
{ name: "Richard Madden", character_name: "Ikaris" },
{ name: "Angelina Jolie", character_name: "Thena" },
{ name: "Salma Hayek", character_name: "Ajak" },
],
});
Bulk updates
^^^^^^^^^^^^
You can use a :ref:`for loop <gel-js-for>` to perform :ref:`bulk updates
<gel-js-for-bulk-inserts>`.
================================================================================
.. File: with.rst
.. _gel-js-with:
With Blocks
-----------
During the query rendering step, the number of occurrences of each expression
are tracked. If an expression occurs more than once it is automatically
extracted into a ``with`` block.
.. code-block:: typescript
const x = e.int64(3);
const y = e.select(e.op(x, '^', x));
y.toEdgeQL();
// with x := 3
// select x ^ x
const result = await y.run(client);
// => 27
This hold for expressions of arbitrary complexity.
.. code-block:: typescript
const robert = e.insert(e.Person, {
name: "Robert Pattinson"
});
const colin = e.insert(e.Person, {
name: "Colin Farrell"
});
const newMovie = e.insert(e.Movie, {
title: "The Batman",
actors: e.set(colin, robert)
});
/*
with
robert := (insert Person { name := "Robert Pattinson"}),
colin := (insert Person { name := "Colin Farrell"}),
insert Movie {
title := "The Batman",
actors := {robert, colin}
}
*/
Note that ``robert`` and ``colin`` were pulled out into a top-level with
block. To force these variables to occur in an internal ``with`` block, you
can short-circuit this logic with ``e.with``.
.. code-block:: typescript
const robert = e.insert(e.Person, {
name: "Robert Pattinson"
});
const colin = e.insert(e.Person, {
name: "Colin Farrell"
});
const newMovie = e.insert(e.Movie, {
actors: e.with([robert, colin], // list "dependencies"
e.select(e.set(robert, colin))
)
})
/*
insert Movie {
title := "The Batman",
actors := (
with
robert := (insert Person { name := "Robert Pattinson"}),
colin := (insert Person { name := "Colin Farrell"})
select {robert, colin}
)
}
*/
.. note::
It's an error to pass an expression into multiple
``e.with``\ s, or use an expression passed to ``e.with`` outside of that
block.
To explicitly create a detached "alias" of another expression, use ``e.alias``.
.. code-block:: typescript
const a = e.set(1, 2, 3);
const b = e.alias(a);
const query = e.select(e.op(a, '*', b))
// WITH
// a := {1, 2, 3},
// b := a
// SELECT a + b
const result = await query.run(client);
// => [1, 2, 3, 2, 4, 6, 3, 6, 9]
================================================================================