Skip to content
Raw Markdown

Tables and joins

One file per table at tables/schema/table.yml with its columns inline, and one joins.yml for every join in the ontology.

The schema directory is the schema name verbatim, in the warehouse’s stored case, which is why a Snowflake tree has tables/STALLORA/FCT_ORDERS.yml while a PostgreSQL one has tables/public/orders.yml. The schema_name and table_name fields inside the file are the authoritative identity and the path is derived from them; if they disagree, validation fails.

Table fields

FieldTypeNotes
schema_namestring, requiredPhysical schema, warehouse stored case
table_namestring, requiredPhysical table name, stored case. The filename derives from it
columnslistInline column objects, below
descriptionstringWhat a row is, the grain, the gotchas
domain_pathstringPath of the domain this table lives in. Must name a domain that exists in the tree. Absent means the table is tracked but not placed in any domain
grainlist of stringsColumn names that identify a row, stored case
synonymslist of stringsAlternative names users say. Matching is case-insensitive
is_virtualboolSee virtual tables. Only ever appears as is_virtual: true
sqlstringThe defining SELECT of a virtual table. Never valid on a physical table
lineage_descriptionstringManaged. Prose provenance of a built table
source_tableslist of stringsManaged. Upstream tables this table is built from
source_sqlstringManaged. The upstream SQL that builds this table, not the virtual-table definition
table_typestringManaged. Warehouse-reported type from introspection
approximate_row_countintegerManaged. From introspection

Managed fields are written by Cassis, from warehouse introspection or from a Cassis build. Leave them as you found them.

Column fields

FieldTypeNotes
namestring, requiredPhysical column name, stored case
data_typestringWarehouse type as introspected
descriptionstringValue lists, semantics, caveats
nullableboolDefaults to true. Only ever appears as nullable: false
ordinalintegerZero-based position from introspection. Drives the canonical column order
sourceenumintrospected or manual. manual marks a hand-added column that warehouse sync will never overwrite or drop
synonymslist of stringsAlternative names for this column
unitstringFree text: EUR, days, count, %

Columns are ordered by ordinal, with columns lacking one going last, alphabetically.

columns:
- data_type: TEXT
  description: Product category label. Join key from PRODUCTS.CATEGORY.
  name: CATEGORY
  ordinal: 0
- data_type: TEXT
  description: 'Broad department the category rolls up to. Values: electronics, home_garden,
    fashion, health_beauty, sports_leisure, media, food_drinks, industry_tools, automotive,
    office, other.'
  name: DEPARTMENT
  ordinal: 1
  synonyms:
  - department
description: Lookup mapping each product category to a broader department. One row
  per category. LEFT JOIN from PRODUCTS on CATEGORY to roll sales up to the department
  level.
domain_path: marketplace
grain:
- CATEGORY
schema_name: STALLORA
table_name: DIM_CATEGORY

Virtual tables

A virtual table is a table the warehouse does not have: is_virtual: true plus a defining SELECT in sql. To the agent it looks like any other table, with its own description, columns, grain, and joins. At query time Cassis expands the reference by inlining the sql body. Declare the output columns inline as usual, marked source: manual since they are hand-declared rather than introspected.

  • Expansion is single-level. A virtual table’s sql must not reference another virtual table, because such a reference will not expand at query time.
  • Quote every identifier in the sql body with its explicit stored case, output column aliases included. Bare identifiers get case-folded by the warehouse and stop matching the declared columns.
columns:
- data_type: TEXT
  name: SELLER_ID
  source: manual
- data_type: NUMBER
  description: Delivered GMV for the seller in the month, in EUR.
  name: GMV
  source: manual
  unit: EUR
description: One row per seller per month, with delivered GMV.
domain_path: marketplace
grain:
- SELLER_ID
- MONTH
is_virtual: true
schema_name: STALLORA
sql: |-
  SELECT
    "STALLORA"."ORDER_ITEMS"."SELLER_ID" AS "SELLER_ID",
    DATE_TRUNC('month', "STALLORA"."ORDERS"."ORDER_PURCHASE_TIMESTAMP") AS "MONTH",
    SUM("STALLORA"."ORDER_ITEMS"."PRICE") AS "GMV"
  FROM "STALLORA"."ORDER_ITEMS"
  JOIN "STALLORA"."ORDERS"
    ON "STALLORA"."ORDER_ITEMS"."ORDER_ID" = "STALLORA"."ORDERS"."ORDER_ID"
  GROUP BY 1, 2
table_name: SELLER_MONTHLY_SALES

Joins

Every join in the ontology lives in a single joins.yml, as one YAML list. There are no per-table join files. The list is kept in canonical sort order (by from_schema, from_table, to_schema, to_table, then condition_sql), so keep it sorted when adding one by hand: an out-of-order list fails the round-trip check.

FieldTypeNotes
from_schemastring, requiredStored case
from_tablestring, requiredStored case
to_schemastring, requiredStored case
to_tablestring, requiredStored case
condition_sqlstringThe ON-clause boolean expression, columns table-qualified, in your warehouse’s dialect. A join without one is useless, so always set it
column_pairslist{from_column, to_column} equi-join pairs. You can omit this when authoring: Cassis derives the pairs from condition_sql on import, and they appear in the next export
cardinalityenumone_to_one, one_to_many, many_to_one, many_to_many, oriented from to. many_to_one means many from-rows match one to-row
descriptionstringJoin-specific caveats: fan-out traps, partial bridges, dedup requirements
sourceenumintrospected or manual. Set source: manual on joins you author. introspected joins are owned by warehouse sync, which derives them from foreign keys and may reconcile them
parse_okboolManaged. false means Cassis could not parse condition_sql into pairs and treats the join as opaque. Never write it
- cardinality: many_to_one
  column_pairs:
  - from_column: CATEGORY
    to_column: CATEGORY
  condition_sql: STALLORA.PRODUCTS.CATEGORY = STALLORA.DIM_CATEGORY.CATEGORY
  description: Maps a product's category to its department.
  from_schema: STALLORA
  from_table: PRODUCTS
  source: manual
  to_schema: STALLORA
  to_table: DIM_CATEGORY

Removing a table takes its joins and metrics with it. If you delete a table file by hand, delete the joins that reference it in the same commit, or the import fails naming the unknown table.