Skip to main content
An RQL recipe is a single .rql file that pins exactly one evidence question. It imports the relations it needs, declares a typed join tree, and selects what to group by and what to measure. It compiles to portable SQL, and because it runs against a specific ontology it is reproducible down to the ontology digest. If the earlier pages are the vocabulary, RQL is the sentence.

Anatomy of a recipe

Here is a complete recipe. Every field is covered below.

rql_version, name, version, domain

The envelope. rql_version pins the recipe grammar; name and version identify the recipe; domain scopes it to a corpus (e.g. scientific_corpus). The recipe answers exactly one question, so these stay stable as the corpus underneath is re-released.

imports and from

imports lists the relations the recipe uses; from names the one it starts from. Everything else is reached through the join tree.

joins

The typed join tree. Each edge names a left_source, a right_source, and a type (inner or left). Identities are checked against the shared schema before any row is read. An optional on list adds typed refinements to the ontology relationship. It cannot replace that relationship’s identity key. Lists are implicit all groups; nested all, any, and not nodes make precedence explicit. Safe optional joins may set activation: when_referenced. The compiler prunes one only when it is a left join, the ontology declares it one_to_one or many_to_one, and no selected field, aggregate predicate, caller filter, or ordering term needs its right source. Inner and fanout joins are never conditionally pruned.

dimensions

The columns to group by — the axes of the result. Each names a source relation and a column, with an optional alias when two sources expose the same column name.

measurements

The aggregations — the numbers the recipe computes over each group. Each names a function, a source, a column, and an alias.
Two closed measurement forms cover common cases without admitting arbitrary SQL:
A recipe with dimensions but no measurements is a projection (row-per-match); add measurements to roll those rows up per group. Every aggregate supports a typed where list. Counts return zero for no matching input; the other aggregates retain SQL null-on-empty semantics. An optional input_grain lists the measurement source’s complete ontology primary key. When present, compilation rejects a join plan that could multiply that key, except for a count_distinct over the same key. having applies the same closed predicate shape to measurement names after aggregation. It cannot name source columns or contain an aggregate expression.

parameters and filters

parameters declares typed inputs with defaults, so one recipe covers a family of thresholds without editing the file. allowed_values, minimum, and maximum close the caller contract further. When a filter targets an ontology column, RQL validates its type and unit and inherits declared ontology units or allowed values when the parameter omits them. Scalar types include date (YYYY-MM-DD) and timestamp (RFC 3339 with an explicit timezone), with corresponding list types. Lists can declare minimum_items, maximum_items, and unique; timestamps can require any timezone or UTC specifically. filters restrict rows using a source, column, operator (e.g. gte), and a literal, literal list, parameter, or compared ontology column. The outer list is an implicit all; use nested all, any, and not for boolean composition.
parameter_guards rejects invalid combinations before SQL compilation. A requires guard has one trigger and one or more dependencies; mutually_exclusive allows at most one named parameter; exactly_one_of requires one. The additional closed guards are requires_when (a dependency triggered by a typed comparison), at_least_one_of, and compare (two parameters compared by eq, ne, gt, gte, lt, or lte).

Governed caller filters and relationship existence

filterables is the only way a caller can add a predicate. The module owns each name’s source, column, and allowed operators; a compilation request supplies a boolean tree of filterable names and typed values. Callers cannot introduce an identifier or expression. exists and not_exists test an imported relationship without adding its rows to the result grain. The compiler obtains every correlation key from the ontology and lowers the node to a correlated EXISTS/NOT EXISTS subquery. An optional where can refine the two declared sources but cannot replace the ontology identity.

Biological evidence relationships

Ontology joins express exact identity or source-defined key mappings. Biology usually asks a different question: whether a measured pair satisfies explicit criteria for a similarity candidate, or whether a named provider asserted an evolutionary relationship. RQL keeps those meanings separate with the closed evidence_relationships construct. The only supported kinds are sequence_similarity, structure_similarity, and evolutionary_assertion. Similarity kinds must have status: candidate and at least one numeric biological cutoff; rank alone is rejected. Evolutionary assertions must have status: asserted and remain facts copied from their provider. Every criterion comes from one declared evidence source, and every parameterized cutoff must be required or have a default so it cannot silently disappear at execution time.
Compilation lowers the criteria to typed row predicates. It does not add an ontology edge, turn similarity into equality, or infer a transitive closure. The compilation explanation and reproducibility lock carry the relationship kind, method field, pinned evidence database/release, criteria_digest, and complete relationship_digest. Changing a cutoff changes the criteria digest; changing the evidence release changes the relationship digest.

Candidate decision ledgers

