Annotations¶
Annotations appear at continuation points in a query pipeline, and permit language directives to operate as inline syntactic elements.
The Annotation Framework¶
Annotations are distinguished by matching PARENOTATES (~~ ~~) followed immediately (no space) by an <identifier>.
(~~<identifier> body ~~) // annotation with body
(~~<identifier>:instance body ~~) // annotation with instance name
The identifier after the (~~ is the ANNOTATION-TYPE.
How the body is parsed within an annotation is a function of the specific ANNOTATION-TYPE. Each recognized type has its own grammar rule:
(~~error: no body – just an optional URI which matches with well-known errors(~~danger: a URI naming the acknowledged danger
(~~docs shares the delimiters but is not an annotation. It is the dedicated definition-document form, and its body is raw text.
A colon form identifier:instance names a specific annotation instance and is also a function of the particular annotation type in question.
Only the annotation types listed above are recognized by the grammar. Unknown annotation names produce a parse error.
Placement¶
Annotations may appear at any continuation point – before a pipe, before a comma, or at the end of an expression:
invoice(*), total > 20
(~~config://generation/rule/inlining/view ~~)
|> (invoice_date, billing_city)
Annotations are usually transparent to SQL generation and so the pipeline above produces identical SQL to:
Multiple annotations at the same point are permitted. They appear as siblings in the CST and are processed in order.
Annotation Types¶
error– expects compilation to fail, matches the error URI (see Error Assertions)danger– acknowledges a named safety gate for the current query (see Danger Gates) (see Options)
The dedicated docs form attaches documentation to a definition; see Docs.
Error Assertions¶
Error assertions verify that a query fails compilation or resolution with a specific error. They use a separate annotation with the reserved name error:
The URI identifies the expected error category using a hierarchical path. The pipeline attempts to compile the query; if compilation fails and the actual error matches the URI, the assertion passes: the statement returns no rows. A statement that compiles anyway is refused (runtime/expectation).
// should fail: table does not exist
nonexistent_table(*) (~~error://semantic/resolution/table ~~)
// should fail: column not in scope
customer(*) |> (no_such_column) (~~error://semantic/resolution/column ~~)
// should fail: any semantic error (prefix match)
invoice(*), total in (1,2,3) (~~error://semantic ~~)
A bare error annotation with no URI matches any error:
Errors are rarely needed for end users.
URI Prefix Matching¶
The URI is matched as a prefix against the actual error’s canonical URI. error://semantic/resolution matches semantic/resolution/table, semantic/resolution/column, and any future semantic/resolution/* error. error://semantic/arity matches only semantic/arity and its sub-paths.
Error URI Categories¶
Each DelightQLError variant maps to a canonical URI path. The URI is a stable identifier for the error category, reusable in documentation, tooling, and diagnostics.
| URI | Meaning |
|---|---|
authored |
A termination the program itself demanded. |
client |
An incident of the interactive client itself. |
configuration |
A host’s boot settings are invalid. |
dml |
A data-modification query violated DML shape rules. |
imprint |
A blueprint or manifest lifecycle refusal. |
internal |
A defect in DelightQL itself. |
namespace |
A namespace-creation policy refusal. |
operational |
This session refuses to run a valid query. |
parse |
The source text is structurally invalid. |
runtime |
An execution-time failure. |
semantic |
The query is well-formed but semantically invalid. |
target |
The foreign engine rejected or failed the query. |
Top-level error URI families (dql explain delightql-error://<family> lists each family’s members)
Coexistence with Data Assertions¶
Error assertions and data assertions can appear in the same file, documenting both correct and incorrect forms:
// correct: an ordinary assertion effect checks the established relation
invoice(*), total > 0
!> assert!(exists(*), "a positive-total row exists")(*)
// incorrect: commas produce a multi-column single-row relation
invoice(*), total in (1,2,3) (~~error://semantic/membership ~~)
Scope¶
Error assertions are primarily a language development tool. They assert contracts about the compiler’s behavior. End users writing queries against a database should have no use for expected failures. I think
Danger Gates¶
Certain behaviors are safe in most contexts but dangerous in others. Rather than forbid them outright, delightql gates them behind danger URIs: named safety boundaries that are closed by default and opened explicitly per-query.
Syntax¶
A danger gate is a danger:// URI inside annotation delimiters:
The annotation attaches at a continuation point (after a relation). The URI identifies the specific danger. The annotation takes the URI alone: writing it opens that gate for this query, and nothing else opens one: a state word such as ON has no place in it.
A state word inside the annotation is an error:
// INVALID: the annotation takes no state word
genre_2024(*) as x (~~danger://semantics/min_multiplicity ON~~)
|;| genre_2025(*) as y,
x.* = y.*
Scoping¶
A danger gate opens for one query and auto-closes at query end. It does not leak into subsequent queries:
// gate is open for this query: 23 rows, Jazz once
genre_2024(*) as x (~~danger://semantics/min_multiplicity~~)
|;| genre_2025(*) as y,
x.* = y.*
// gate is closed again: 47 rows, every matching row of both arms
genre_2024(*) as x |;| genre_2025(*) as y,
x.* = y.*
A min_multiplicity gate must be spent: written on a statement where no correlated union uses it, it refuses (semantic/setop/min_multiplicity/unspent), and over a correlation that compares only some of the columns the arms share it refuses too (semantic/setop/min_multiplicity/partial) — correlate the whole row (x.* = y.*).
An unaliased self-join is not a danger to acknowledge: two live scopes sharing a name always refuse (semantic/scope/duplicate). Alias one side.
Session Baseline¶
The program starts with every danger OFF, and only an annotation in the source text opens one, for its query.
Per-query annotations override the session baseline. At query end, the danger reverts to the enclosing scope:
Danger URI Reference¶
The known dangers are:
| URI | What it gates |
|---|---|
delightql-danger://semantics/min_multiplicity |
A correlated union pairs duplicate copies by minimum multiplicity (SQL’s INTERSECT ALL) instead of keeping every matching row of both arms; inline only |
Docs¶
Definitions may carry structured documentation between the neck and the body. The docs block uses the annotation delimiters with the docs identifier:
Syntax¶
long_tracks(*) :-
(~~docs
Tracks longer than ten minutes.
Returns:
columns: inherited from track
cardinality: variable
~~)
track(*), milliseconds > 600000
The docs block is a (~~docs ... ~~) annotation. The body is raw text – no DQL parsing is applied. Line breaks, indentation, and blank lines are preserved as written.
The block must appear immediately after the neck, before the first expression of the body. Only one docs block per definition is permitted.
Applicability¶
The docs block is valid on any rule-form definition:
Views:
Functions:
tax_amount:(price, rate) :-
(~~docs
Computes tax as price times rate, rounded to two decimal places.
Returns:
type: numeric
~~)
round:(price * rate, 2)
Higher-order rules:
same_schema(T(*), V(*))(*) :-
(~~docs
Compares T's and V's schema for
equality. Equality is reached if the
column names match exactly and are
in the same ordinal position.
Returns:
column: pass
column type: boolean
cardinality: 1
~~)
first_md(*) : T(^)
second_md(*) : V(^)
together(*) :
second_md(*),
first_md(*.(column_name, ordinal))
together(~> count:() as a),
first_md(~> count:() as b),
second_md(~> count:() as c)
|> ( (a = b) and (b = c) as pass)
Sigma predicates:
is_recent(invoice_date, threshold) :-
(~~docs
Filters to rows where invoice_date is
within threshold days of today.
~~)
invoice_date > date:("now", ("-" ++ threshold) ++ " days")
The docs block is not valid on facts (which have no neck) or on shadow-neck definitions (which are query-scoped and ephemeral).
Storage¶
When a definition is loaded via consult!, the docs text is extracted at parse time and stored alongside the entity in the system catalog.
The docs are queryable through the system metadata: