Skip to content

Vocabulary

Every annotation any active extension permits, generated from the profiles themselves. If an annotation is not listed here, rule V001 rejects it.

Annotations on a table

Annotation Range Meaning
varda:fact_type FactType The temporal shape of a fact table. Facts only.
varda:grain_statement string What exactly one row of this table represents, as a sentence. Required on facts, alongside varda:grain. Conventionally phrased "one row per ...". Checked for being a sentence and nothing further: varda:grain carries the half of the claim a validator can test, and this carries the half it cannot. The sentence is not redundant with the columns. It states why those columns, and what a row means to somebody reading the model rather than querying it. A fact table whose grain nobody can state in a sentence is one whose double counting nobody can rule out, and the discipline of writing it is most of the value.
varda:grain string, list The columns at which rows of this table are unique — the set that together identifies exactly one row. Required on facts. This is the grain proper: in every formal treatment of dimensional modeling the grain is the column set that identifies a row. Every entry must name a column of this table, and each must be a foreign key or a degenerate dimension: those are the columns that locate a row in the model's dimensional space. A measure cannot be part of a grain, because a grain is what a row is, not what it records. Stating the grain as columns is what makes it checkable — a validator can test the claim, and a generator can emit it as a uniqueness constraint the database enforces.
varda:hierarchies Hierarchy, list Named drill paths over this dimension's columns. Dimensions only. Each hierarchy lists its levels from least to most granular — [country, region, city], not the reverse. A column may appear in several hierarchies, which is how a date dimension carries both a calendar path and a week path: weeks do not nest inside months, so they are separate hierarchies rather than one chain. A level names what a reader sees when they drill, so it must be a column somebody can read. A surrogate key or a foreign key is a fine identity and an unreadable label — nobody drills into 4718 — so neither may name a level. Where the coarser levels are their own tables, write foreign_key.column to reach the readable column through the key: country_key.country_name rather than country_key. Both halves are checked — the near column must be a foreign key of this table, and the far one a column of the dimension it points at. A level names a member of the level; whether it also identifies one is a separate question. month_name holds "January", which every year has, and city_name holds "Springfield", which several states have. Both describe the path correctly and neither can be grouped by alone. Prefer a column that identifies where one exists naturally — year_month in a date dimension — and expect anything grouping by a level that does not to carry the coarser levels with it. Reaching through a foreign key avoids the question, because the key has already picked one row out. That each level determines the one above it — that every city is in exactly one region — is asserted here and not checked, because it is a claim about data rather than about the schema. What is checked is that the levels are real, distinct, and the kind of column a level can be. Only fixed-depth paths where each level rolls up into exactly one parent. A hierarchy whose branches differ in depth, whose levels can be skipped, or where a child has several parents is a bridge table.
varda:physical_name string The table name to emit, when it differs from the class name. Without it, generators derive a physical name from the class name.
varda:role TableRole required What part this table plays. Required on every table.
varda:scd SlowlyChangingType How this dimension responds to source changes. Dimensions only. Declared on the table rather than per column, because a mixed dimension — some attributes type 1, others type 2 — is a genuinely harder object that this core deliberately does not model. Split it into two dimensions, or handle it in an extension.

Annotations on a column