An RQL run normally returns the recipe’s result plus aggregate relationship audits. When you need to inspect exactly why each measured pair did or did not enter a candidate cohort, use the closed evidence-ledger projection:
There is one deterministic row per candidate and criterion. Each row includes the ontology primary key, subject, object, method, scope, measured value, unit, ontology value type, operator, threshold, missing-evidence policy, explicit criterion and cumulative pass, fail, or missing states, cumulative acceptance, exact release, and compiler, ontology, criterion, and relationship digests. Set-operation rows retain their branch number and union_all, intersect, or except identity instead of being silently mixed with the base release. Candidate rows are restricted by every active filter local to the evidence source—such as a retained search id, namespace, or rank bound—and expose the canonical filter count and digest. Filters that need a joined contextual source are excluded with a structured warning instead of being approximated. Branch filters are rebound to their set-operation aliases. In cumulative state, an observed failure takes precedence over missing evidence; missing means that no observed criterion has failed, while is_cumulative_accepted records the declared retain-or-reject policy exactly. A threshold sweep can replace only one compiled numeric criterion, must preserve the ontology’s direction and range, and reports input, accepted, gained, and lost membership counts. It does not change the recipe result or turn similarity into equality. Evidence profiles cover the closed multi-criterion case. Each --profile names a bounded conjunction of thresholds by compiled relationship name and criterion index. The compiler still owns the field, operator direction, unit, range, and missing-evidence policy. It emits one typed candidate/profile row with accepted, failed, or missing state and the first responsible criterion. Every profile has an exact comparison with the authored criteria; each profile after the first also has an exact comparison with the preceding profile in caller order. Those adjacent comparisons report gained, lost, unchanged accepted, unchanged rejected, and missing counts with their own measurement and membership digests. Set-operation branch overrides remain scoped to the compiled branch relationship and never leak to the base branch. Relationship comparisons cover concordance across independent evidence sources. Where a profile measures sensitivity within one relationship, a --relationship-comparison name=left:right compares two compiled relationships over their shared typed candidate identity space—for example a base release against a set-operation release branch, or two methods over the same subject and object. Each distinct accepted (subject, object) key is classified both, left_only, or right_only, and the summary reports exact counts, the two methods and releases, the matched scope, per-side source digests, and a membership digest. Comparability is decided once, at compile time, from the ontology. Two relationships are comparable only when they share a scope kind and either resolve to the identical typed subject and object columns (the base-vs-branch case) or declare the same non-empty identifier namespace on both. Overlapping values never imply comparability: a pair with a mismatched scope, an undeclared namespace, or different namespaces resolves to not_comparable with an exact reason and a single sentinel row, never a fabricated join. Comparisons are caller-selected and bounded; the compiler never compares all pairs, and it never turns concordance into biological truth. JSON returns the complete envelope, including profile_comparisons, profile_pair_comparisons, and relationship_comparisons. CSV and NDJSON stream the table selected by --table criteria|profiles|comparisons; --format parquet --max-rows N hands the corresponding portable query to the generic immutable query exporter. Profile and comparison result and summary digests are bound into the reproducibility lock. The ledger is an execution option, not a new expression language. RQL remains closed: callers cannot inject columns, SQL expressions, macros, CTEs, or window functions through it.

order_by

Sorts the result by a dimension or measurement alias. When a module declares result_key, every alias must be projected and included in order_by. The compilation response also reports the compiler-derived result_grain, the declared total key, and activated/pruned joins, so a caller can inspect pagination and fanout semantics before executing.

strict_joins

Whether unresolved join keys fail loudly (true) or are tolerated (false). See Join safety.

quality_notes

Free-text caveats that travel with the recipe — most often the reminder that the recipe returns measurements from pinned relations, and biological interpretation stays with the caller. The engine is an instrument; the recipe never editorializes.

One ontology, many questions

Because every recipe draws on the same typed ontology, a handful of small files covers biological questions that would otherwise each need bespoke, error-prone SQL: Each is a different scientific workload — variant↔ligand proximity, local pocket confidence, interface contacts, and mapping evidence — answered by the same typed layer. Recipes do not retain joins whose review provenance is missing; affected measurements stay single-relation until a reviewed join is promoted.

Compilation and reproducibility

A recipe is not the query engine; it is a portable description that compiles to a SELECT. Portable SQL is Rafflesia’s universal execution contract: the public boundary is stateless — a query submits (database, release, relation) sources plus a bounded SELECT, and gets back typed rows, measurements, provenance, and warnings. Two properties follow:
  • Portable. The same .rql compiles to the SQL the bounded engine (DuckDB or ClickHouse) runs — the recipe never hard-codes a dialect.
  • Reproducible. A run is pinned to the ontology it compiled against, stamped with that ontology’s digest and the resolved release digests. Given the recipe and those digests, the compiled SQL and its result are fully determined.
Reproducible down to the digestWhen a result needs to be defended — in a model’s training set, a report, or a decision — the recipe plus the ontology digest is enough to regenerate exactly the SQL that produced it. Nothing is inferred at run time.

Where to go next

  • Revisit Identity & namespaces to see why the join keys in a recipe are typed the way they are.
  • See Join safety for what the ontology checks before a recipe reads a row.