Skip to content

Language reference

Reference. TTR-M in full — the definition kinds, properties and constructs the parser accepts.

Skeleton

This is the language-reference skeleton for TTR-M grammar v0.9 (the grammar version tracks the product major.minor). The tables below enumerate the surface; the per-construct property detail marked (from grammar) is generated from TTR.g4 so it cannot drift from what the parser accepts, and the prose marked (hand-written) is authored. Fuller worked examples are migrating in from the user manual.

File shape

A TTR-M document is a package member with a model directive:

package <name>            // the package this file belongs to
import  <name>[.*]        // optional cross-package imports
model   <code> [schema <id>]   // the layer this file defines
def <kind> <name> { … }   // one or more definitions

<code> is the layer: db, er, binding (er↔db and other mappings), md, cn (cnc is deprecated). Identifiers allow Latin-1/Extended letters, so real Czech names are valid verbatim.

Definition kinds (from grammar)

Layer Kinds
db table, column (inline), view, index, constraint, fk, procedure
er entity, attribute (inline), relation
binding er2db_entity, er2db_attribute, er2db_relation
cn kind, variant (or subkind), role, event, quality, phase, relation_type; grounded by cn2md_* / cn2er_* in binding
cnc (deprecated) role, er2cnc_role
md domain, dimension, hierarchy, measure, cubelet, map, md2db_*, md2er_*
structure package, import, area (packages: / entities:), graph, drill_map, query, world

Packages and areas (hand-written)

  • A package is a directory of .ttrm files sharing a package <name> header, with a modeler.toml manifest at its root. Packages are the unit of import and versioning.
  • An area is a subject area that spans packages — def area <name> { packages: […], entities: […] }. Use it to name a business domain that cuts across package boundaries.

Bindings (hand-written)

Bindings keep the er meaning anchored to the db truth without repeating it. er2db_entity, er2db_attribute and er2db_relation map logical constructs to their physical targets; an inline binding: shorthand on an entity/attribute/relation covers the common case. This is what lets the db mirror be regenerated while the er model stays hand-owned.

Naming, search and the lexicon (from grammar)

Entities and attributes carry the vocabulary the understanding layer resolves against:

  • aliases: [ … ] — alternate names for the concept.
  • search { searchable, fuzzy, keywords { <locale>: [ … ] }, patterns [ … ] } — how the fuzzy and search doors find this field, including localized keywords.
  • lexicon { … } — inline sugar for terms, patterns and examples the resolution layer uses.
  • valueLabels { "<code>": { <locale>: "…" } } — human labels for coded values.
  • semantics { … } — the closed semantic vocabulary, in two orthogonal facets. On an attribute or column, role: declares the grounding facet (which date to filter on, which column is the amount, where the coordinates are). On an entity or table, kind: is the grounding facet and name: / code: / measures: are the mention facet — which attribute carries this entity when a human refers to it by name, by code, or as a value to aggregate. measures: is ordered and its first item is the entity's default measure.

nameAttribute: / codeAttribute: are deprecated

The entity properties nameAttribute: and codeAttribute: still parse, but semantics { name: … , code: … } is the source of truth from vocabulary v3 on. Declaring only the legacy property, or declaring both in agreement, raises a deprecation warning; declaring both so they disagree is an error — a disagreement is always a bug, not a preference. For quoting a literal, the column a quoted value filters is read from the semantics block; where the block names no name: (or no code:), the legacy property is used as a fallback, so an existing model keeps working. Only the semantics block can say more than that — a code_pattern: needs code: beside it. Prefer the semantics block in new models.

Quoting a literal (hand-written)

TTR-M has one escape hatch on the asking side, and it belongs here because what it needs from a model is declared in the section above. Putting a span of a question in quotes means take this exactly as typed: it is a value, not language. Nothing rewrites it, nothing corrects its spelling, nothing proposes alternatives for it, and no language model is asked what it might have meant. The one thing that can happen to it besides becoming a filter is a lookup among the values the model already knows for that column — and only when the question gives no predicate word and the model has such a vocabulary (below).

Show stores starting with "Abl"
Zobraz prodejny začínající na „Abl"

Both lines filter the store name with starts with Abl: starting with and začínající na are predicate words from the standard library (below), and "Abl" is the value they apply to.

The delimiter family

Any member of this closed set opens a literal, and any member closes it — so a question pasted out of Word, typed on a phone keyboard, or written with Czech typography all work without the user knowing which quote character they produced.

