Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
850 changes: 390 additions & 460 deletions academy/modules/ROOT/attachments/bookstore-data.tql

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion academy/modules/ROOT/attachments/bookstore-schema.tql
Original file line number Diff line number Diff line change
Expand Up @@ -294,4 +294,4 @@ fun transitive_places($place: place) -> { place }:
locating (located: $place, location: $middle);
let $parent in transitive_places($middle);
};
return { $parent };
return { $parent };
37 changes: 37 additions & 0 deletions core-concepts/modules/ROOT/pages/typeql/invalid-patterns.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -224,3 +224,40 @@ match
----

====

=== Nested optional blocks in write stages

A `try` block in a write stage is executed as a unit. When all variables referenced in the `try` block are bound, the write instructions are executed. While this could allow for arbitrary nesting of
`try` blocks where the bound variables are checked at every level, for simplicity's sake nesting `try` blocks is not permitted.

[,typeql]
----
#!test[write, fail_at=runtime]
match
$person isa person;
try {
$edu isa education, links (institute: $institute, attendee: $person);
try { $emp isa employment, links (employer: $company, employee: $person); }; # allowed in match
};
delete
try {
$edu;
try { $emp; }; # not allowed!
};
----

Use instead:

[,typeql]
----
#!test[write, rollback]
match
$person isa person;
try {
$edu isa education, links (institute: $institute, attendee: $person);
try { $emp isa employment, links (employer: $company, employee: $person); };
};
delete
try { $edu; };
try { $emp; };
----
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,42 @@ Finished. Total rows: 3
Optional variables *are* bound by the optional block and not internal to it.
====

==== Optional writes
A `try` block in a write stage such as `insert` allows for conditional writes in a larger pipeline.

[,typeql]
----
#!test[write, rollback]
match
$p isa person, has name $p-name;
try {
$e isa employment, links (employer: $c, employee: $p);
$c has name "Shut Shop";
};
delete try { $e; };
# pipeline continues with $p, $p-name, and $c still available
----

Optional patterns in write stages execute only if all the variables referenced in them are bound to a value.
That means that in this example, if either `$p` or `$c` is not bound, the insert stage does nothing.
If they are both bound, however, then a new employment relation is inserted.

[,typeql]
----
#!test[write, rollback]
match
try { $p isa person, has name $p-name; };
try { $c isa company, has name "Shut Shop"; };
insert
try { $_ isa employment, links (employer: $c, employee: $p); };
----

[NOTE]
====
Note that `try` blocks may not be nested (`try { try { ... }; };`) in write stages.
The inner `try` block can usually either be flattened into the outer block or placed at the top level in the write stage.
====

== Notes on variables
=== Variables in an answer
Notice that a variable that is "bound" in a conjunction is guaranteed to be bound
Expand Down
3 changes: 2 additions & 1 deletion test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Text.

[,typeql]
----
#!test[<TYPE>[, reset] [, rollback] [, fail_at=<FAILURE>] [, count=<NUM>] [, jump=<LABEL>] [, name=<LABEL>]]
#!test[<TYPE>[, reset] [, rollback] [, fail_at=<FAILURE>] [, documents] [, count=<NUM>] [, jump=<LABEL>] [, name=<LABEL>]]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#{{
<HIDDEN-QUERY>
#}}
Expand All @@ -46,6 +46,7 @@ where
* `reset` resets the database before running the test
* `rollback` rolls backs the transaction instead of committing it
* `fail_at=<FAILURE>` can be `runtime, commit`
* `documents` specifies that the query is a fetch query, and the results are documents
* `count=<NUM>` is an integer representing expected answer count
* `name=<LABEL>` names the test
* `jump=<LABEL>` is a label for the test (used for entrypoints and jumping around)
Expand Down
21 changes: 16 additions & 5 deletions test/parser/parser.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os
import re
from dataclasses import dataclass
from typing import List, Dict
from typing import List, Dict, Tuple
import logging
logger = logging.getLogger('main')
# To see debug log, set logging level to debug in main.py
Expand Down Expand Up @@ -114,9 +114,17 @@ def retrieve_antora_include(self, include_str: str):
if tags_match.startswith('tags='):
tags = tags_match.split('=')[1].split(';')

line_nrs: Tuple[str, str] = (None, None)
if tags_match.startswith('lines='):
line_nrs = tags_match.split('=')[1].split('..')

# Read tagged lines from Antora include
with open(file_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
if line_nrs[1]:
lines = lines[:int(line_nrs[1])]
if line_nrs[0]:
lines = lines[int(line_nrs[0]) - 1:]

tagged_lines: Dict[str, List[str]] = {}
current_tag = None
Expand Down Expand Up @@ -148,10 +156,13 @@ def retrieve_antora_include(self, include_str: str):
logging.debug(f"... finished scanning included file, resolved tags: {tagged_lines}")

output_lines = []
for tag in tags:
if tagged_lines.get(tag) is None:
self.error(f"Include is missing tag {tag}")
output_lines += tagged_lines[tag]
if tags:
for tag in tags:
if tagged_lines.get(tag) is None:
self.error(f"Include is missing tag {tag}")
output_lines += tagged_lines[tag]
else:
output_lines = lines

return output_lines

Expand Down
3 changes: 2 additions & 1 deletion test/runners/typeql_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ def run_failing_queries(self, queries: List[str], type: TransactionType) -> str:
return FailureMode.NoFailure

def run_transaction(self, queries: List[str], type: TransactionType, rollback=False) -> Union[int, None]:
queries = [q for qs in queries for q in qs.split('end;') if q.strip()]
Copy link
Member

@flyingsilverfin flyingsilverfin Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw if someone does end ; or with a newline in between it should still work - maybe use regex with any whitespace in between or something

with self.driver.transaction(self.db, type) as tx:
try:
for q in queries:
Expand Down Expand Up @@ -236,4 +237,4 @@ def try_tests(self, parsed_tests: List[ParsedTest], adoc_path: str, file_config:

current_test_index += 1

return None
return None
18 changes: 18 additions & 0 deletions tutorials/modules/ROOT/pages/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,24 @@ Set up a script for launching TypeDB from the TypeDB Cloud API
****
Run TypeDB from your GitHub Actions CI pipeline
****

.xref:{page-version}@tutorials::pipelines-read.adoc[]
[.clickable]
****
Read data from TypeDB
****

.xref:{page-version}@tutorials::pipelines-crud.adoc[]
[.clickable]
****
TODO
****

.xref:{page-version}@tutorials::pipelines-advanced.adoc[]
[.clickable]
****
TODO
****
--

Got other tutorials you'd like to see?
Expand Down
Loading