typeql

TypeQL Language Reference for TypeDB 3.8+

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "typeql" with this command: npx skills add caliluke/skills/caliluke-skills-typeql

TypeQL Language Reference for TypeDB 3.8+

This skill provides comprehensive guidance for writing TypeQL queries against TypeDB databases.

Note (3.8+): Trailing commas are now allowed in all comma-separated contexts (variable lists, statements, reductions) for easier query composition.

Note (3.8+): Unicode identifiers are now supported. Type names, attribute names, and variable names can use any Unicode XID_START character followed by XID_CONTINUE characters (e.g., 名前 , prénom , город ).

Quick Reference

Transaction Types

Type Use For Commit

schema

Define/undefine types, functions Yes

write

Insert, update, delete data Yes

read

Match, fetch, select queries No (use close )

Query Structure

[with ...] -- Inline function preamble [define|undefine|redefine] -- Schema operations [match] -- Pattern matching [insert|put|update|delete] -- Data mutations [select|sort|offset|limit] -- Stream operators [require|distinct] -- Filtering operators [reduce ... groupby] -- Aggregations [fetch] -- JSON output ; -- EVERY query MUST end with a semicolon

  1. Schema Definition

Schema definitions create types and constraints. Run in schema transactions.

Root Types (TypeDB 3.x)

In TypeDB 3.x, thing is no longer a valid root. The three roots are:

  • entity

  • relation

  • attribute

Attribute Types

define

Basic value types

attribute name, value string; attribute age, value integer; attribute salary, value double; attribute is_active, value boolean; attribute created_at, value datetime;

With constraints

attribute email, value string @regex("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"); attribute priority, value integer @range(1..5); attribute status, value string @values("pending", "active", "completed");

Entity Types

define

Simple entity

entity person;

Entity with attributes

entity person, owns name, owns email @key, # Unique identifier owns age @card(0..1); # Optional (0 or 1)

Abstract entity (cannot be instantiated)

entity artifact @abstract, owns name, owns created_at;

Entity inheritance

entity user sub artifact, owns email @key;

Relation Types

define

Basic relation with roles

relation employment, relates employer, relates employee;

Entities playing roles

entity company, plays employment:employer;

entity person, plays employment:employee;

Relation with cardinality on roles

relation manages, relates manager @card(1), # Exactly one manager relates report @card(1..); # One or more reports

Relation owning attributes

relation employment, relates employer, relates employee, owns start_date, owns end_date @card(0..1);

Self-referential relation

relation friendship, relates friend; # Same role played twice

entity person, plays friendship:friend;

Role Overriding (Inheritance)

define

Base relation

relation relationship, relates partner;

Specialized relation with role override

relation marriage sub relationship, relates spouse as partner; # 'spouse' overrides 'partner'

entity person, plays marriage:spouse;

Type Aliases

define

Create an alias for an existing type

attribute username alias name; # username is an alias for name

Annotations Reference

Annotation Usage Description

@key

owns name @key

Unique identifier for type

@unique

owns email @unique

Unique but not primary key

@card(n)

owns age @card(0..1)

Cardinality constraint

@regex(...)

value string @regex(...)

Pattern validation

@range(...)

value integer @range(1..100)

Value range

@values(...)

value string @values("a","b")

Enum-like constraint

@abstract

entity @abstract

Cannot be instantiated

@cascade

owns ref @cascade

Cascade delete when owner deleted

@independent

owns tag @independent

Attribute exists independently of owner

@distinct

owns item @distinct

Each value can only be owned once

@subkey(...)

owns code @subkey(region)

Composite key with another attribute

Note: @key and @unique can only be put on owns , not directly on an attribute value definition.

  1. Schema Modification

Undefine (Remove Schema Elements)

undefine

Remove entire type

person;

Remove capability from type

owns email from person; plays employment:employee from person; relates employee from employment;

Remove annotation from type

@abstract from artifact;

Remove annotation from capability

@key from person owns email;

Remove role specialization

