Lexical Structure

Every EdgeQL command is composed of a sequence of tokens, terminated by a semicolon (;). The types of valid tokens as well as their order is determined by the syntax of the particular command.

EdgeQL is case sensistive except for keywords (in the examples the keywords are written in upper case as a matter of convention).

There are several kinds of tokens: keywords, identifiers, literals (constants) and symbols (operators and punctuation).

Tokens are normally separated by whitespace (space, tab, newline) or comments.

There are two ways of writing identifiers in EdgeQL: plain and quoted. The plain identifiers are similar to many other languages, they are alphanumeric with underscores and cannot start with a digit. The quoted identifiers start and end with a backtick `quoted.identifier` and can contain any characters inside with a few exceptions. They must not start with an ampersand (@) or contain a double colon (::). If there’s a need to include a backtick character as part of the identifier name a double-backtick sequence (``) should be used: `quoted``identifier` will result in the actual identifier being quoted`identifier.

identifier::= plain_ident | quoted_ident
plain_ident::= ident_first ident_rest*
ident_first::= <any letter, underscore>
ident_rest::= <any letter, digits, underscore>
quoted_ident::= "`" qident_first qident_rest* "`"
qident_first::= <any character except "@">
qident_rest::= <any character>

Quoted identifiers are usually needed to represent module names that contain a dot (.) or to distinguish names from reserved keywords (for instance to allow referring to a link named “order” as `order`).

There are a number of reserved and unreserved keywords in EdgeQL. Every identifier that is not a reserved keyword is a valid name. Names are used to refer to concepts, links, link properties, etc.

short_name::= not_keyword_ident | quoted_ident
not_keyword_ident::= <any plain_ident except for keyword>
keyword::= reserved_keyword | unreserved_keyword
reserved_keyword::= case insensitive sequence matching any of the following "AGGREGATE" | "ALTER" | "AND" | "ANY" | "COMMIT" | "CREATE" | "DELETE" | "DETACHED" | "DISTINCT" | "DROP" | "ELSE" | "EMPTY" | "EXISTS" | "FALSE" | "FILTER" | "FUNCTION" | "GET" | "GROUP" | "IF" | "ILIKE" | "IN" | "INSERT" | "IS" | "LIKE" | "LIMIT" | "MODULE" | "NOT" | "OFFSET" | "OR" | "ORDER" | "OVER" | "PARTITION" | "ROLLBACK" | "SELECT" | "SET" | "SINGLETON" | "START" | "TRUE" | "UPDATE" | "UNION" | "WITH"
unreserved_keyword::= case insensitive sequence matching any of the following "ABSTRACT" | "ACTION" | "AFTER" | "ARRAY" | "AS" | "ASC" | "ATOM" | "ANNOTATION" | "BEFORE" | "BY" | "CONCEPT" | "CONSTRAINT" | "DATABASE" | "DESC" | "EVENT" | "EXTENDING" | "FINAL" | "FIRST" | "FOR" | "FROM" | "INDEX" | "INITIAL" | "LAST" | "LINK" | "MAP" | "MIGRATION" | "OF" | "ON" | "POLICY" | "PROPERTY" | "REQUIRED" | "RENAME" | "TARGET" | "THEN" | "TO" | "TRANSACTION" | "TUPLE" | "VALUE" | "VIEW"

Fully-qualified names consist of a module, ::, and a short name. They can be used in most places where a short name can appear (such as paths and shapes).

A number of scalar types have literal constant expressions.

Production rules for str literals:

string::= str | raw_str
str::= "'" str_content* "'" | '"' str_content* '"'
raw_str::= "r'" raw_content* "'" | 'r"' raw_content* '"' | dollar_quote raw_content* dollar_quote
raw_content::= <any character different from delimiting quote>
dollar_quote::= "$" q_char0? q_char* "$"
q_char0::= "A"..."Z" | "a"..."z" | "_"
q_char::= "A"..."Z" | "a"..."z" | "_" | "0"..."9"
str_content::= <newline> | unicode | str_escapes
unicode::= <any printable unicode character not preceded by "\">
str_escapes::= <see below for details>

The inclusion of “high ASCII” character in q_char in practice reflects the ability to use some of the letters with diacritics like ò or ü in the dollar-quote delimiter.

Here’s a list of valid str_escapes:

Escape Sequence

Meaning

\[newline]

Backslash and all whitespace up to next non-whitespace character is ignored

\\

Backslash (\)

\'

Single quote (‘)

\"

Double quote (“)

\b

ASCII backspace (\x08)

\f

ASCII form feed (\x0C)

\n

ASCII newline (\x0A)

\r

ASCII carriage return (\x0D)

\t

ASCII tabulation (\x09)

\xhh

Character with hex value hh

\uhhhh

Character with 16-bit hex value hhhh

\Uhhhhhhhh

Character with 32-bit hex value hhhhhhhh

Here’s some examples of regular strings using escape sequences

Copy
db> 
... 
SELECT 'hello
world';
{'hello
world'}
Copy
db> 
SELECT "hello\nworld";
{'hello
world'}
Copy
db> 
... 
SELECT 'hello \
        world';
{'hello world'}
Copy
db> 
... 
... 
SELECT 'https://edgedb.com/\
        docs/edgeql/lexical\
        #constants';
{'https://edgedb.com/docs/edgeql/lexical#constants'}
Copy
db> 
SELECT 'hello \\ world';
{'hello \ world'}
Copy
db> 
SELECT 'hello \'world\'';
{"hello 'world'"}
Copy
db> 
SELECT 'hello \x77orld';
{'hello world'}
Copy
db> 
SELECT 'hello \u0077orld';
{'hello world'}

Raw strings don’t have any specially interpreted symbols; they contain all the symbols between the quotes exactly as typed.

Copy
db> 
SELECT r'hello \\ world';
{'hello \\ world'}
Copy
db> 
... 
SELECT r'hello \
world';
{'hello \
 world'}
Copy
db> 
... 
SELECT r'hello
world';
{'hello
 world'}

A special case of raw strings are dollar-quoted strings. They allow using either kind of quote symbols ' or " as part of the string content without the quotes terminating the string. In fact, because the dollar-quote delimiter sequences can have arbitrary alphanumeric additional fillers, it is always possible to surround any content with dollar-quotes in an unambiguous manner:

Copy
db> 
... 
SELECT $$hello
world$$;
{'hello
world'}
Copy
db> 
SELECT $$hello\nworld$$;
{'hello\nworld'}
Copy
db> 
SELECT $$"hello" 'world'$$;
{"\"hello\" 'world'"}
Copy
db> 
SELECT $a$hello$$world$$$a$;
{'hello$$world$$'}

More specifically, a delimiter:

  • Must start with an ASCII letter or underscore

  • Has following characters that can be digits 0-9, underscores or ASCII letters

Production rules for bytes literals:

bytes::= "b'" bytes_content* "'" | 'b"' bytes_content* '"'
bytes_content::= <newline> | ascii | bytes_escapes
ascii::= <any printable ascii character not preceded by "\">
bytes_escapes::= <see below for details>

Here’s a list of valid bytes_escapes:

Escape Sequence

Meaning

\\

Backslash (\)

\'

Single quote (‘)

\"

Double quote (“)

\b

ASCII backspace (\x08)

\f

ASCII form feed (\x0C)

\n

ASCII newline (\x0A)

\r

ASCII carriage return (\x0D)

\t

ASCII tabulation (\x09)

\xhh

Character with hex value hh

There are two kinds of integer constants: limited size (int64) and unlimited size (bigint). Unlimited size integer bigint literals are similar to a regular integer literals with an n suffix. The production rules are as follows:

bigint::= integer "n"
integer::= "0" | non_zero digit*
non_zero::= "1"..."9"
digit::= "0"..."9"

By default all integer literals are interpreted as int64, while an explicit cast can be used to convert them to int16 or int32:

Copy
db> 
SELECT 0;
{0}
Copy
db> 
SELECT 123;
{123}
Copy
db> 
SELECT <int16>456;
{456}
Copy
db> 
SELECT <int32>789;
{789}

Examples of bigint literals:

Copy
db> 
SELECT 123n;
{123n}
Copy
db> 
SELECT 12345678901234567890n;
{12345678901234567890n}

Just as for integers, there are two kinds of real number constants: limited precision (float64) and unlimited precision (decimal). The decimal constants have the same lexical structure as float64, but with an n suffix:

decimal::= float "n"
float::= float_wo_dec | float_w_dec
float_wo_dec::= integer_part exp
float_w_dec::= integer_part "." decimal_part? exp?
integer_part::= "0" | non_zero digit*
decimal_part::= digit+
exp::= "e" ("+" | "-")? digit+
non_zero::= "1"..."9"
digit::= "0"..."9"

By default all float literals are interpreted as float64, while an explicit cast can be used to convert them to float32:

Copy
db> 
SELECT 0.1;
{0.1}
Copy
db> 
SELECT 12.3;
{12.3}
Copy
db> 
SELECT 1e3;
{1000.0}
Copy
db> 
SELECT 1.2e-3;
{0.0012}
Copy
db> 
SELECT <float32>12.3;
{12.3}

Examples of decimal literals:

Copy
db> 
SELECT 12.3n;
{12.3n}
Copy
db> 
SELECT 12345678901234567890.12345678901234567890n;
{12345678901234567890.12345678901234567890n}
Copy
db> 
SELECT 12345678901234567890.12345678901234567890e-3n;
{12345678901234567.89012345678901234567890n}

EdgeQL uses ; as a statement separator. It is idempotent, so multiple repetitions of ; don’t have any additional effect.

Comments start with a # character that is not otherwise part of a string literal and end at the end of line. Semantically, a comment is equivalent to whitespace.

comment::= "#" <any other characters until the end of line>

EdgeQL operators listed in order of precedence from lowest to highest:

Light
Dark
System