typeql

📁 caliluke/skills 📅 14 days ago
4
总安装量
2
周安装量
#50306
全站排名
安装命令
npx skills add https://github.com/caliluke/skills --skill typeql

Agent 安装分布

opencode 2
github-copilot 2
codex 2
gemini-cli 2
crush 1

Skill 文档

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

2. 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;

3. 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
  (employer: $c, employee: $p) isa employment,
    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
  (employer: $c, employee: $p) isa employment;

# 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 (employer: $c, employee: $p) isa employment;
delete
  $e;

# Delete role player from relation (keeps relation)
match
  $rel (member: $old_member, group: $g) isa membership;
  $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;
  };

4. 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
      (employer: $c, employee: $p) isa employment;
    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";
  (employer: $c, employee: $p) isa employment;

# 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;
  (friend: $p1, friend: $p2) isa friendship;
  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";
    (employer: $c, employee: $p) isa employment;
  };
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
}

5. 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

6. 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;
  (employer: $c, employee: $e) isa employment;
reduce
  $count = count groupby $c;

# Multiple groupby variables
match
  $c isa company, has industry $ind;
  (employer: $c, employee: $e) isa employment;
  $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

7. 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
}

8. 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 }

9. 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;

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
}

10. 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;
    (member: $u, project: $p) isa membership;
    $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 }

11. 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)
}

12. Rules (Inference)

define
  # Transitive relation
  rule transitive_manages:
    when {
      (manager: $a, report: $b) isa manages;
      (manager: $b, report: $c) isa manages;
    } then {
      (manager: $a, report: $c) isa manages;
    };

  # Derived relation
  rule colleague_rule:
    when {
      (employer: $c, employee: $p1) isa employment;
      (employer: $c, employee: $p2) isa employment;
      not { $p1 is $p2; };
    } then {
      (colleague: $p1, colleague: $p2) isa colleague;
    };

  # Rule with disjunction
  rule privileged_access:
    when {
      $u isa user;
      { $u has role "admin"; } or { $u has role "superuser"; };
    } then {
      (accessor: $u) isa privileged;
    };

13. Common Patterns

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
  (employer: $c, employee: $p) isa employment;

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

14. Critical Pitfalls

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;
  (book: $b, promo: $p) isa promotion;

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: $b, promotion: $p) isa book_promotion;

Symmetric Relations Return Duplicates

# PROBLEM: Returns (A,B) and (B,A)
match
  (friend: $p1, friend: $p2) isa friendship;

# FIX: Add asymmetric constraint
match
  $p1 has email $e1;
  $p2 has email $e2;
  (friend: $p1, friend: $p2) isa friendship;
  $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;
  {
    (employee: $p, employer: $company) isa employment;
  } 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;
  (employee: $p, employer: $company) isa employment;
  not { $company has name "BadCorp"; };

Variable Reuse in OR Branches

# INVALID: $x means different things in each branch
match {
    (person: $p, document: $x) isa editing;
} or {
    (person: $p, message: $x) isa messaging;
};

# VALID: Use different variable names
match {
    (person: $p, document: $doc) isa editing;
} or {
    (person: $p, message: $msg) isa messaging;
};

15. 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

16. 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

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

17. 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;

18. 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