as spouse from marriage relates spouse;

Remove function

fun get_active_users;

Remove struct

struct address;

Redefine (Modify Existing Schema)

redefine

Change annotation on type

entity person @abstract;

Change annotation on capability

person owns age @card(1..1); # Make required

Redefine function (replaces entirely)

fun count_users() -> integer: match $u isa user, has status "active"; return count;

  1. Data Operations (CRUD)

Insert

Insert entity with attributes

insert $p isa person, has name "Alice", has email "alice@example.com";

Insert using anonymous variable (when reference not needed)

insert $_ isa person, has name "Bob";

Insert relation (match entities first)

match $p isa person, has email "alice@example.com"; $c isa company, has name "Acme Inc"; insert $_ isa employment (employer: $c, employee: $p), has start_date 2024-01-15;

Put (Upsert)

Put creates if not exists, matches if exists

Useful for idempotent operations

match $c isa company, has name "Acme Inc"; put $p isa person, has email "alice@example.com", has name "Alice"; insert employment (employer: $c, employee: $p);

Put finds existing person by email (key) or creates new one

Then insert creates the relation

Update

Update single-cardinality attribute (implicit replacement)

match $p isa person, has email "alice@example.com"; update $p has email "alice.smith@example.com";

Update multi-cardinality: delete old, insert new

match $p isa person, has name "Alice"; $p has nickname $old; delete $p has $old; insert $p has nickname "Ally";

Delete

Delete entity

match $p isa person, has email "alice@example.com"; delete $p;

Delete attribute from entity

match $p isa person, has email "alice@example.com", has nickname $n; delete $p has $n;

Alternative: delete attribute using 'of'

match $p isa person, has email "alice@example.com", has nickname $n; delete has $n of $p;

Delete relation

match $p isa person, has email "alice@example.com"; $c isa company, has name "Acme Inc"; $e isa employment (employer: $c, employee: $p) ; delete $e;

Delete role player from relation (keeps relation)

match $rel isa membership (member: $old_member, group: $g) ; $old_member has email "alice@example.com"; delete links ($old_member) of $rel;

Optional delete (try) - no error if not found

match $p isa person, has email "alice@example.com"; delete try { $p has nickname $n; };

  1. Querying Data

Match + Fetch (Primary Query Pattern)

Fetch specific attributes

match $p isa person, has name $n, has email $e; fetch { "name": $n, "email": $e }

Fetch all attributes of entity

match $p isa person, has email "alice@example.com"; fetch { "person": { $p.* } }

Fetch with attribute projection

match $p isa person; fetch { "name": $p.name, "email": $p.email }

Fetch multi-valued attributes as list

match $p isa person, has email "alice@example.com"; fetch { "name": $p.name, "all_nicknames": [ $p.nickname ] }

Nested fetch for related data

match $c isa company, has name "Acme Inc"; fetch { "company": $c.name, "employees": [ match employment (employer: $c, employee: $p); fetch { "name": $p.name } ] }

Fetch with inline function

match $c isa company; fetch { "company": $c.name, "employee_count": ( match (employer: $c, employee: $e) isa employment; reduce $count = count; ) }

Filtering Patterns

Filter by type

match $x isa person;

Filter by exact type (not subtypes)

match $x isa! person;

Filter by attribute value

match $p isa person, has age > 30;

Filter by relation

match $c isa company, has name "Acme Inc"; employment (employer: $c, employee: $p);

Filter by relation with relation variable

match $c isa company, has name "Acme Inc"; $rel isa employment, links (employer: $c, employee: $p);

Comparison operators: ==, !=, <, <=, >, >=, like, contains

match $p isa person, has name $n; $n like "^A.*"; # Regex match

match $p isa person, has bio $b; $b contains "engineer"; # Substring match

Identity Check (is)

Check if two variables refer to the same concept

match $p1 isa person; $p2 isa person; friendship (friend: $p1, friend: $p2); not { $p1 is $p2; }; # Exclude self-friendship fetch { "person": $p1.name }