Character Name
✅ " U+0022 quotation mark
✅ „ “ ” ‟ U+201E · U+201C · U+201D · U+201F — the curly family, German/Czech low-high included
✅ « » U+00AB · U+00BB guillemets
❌ ' ‚ ‘ ’ apostrophes and single quotes — they appear inside real names (O'Brien)
❌ ‹ › single guillemets
❌ ` backtick

A delimiter only counts as one at a word boundary: an opener must follow the start of the text, whitespace, or one of ([{-–—:;,, and a closer must precede the end, whitespace, or one of )]}.,;:!?-–—. That is what keeps the inch mark in a 5" screen from opening a literal. Pairing is left to right and greedy to the nearest closer; an unpaired delimiter is just text, and so is an empty one. Whatever sits between the delimiters is the value, with the outer whitespace trimmed and everything else — case, diacritics, inner spaces, punctuation — kept exactly as typed. There is no escaping syntax, because a literal is never read as anything but itself.

What the literal filters, and how

The literal has to land on a column, and the model is what says which one. The head it attaches to is the nearest model object that governs it, and that head's mention facet decides:

The head declares A literal there filters on
semantics { name: … } the name attribute
semantics { code: … }, and the literal is code-shaped the code attribute
neither nothing — the literal stays unattached and the question is refused, visibly

That last row is the one to design for. A head with no mention facet cannot carry a quoted literal at all, and refusing is deliberate: guessing a column for a value the user was explicit about is the one failure mode that produces a confident wrong answer. If quoting a name does not work on some entity, the fix is semantics { name: … } on that entity, not a rephrasing. (An entity that still uses the legacy nameAttribute: / codeAttribute: gets them as a fallback.)

Code-shaped means the literal matches the head's code_pattern: — a Java regular expression every code of that entity matches — or, failing that, the generic code shape: capital letters, digits and - / ., starting with a letter or digit, and containing at least one digit. The digit is not required when the head declares a code: and no name: (there is nothing else the literal could be). A period table's code with a code_format: mask (yyyyMM) is recognised by that mask. Declare code_pattern: whenever your codes are not caught by the generic shape — codes made only of letters, for instance:

def entity promotion {
    semantics { name: promo_name, code: promo_id, code_pattern: "^[A-P]{16}$" }
    attributes: [
        def attribute promo_name { type: text },
        def attribute promo_id   { type: text }    // AAAAAAAABAAAAAAA …
    ]
}

code_pattern: needs code: in the same block, and a pattern that does not compile is an error (TTR-SEM-219). Prefer character classes to backslashes ([0-9], not \d): inside a TTR string a backslash escapes the next character, so \d has to be written \\d.

How it filters comes from the predicate words beside the literal — ordinary lexicon vocabulary shipped in the standard library, matched in Czech and English:

Predicate Some of the words Filter
starts_with začínající na · začíná na · s prefixem · starts with · starting with · begins with col LIKE ? \|\| '%'
not_starts_with nezačínající na · nezačíná na · not starting with · not beginning with NOT (col LIKE ? \|\| '%')
ends_with končící na · končí na · s příponou · ends with · ending with · ending in col LIKE '%' \|\| ?
not_ends_with nekončící na · nekončí na · not ending with · not ending in NOT (col LIKE '%' \|\| ?)
contains obsahující · obsahuje · s textem · v názvu · contains · containing · including col LIKE '%' \|\| ? \|\| '%'
not_contains neobsahující · neobsahuje · not containing · without · excluding NOT (col LIKE '%' \|\| ? \|\| '%')
equals přesně · s názvem přesně · rovná se · exactly · named exactly · equal to col = ?
not_equals nerovná se · se nerovná · not equal to · not equals col <> ?

Every predicate word matches whole and exactly as listed — starting with fires, a bare with never does — so the forms are spelled out one by one rather than left to a lemmatiser: Czech participles in every case (firmy obsahující, firem obsahujících, s firmami obsahujícími), both word orders where Czech uses both (rovná se · se rovná). A form may be at most three words. Negation is part of the vocabulary: not starting with and nezačínající na are the not_starts_with predicate, never starts_with, and a predicate word right after a negator (does not start with, never, ne) is negated too. The bare named / s názvem are deliberately not predicate words: quoting a name after them keeps the name default below; say named exactly / s názvem přesně to mean equals.

With no predicate word, what happens depends on where the literal landed:

  • On a name attribute whose values the model indexes (a member vocabulary), the literal is first looked up among those values. One match: the question is about that value. Several: you are asked which one you meant. None — or no vocabulary to look in, or the lookup service unavailable — falls through to the default below. The literal itself is never altered; a lookup can only pick one of the column's existing values for it.
  • Otherwise, the default follows the facet it was attributed to: a name (or an attribute that is itself the head, as in customers with the name "Valmy") is contains, a code is equals. Someone asking about a name usually means "has this in it"; someone quoting a code means that code. The resolver decides this from the attribution and sends the predicate to the query stage explicitly — the filter never depends on a later stage re-guessing it from the literal's shape.

An estate can add its own wording — a pred: entry in the estate's lexicon/ area extends the shipped list rather than replacing it — but the eight predicates themselves are a closed set.

The literal is a bound parameter, always

The quoted text never becomes SQL text. It is bound as a parameter, and the query stage escapes the wildcard characters %, _ and [ inside it, with an explicit ESCAPE clause, so a value containing one filters for that character rather than acting as a wildcard — [ included, which SQL Server would otherwise read as the start of a character set. A user who asks for names containing "50%" gets rows with 50% in them, and "[DE] Pelex" finds exactly that text.

Queries (from grammar)

  • Named queries — def query <name> { … } with a SQL/DSL template and a parameter list; surfaced through list_queries and runnable through the query door.
  • Pattern queries — parameterized query shapes an agent can bind and run.
  • Logical queries — a def query declared in a model er file is written over entities (er.query.<name>, bare JOINs conditioned from the relations, no parameters), and an entity can be stacked on it (binding: { target: { query: er.query.<name> } }) — agents then ask over that entity.

Governance — policy, not a security block

There is no security {} construct

Access policy is not part of the model text: row-level filters, column masks and the roles a question requires live in the validator's store, versioned in git beside the model (Policy in git). The validator enforces them downstream and reports what it applied in pipelineWarnings on every governed answer.

Worlds and composition (from grammar)

def world <name> { … } (with engine/executor/storage nouns) describes a composition of packages into a deployable whole — the model as the deployment artifact, named.

Types (from grammar)

The canonical type tokens (int, bigint, text, decimal, float, bool, date, time, datetime, …) are what db columns and er attributes carry; ttr import-schema normalizes SQL types onto them. The full type table is generated from the grammar.


For a guided path into the language rather than a lookup, start with model your first three tables; for how the layers relate, see the layers.