Annotation Range Meaning
varda:additivity Additivity How this measure may be aggregated. Required on every measure.
varda:max_length integer The widest value this column holds, in characters. Emitted as the length of the SQL type: VARCHAR(80). String columns only — string, uri, uriorcurie, ncname. A length on a date parameterizes nothing. Optional, and there is no default. A column declaring none emits a bare VARCHAR, which is unbounded in PostgreSQL and DuckDB and means VARCHAR(1) in SQL Server. Varda states the width the model gave it and invents nothing, because a default width is a number nobody chose appearing in a schema everybody trusts.
varda:physical_name string The column name to emit, when it differs from the slot name.
varda:precision integer How many significant digits this column keeps, counting both sides of the decimal point. Emitted as the first parameter of the SQL type: the 18 in NUMERIC(18, 2). Decimal columns only. A float has no exact precision to declare — that is what makes it a float. Optional. A bare NUMERIC is exact and unconstrained in PostgreSQL and DECIMAL(18, 3) in DuckDB, where a unit price of 0.123456 is stored as 0.123 and no error is raised. V707 asks every decimal measure to say what it keeps.
varda:references string The name of the class this foreign key points at. Required on every column whose role is FOREIGN_KEY, and checked: it must name a class that exists and that is a dimension or a bridge. The table holding the key may be any of the three.
varda:role ColumnRole required What part this column plays. Required on every column.
varda:scale integer How many of this column's digits fall after the decimal point. Emitted as the second parameter of the SQL type: the 2 in NUMERIC(18, 2). Decimal columns only, and needs a varda:precision beside it — there is no NUMERIC(, 2) to emit. May be zero, which is a whole number stored as a decimal and not a mistake.
varda:semi_additive_over string The foreign key across which this measure may NOT be summed — almost always the date. Required when additivity is SEMI_ADDITIVE, and checked against the table's actual foreign keys, because naming a dimension the fact does not have is a constraint that silently never applies.

Structured values

The shapes an annotation's value takes when its range is not a scalar.

Hierarchy

One named drill path over a dimension's columns. Not an annotation class: it carries no varda:applies_to, because it is the range of varda:hierarchies rather than something a model annotates directly.

Key Range Meaning
name string required What this path is called, unique within the table. Names it rather than numbering it, because a dimension with a calendar path and a fiscal path needs the reader to know which is which.
description string What this path is for, when the name alone does not carry it. A dimension with several paths is where this earns its place: merchandise and finance name two ways of rolling the same products up, and which one a reader wants is not decidable from the names.
levels Level, list required The levels, from least to most granular. At least two: one level is not a hierarchy, it is a column. Each is written as a column name — city_name, or country_key.country_name to reach through a foreign key — or as a mapping carrying column and key when what names the level is not what tells its members apart.

Level

One step of a drill path. Written as a bare column name wherever that column identifies the level's members, which is the common case. The mapping form exists for the case it does not. Declared as a class so the shape is documented and machine-readable, not because LinkML enforces it: annotation values are opaque to LinkML, so a level naming a field this class does not have is caught by Varda's own rules rather than by the schema.

Key Range Meaning
column string required The column that names this level to a reader. Either a column of the table the hierarchy is declared on, or foreign_key.column to reach one through a foreign key.
key string The column that tells one member of this level from another under the same parent, when that is not the column naming it. A level showing product_name where two products share a name is identified by sku. Rarely needed. It defaults to the foreign key for a level reached through one and to the naming column otherwise, and a level's full identity is that key preceded by the key of every coarser level — city_name holds "Springfield" for cities in three states, and country, state and city together hold one of them. That path is what the hierarchy already says, so it is never written out. Asserted and not checked. Varda checks the column exists and is the kind that can identify something; whether members are actually distinct is a claim about data.

Enumerations

These are closed. An extension may not add a value to one — see Extending.

TableRole

What part a class plays in the dimensional model. Every class carrying Varda annotations must declare exactly one.

Value Meaning
FACT A measurement event table. Has a declared grain, foreign keys to dimensions, and zero or more measures. Never the target of another table's foreign key.
DIMENSION Descriptive context. Has one surrogate key, one or more natural key columns, and attributes that are almost always textual or categorical. Referenced by facts.
BRIDGE Resolves a many-to-many between a fact and a dimension, or a multi-valued dimension attribute. Carries an allocation factor when the many-to-many is weighted.

ColumnRole

What part a slot plays inside its table. Every slot of a class carrying Varda annotations must declare exactly one. This is the most valuable annotation in the profile: it is what turns a flat list of columns into a structure that generators and validators can reason about.