Conjunction (Grouping with AND)

Explicit grouping with curly braces

match $p isa person; { $p has age > 18; $p has status "active"; }; # Both conditions must be true

Disjunction (OR)

match $p isa person, has email $e; { $p has name "Alice"; } or { $p has name "Bob"; }; fetch { "email": $e }

Negation (NOT)

Find persons not employed by Acme

match $p isa person; not { $c isa company, has name "Acme Inc"; employment (employer: $c, employee: $p); }; fetch { "unemployed": $p.name }

Optional Patterns (TRY)

Match with optional pattern

match $p isa person, has name $n; try { $p has email $e; }; fetch { "name": $n, "email": $e # May be null if no email }

  1. Stream Operators

Order must be: sort , offset , limit

Sort results

match $p isa person, has name $n; sort $n asc;

Sort descending

match $p isa person, has age $a; sort $a desc;

Pagination

match $p isa person, has name $n; sort $n asc; offset 10; limit 10;

Select specific variables (like SQL SELECT)

match $p isa person, has name $n, has email $e; select $n, $e;

Distinct results

match $p isa person, has name $n; distinct;

Require variables to be bound (filter nulls from try)

match $p isa person, has name $n; try { $p has email $e; }; require $e; # Only return rows where email exists

  1. Aggregations

Count

match $p isa person; reduce $count = count;

Count specific variable

match $p isa person, has email $e; reduce $count = count($e);

Multiple aggregations

match $p isa person, has salary $s; reduce $max = max($s), $min = min($s), $avg = mean($s);

Group by

match $c isa company; employment (employer: $c, employee: $e); reduce $count = count groupby $c;

Multiple groupby variables

match $c isa company, has industry $ind; employment (employer: $c, employee: $e); $e has department $dept; reduce $count = count groupby $ind, $dept;

Available Reducers

Reducer Usage Description

count

count or count($var)

Count results

sum($var)

sum($salary)

Sum numeric values

min($var)

min($age)

Minimum value

max($var)

max($age)

Maximum value

mean($var)

mean($score)

Average

median($var)

median($score)

Median

std($var)

std($score)

Standard deviation

list($var)

list($name)

Collect into list

  1. Expressions and Computed Values

Arithmetic Operators

match $p isa product, has price $price, has quantity $qty; let $subtotal = $price * $qty; # Multiplication let $with_tax = $subtotal * 1.1; # More multiplication let $discount = $subtotal / 10; # Division let $final = $subtotal - $discount; # Subtraction let $total = $final + 5.00; # Addition (shipping) let $squared = $price ^ 2; # Power/exponent let $remainder = $qty % 3; # Modulo fetch { "product": $p.name, "total": $total }

Assignment and Literals

match $p isa product, has price $price; let $vat_rate = 0.2; # Assign literal let $price_with_vat = $price * (1 + $vat_rate); fetch { "price_with_vat": $price_with_vat }

Built-in Functions

match $p isa product, has price $price, has name $name;

NOTE: ceil, floor, round only work on double/decimal, not integers

let $rounded = round($price); # Round to nearest integer let $ceiling = ceil($price); # Round up let $floored = floor($price); # Round down let $absolute = abs($price - 100); # Absolute value let $name_len = len($name); # String length (note: len, not length) let $higher = max($price, 10.0); # Maximum of two values let $lower = min($price, 100.0); # Minimum of two values fetch { "stats": { $rounded, $ceiling, $floored } }

String concatenation

match $u isa user, has first_name $fn, has last_name $ln; let $full = concat($fn, " ", $ln); fetch { "full_name": $full }

Get IID of a concept (3.8+)

match $p isa person, has email "alice@example.com"; fetch { "iid": iid($p) # Get internal identifier }

Get type label (3.8+) - NOTE: label() works on TYPE variables, not instances!

Must bind the exact type first using isa! and a type variable

match $p isa! $t, has email "alice@example.com"; $t sub person; # Bind $t to exact type of $p fetch { "iid": iid($p), "type": label($t) # label() on TYPE variable $t }

  1. List Operations

List Literals

match $p isa person, has name $name; let $tags = ["active", "verified", "premium"]; # List literal fetch { "name": $name, "tags": $tags }

List Indexing

match $p isa person, has score $scores; # Multi-valued attribute let $first = $scores[0]; # First element (0-indexed) let $second = $scores[1]; # Second element fetch { "first_score": $first }

List Slicing

match $p isa person, has score $scores; let $top_three = $scores[0..3]; # Elements 0, 1, 2 let $rest = $scores[3..10]; # Elements 3 through 9 fetch { "top_scores": $top_three }

  1. Structs

Struct Definition

define

Define a struct type

struct address: street value string, city value string, zip value string?, # Optional field (nullable) country value string;

WARNING: structs are not yet implemented as of TypeDB 3.8.0, so should not be used.

Using Structs

Insert with struct value

insert $p isa person, has name "Alice", has home_address { street: "123 Main St", city: "Boston", zip: "02101", country: "USA" };

Match struct fields

match $p isa person, has home_address $addr; $addr isa address { city: "Boston" }; fetch { "person": $p.name }

Struct Destructuring

Destructure struct in let

match $p isa person, has home_address $addr; let { city: $city, zip: $zip } = $addr; fetch { "person": $p.name, "city": $city }

  1. Functions

Define reusable query logic in schema.

Function Definition

define

Return stream of values

fun get_active_users() -> { string }: match $u isa user, has status == "active", has email $e; return { $e };

Return single value with aggregation

fun count_users() -> integer: match $u isa user; return count;

Function with parameters

fun get_user_projects($user_email: string) -> { string }: match $u isa user, has email == $user_email; membership (member: $u, project: $p); $p has name $name; return { $name };

Return multiple values in stream

fun get_user_details($email: string) -> { string, string }: match $u isa user, has email == $email, has name $n, has role $r; return { $n, $r };

Return first match only

fun get_oldest_user() -> string: match $u isa user, has name $n, has age $a; sort $a desc; return first $n;

Return last match only

fun get_youngest_user() -> string: match $u isa user, has name $n, has age $a; sort $a desc; return last $n;

Return boolean (existence check)

fun user_exists($email: string) -> boolean: match $u isa user, has email == $email; return check; # Returns true if match found, false otherwise

Using Functions

Call function in match with 'let ... in'

match let $emails in get_active_users(); fetch { "active_emails": $emails }

Call function with parameter

match let $projects in get_user_projects("alice@example.com"); fetch { "projects": $projects }

Call function returning single value

match let $count in count_users(); fetch { "total_users": $count }

Use function in expression

match $u isa user; let $exists in user_exists($u.email); fetch { "user": $u.name, "verified": $exists }

Inline Functions with WITH

Define function inline for single query

with fun active_in_dept($dept: string) -> { user }: match $u isa user, has department == $dept, has status "active"; return { $u };

match let $user in active_in_dept("Engineering"); fetch { "engineer": $user.name }

  1. IID (Internal Identifier) Operations

Match by IID (for direct lookups)

match $entity iid 0x1f0005000000000000012f; fetch { "data": { $entity.* } }

Get IID using iid() function (3.8+)

match $p isa person, has email "alice@example.com"; fetch { "person": $p.name, "iid": iid($p) }

Get IID and exact type label together (3.8+)

NOTE: label() requires a TYPE variable, use isa! to bind it

match $p isa! $t, has email "alice@example.com"; $t sub person; fetch { "iid": iid($p), "type": label($t) }

  1. Rules (Inference)

TypeDB 3.0 and on no longer uses rules, and uses only functions instead.

  1. Common Patterns

Note: each clause is not itself terminated with a trailing semicolon, only each statement within a clause is.

Upsert (Insert if not exists)

Use 'put' for upsert behavior

match $c isa company, has name "Acme Inc"; put $p isa person, has email "new@example.com", has name "New Person"; insert employment (employer: $c, employee: $p);

Polymorphic Queries

Query abstract supertype to get all subtypes

match $artifact isa artifact, has name $n; # Gets all subtypes fetch { "name": $n }

Query exact type only (not subtypes)

match $artifact isa! artifact, has name $n; # Only direct instances fetch { "name": $n }

Graph Traversal

Find all connected nodes (1-hop)

match $center isa entity, has id == "target-id"; $rel links ($center), links ($neighbor); not { $neighbor is $center; }; fetch { "center": $center.id, "neighbor": $neighbor.id }

Existence Check

Check if pattern exists

match $u isa user, has email "alice@example.com"; not { (member: $u) isa team_membership; };

Returns results only if user exists but has no team

  1. Critical Pitfalls

TypeDB 3 relation syntax

Relations in TypeDB 3.x use the follow syntax only:

Anonyous relation syntax: always use this if you don't need a relation instance variable

<rel-type> (<role-type>: <player var>, <role-type-2>, <player var 2>, ...);

Variabilized relation syntax (with a variable for the relation instance): only use this if you need a variable for the relation instance

$rel-var isa <rel-type>, links (<role-type>: <player var>, <role-type-2>, <player var 2>, ...);

Reserved keywords

These are reserved identifiers. Never use them as user-defined identifier like type labels or variables, in any capitalization. If required, append _ instead:

with, match, fetch, update, define, undefine, redefine, insert, put, delete, end , entity, relation, attribute, role , asc, desc, struct, fun, return, alias, sub, owns, as, plays, relates, iid, isa, links, has, is, or, not, try, in, true, false, of, from, first, last

Match Clauses are Simultaneous (AND)

All statements in match must be satisfied together. Not sequential.

WRONG assumption: "get all books, then find promotions"

ACTUAL: Only returns books that ARE in promotions

match $b isa book; promotion (book: $b, promo: $p);

Unconstrained Variables = Cross Join

PROBLEM: Returns every book x every promotion (Cartesian product)

match $b isa book; $p isa promotion; # Not linked to $b!

FIX: Link variables through relations

match $b isa book; book_promotion (book: $b, promotion: $p);

Symmetric Relations Return Duplicates

PROBLEM: Returns (A,B) and (B,A)

match friendship (friend: $p1, friend: $p2);

FIX: Add asymmetric constraint

match $p1 has email $e1; $p2 has email $e2; friendship (friend: $p1, friend: $p2); $e1 < $e2; # Each pair only once

Cardinality Validated at Commit

Insert violations don't fail immediately - only on commit.

Sorting Loads All Results

sort requires loading all matching data into memory before pagination.

Variable Scoping in OR/NOT

Variables inside or blocks are not guaranteed bound outside:

INVALID: $company might not be bound

match $p isa person; { employment (employee: $p, employer: $company); } or { $p has status "freelancer"; }; not { $company has name "BadCorp"; }; # $company may be unbound!

VALID: Bind variable in parent scope first

match $p isa person; employment (employee: $p, employer: $company); not { $company has name "BadCorp"; };

Variable Reuse in OR Branches

INVALID: $x means different things in each branch

match { editing (person: $p, document: $x); } or { messaging (person: $p, message: $x); };

VALID: Use different variable names

match { editing (person: $p, document: $doc); } or { messaging (person: $p, message: $msg) ; };

  1. CLI Notes

Command Execution

Server commands: NO semicolon

typedb console --command "database list"

TypeQL in transactions: WITH semicolons

typedb console --command "transaction mydb schema" --command "define entity person;"

Script Files (.tqls)

Console commands without semicolons

transaction mydb schema define entity person; commit

Transaction Lifecycle

  • schema , write transactions: use commit

  • read transactions: use close

  1. Value Types Reference

TypeQL Type Description Example Literal

string

UTF-8 text "hello"

boolean

true/false true , false

integer

64-bit signed int 42 , -17

double

Double-precision float 3.14159

decimal

Precise decimal 99.99dec

date

Date only 2024-01-15

datetime

Date and time 2024-01-15T10:30:00

datetime-tz

With timezone 2024-01-15T10:30:00 America/New_York

duration

Time duration P1Y2M3D , PT4H30M

Note: 0dec is not valid syntax in some versions of TypeDB. Always use 0.0dec .

Duration Format (ISO 8601)

P[n]Y[n]M[n]DT[n]H[n]M[n]S P1Y2M3D = 1 year, 2 months, 3 days PT4H30M = 4 hours, 30 minutes P1W = 1 week PT1H30M45S = 1 hour, 30 minutes, 45 seconds

  1. Debugging Queries

Test Match Before Write

Before running delete:

match $u isa user, has status "inactive"; fetch { "will_delete": $u.email }

Then execute:

match $u isa user, has status "inactive"; delete $u;

Check Schema

In read transaction - list entity types

match $type sub entity; fetch { "entity_types": $type }

List relation types

match $rel sub relation; fetch { "relation_types": $rel }

List attribute types

match $attr sub attribute; fetch { "attribute_types": $attr }

Verify Cardinality

Count instances per type

match $type sub entity; $instance isa! $type; reduce $count = count groupby $type;

Debugging match clauses

When a match returns results unexpectedly (say 0, though maybe more than expected), comment out some connected constraints with # , then count:

match $p isa person, has name $n; $p has status "Active"; # suspect constraint

employment (employer: $c, employee: $p); # commented out

$c has name "Acme"; # commented out

reduce $count = count;

If count > 0, problem is in the commented-out half. If still 0, problem is in the active half. Repeat to isolate the exact constraint.

Debugging sulti-stage Pipelines

When a pipeline (e.g., match → reduce → match → update ) writes nothing, run successive prefixes to find where data stops flowing:

Prefix 1: just the match — does it find data?

match ...; reduce $count = count;

Prefix 2: match + reduce — are aggregated values correct?

match ...; reduce $total = sum($amount) groupby $customer;

Prefix 3: match + reduce + filter — do any pass the threshold?

match ...; reduce $total = sum($amount) groupby $customer; match $total > 20000.0; reduce $count = count;

  1. Complete Operator Reference

Comparison Operators

Operator Description Example

==

Equal $age == 30

!=

Not equal $status != "done"

<

Less than $age < 18

<=

Less than or equal $age <= 65

Greater than $salary > 50000

=

Greater than or equal $score >= 90

like

Regex match $name like "^A.*"

contains

Substring match $bio contains "dev"

Math Operators

Operator Description Example

Addition $a + $b

Subtraction $a - $b

Multiplication $a * $b

/

Division $a / $b

%

Modulo $a % $b

^

Power $a ^ 2

Expression Functions

Function Description Example

abs($x)

Absolute value abs($n - 10)

ceil($x)

Round up (double/decimal only) ceil($price)

floor($x)

Round down (double/decimal only) floor($price)

round($x)

Round nearest (double/decimal) round($price)

len($s)

String length len($name)

max($a,$b)

Maximum of two values max($x, 0)

min($a,$b)

Minimum of two values min($x, 100)

iid($var)

Get internal identifier (3.8+) iid($p)

label($t)

Get type label (TYPE var only!) label($t)

concat(…)

Concatenate strings concat($a," ",$b)

Pattern Keywords

Keyword Description

isa

Type check (includes subtypes)

isa!

Exact type check (excludes subtypes)

has

Attribute ownership

links

Role player in relation

is

Identity comparison

or

Disjunction

not

Negation

try

Optional pattern

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

type-bridge

No summary provided by upstream source.

Repository SourceNeeds Review
General

ai-testing

No summary provided by upstream source.

Repository SourceNeeds Review
General

Find Skills for ClawHub

Search for and discover OpenClaw skills from ClawHub (the official skill registry). Activate when user asks about finding skills, installing skills, or wants...

Registry SourceRecently Updated
1275
Profile unavailable