Gel Database MCP Server

.. 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] ================================================================================