Value Meaning
SURROGATE_KEY The meaningless integer or hash primary key of a dimension. Exactly one per dimension.
NATURAL_KEY A column carrying the business identity of a dimension row. Together the natural key columns — plus, for a type-2 dimension, the validity window — must be unique.
FOREIGN_KEY A reference to the surrogate key of a dimension or a bridge. Must declare varda:references. Legal on any table. A fact referencing its dimensions is the common case, and a dimension referencing another is a snowflake — which is why a hierarchy level may be written country_key.country_name, to reach a readable column through the key. What a foreign key may not do is point at a fact: that join is fact-to-fact, the grain of the result is neither table's, and no aggregate over it is safe.
MEASURE A numeric quantity that is aggregated. Must declare varda:additivity. Lives on a fact or a bridge, never a dimension.
ATTRIBUTE Descriptive context, grouped and filtered on but not summed. The default role for a dimension column.
DEGENERATE_DIMENSION An identifier that lives on the fact with no dimension table of its own — an order number, a ticket reference. Fact tables only.
VERSION_START The instant from which this version of the row is the one in force. Dimensions with varda:scd: TYPE_2 only. Named for the version rather than for validity on purpose. In practice this column usually records when the warehouse observed the change rather than when the change was true in the business — dbt's snapshots and Data Vault's LOAD_DATE both work this way, whatever they are called. Varda does not distinguish the two, because separating business validity from load time is bitemporality and that is out of scope here. The period is closed at the start and open at the end, following SQL:2011: a row is in force from this instant up to but not including VERSION_END. That convention is what stops consecutive versions overlapping at their boundary.
Ontology term: time:hasBeginning
VERSION_END The instant at which this version stopped being the one in force, exclusive. Dimensions with varda:scd: TYPE_2 only. Optional, and genuinely so. A great many type-2 dimensions store only the start and derive the end from the next version's start; Data Vault virtualizes it as a view over an insert-only satellite. Requiring it would reject correct designs, so nothing does.
Ontology term: time:hasEnd
IS_CURRENT Flags the version in force now. Dimensions with varda:scd: TYPE_2 only. Derivable from the period, and materialized anyway almost everywhere, because the alternative is a null test or a far-future sentinel in every query that wants today's row.
VERSION_NUMBER A monotonic counter distinguishing versions of one natural key, for dimensions that version without timestamps at all. The current version is the highest. Dimensions with varda:scd: TYPE_2 only.

Additivity

How a measure may be aggregated across dimensions. Required on every measure, because the wrong answer here is the single most expensive class of error a dimensional model produces: silently summing something that must not be summed yields a number that looks fine and is wrong.

Value Meaning
ADDITIVE May be summed across every dimension of its fact. Sales amount, quantity, cost.
SEMI_ADDITIVE May be summed across some dimensions but not others — almost always not across time. Account balance, inventory level, headcount. Must declare varda:semi_additive_over.
NON_ADDITIVE May not be summed across any dimension. Ratios, percentages, unit prices, temperatures. Aggregate the components and recompute.

SlowlyChangingType

How a dimension responds when a source attribute changes. Kimball's numbering, restricted to the three types that are common enough to generate for.

Value Meaning
TYPE_0 Retain original. The value is written once at first load and never updated, whatever the source says afterwards.
TYPE_1 Overwrite. No history is kept; the row shows the current value.
TYPE_2 Add a row. History is preserved by versioning: a new row per change, bounded by a validity window and marked with a current flag.

FactType

The temporal shape of a fact table, which determines how it is loaded and what its grain sentence must say.

Value Meaning
TRANSACTION One row per event, at the moment the event occurs. Insert-only.
PERIODIC_SNAPSHOT One row per entity per regular interval, whether or not anything happened. Insert-only, predictable size.
ACCUMULATING_SNAPSHOT One row per tracked item, updated in place as it passes milestones. The only fact shape that is routinely updated after insert.
FACTLESS One row per event or condition, carrying no measure — coverage, eligibility, attendance. Counted rather than summed.