The pinned rules
Snowflake publishes the Semantic View YAML specification and its validation rules as prose, and enforces them whenSYSTEM$CREATE_SEMANTIC_VIEW_FROM_YAML runs on
an account. Dex reads the committed YAML without one, so it restates the rules it
relies on as its own port.
The format has no version key. The version a fingerprint records is Dex’s own:
the revision of the specification and rules this reader was written and
qualified against.
The fingerprint records
vendor: snowflake and format_version beside every
hash. A later port that reads a file differently is reported as that change
rather than as every definition changing.
Required extras
Reading the layer installs no Snowflake package and opens no Snowflake
connection. The
[snowflake] extra is the warehouse connector, which only a
governed query (#1290) will need.
The YAML document is read; the DDL is not
Each configured file is one document in Snowflake’s YAML specification: the input ofSYSTEM$CREATE_SEMANTIC_VIEW_FROM_YAML and the output of
SYSTEM$READ_YAML_FROM_SEMANTIC_VIEW. A repository file and a deployed view can
therefore be compared like for like.
- The view is named by its
name. A file whose stem disagrees with it warns (file_name), and two files declaring one view (names compared ignoring case) refuse the second (duplicate_view). - Refused by name, as another format:
- a
.sqlfile (CREATE SEMANTIC VIEW ...), in config; - a legacy Cortex Analyst semantic model, recognized by a table declaring
measures(legacy_semantic_model). Snowflake accepts such a file and drops what a Semantic View does not read; Dex refuses rather than read part of it.
- a
dbt_semantic_view models and DDL is a follow-on (#1298).
YAML as Snowflake reads it, and one difference
The reader shares Dex’s strict YAML loader with the Metric View reader:- YAML 1.2 booleans;
- a key that is not text is refused;
- a key written twice in one mapping is refused (
malformed); - alias expansion is capped, and a recursive alias is refused.
stricter/ set).
What a file is checked against
Each rule below is one row ofRULES in semantic_views/validator.py. Every
refusal names the file and the element (tables['orders'].metrics['revenue']).
A problem in one member or relationship refuses that element and keeps the rest.
A problem in a logical table’s own definition (name, base table, keys) refuses
the table. A problem in what the whole view rests on refuses the view.
Structure
Relationships
Expressions (parsed with sqlglot in the
snowflake dialect)
Granularity follows the relationships. A relationship’s left table is finer than
its right table (many rows to one), unless its left columns are themselves a key
(one-to-one), and the order is transitive. A refused member retracts the members
that read it (a metric’s non-additive dimension included), to a fixed point.
The search for relationship paths checks reachability first and is bounded, so
a dense relationship graph cannot stall the read.
Where the port and the documentation disagree
The account decided, and each row below records what it said:
What only the warehouse can decide. Whether a name a table reads is one of
its base table’s columns is a fact about the warehouse, so the static reader
accepts it.
maintain’s schema axis holds the columns to the warehouse (see below).
How a Semantic View maps onto the catalog
A metric is groupable by its own table’s dimensions and those of every table its
relationships reach many-to-one, except a table joined to it more than one way
(or many-to-many), unless its
using_relationships names a relationship on the
path. A view-level metric is groupable by what all of
its inputs share.
Why every name carries its view. Two views routinely both declare orders.
Inside a view, the name is what Snowflake’s own SEMANTIC_VIEW(...) clause takes
(orders.order_count), and the view is the clause’s first argument. A name never
depends on which other views are configured, so adding one moves nothing.
Columns. A member that reads another member is followed to the column behind
it. A computed one carries no column: the PII gate parses its expression,
rewritten over the base columns behind its members.
Carried and not modelled, each named in the catalog’s notes:
- window function metrics
non_additive_dimensions(NON ADDITIVE BY)- ASOF and range relationships
using_relationships- level-of-detail expressions
- table
filtersand memberlabels variables,max_staleness,tagsand materializations- the Cortex Analyst keys:
verified_queries,custom_instructionsandmodule_custom_instructions
- a display label on every kind
- a metric
filter, since a Semantic View metric has none apart from its expression - time grains and a time axis
model_refandagg_time_dimension- an entity’s label and description
query and values are not available yet
A governed Semantic View query is a SEMANTIC_VIEW(...) clause rendered by Dex
and executed through the Snowflake connector under the cost guard (#1290). Until
then explore semantic query refuses by name. explore semantic values resolves
the token and runs the values PII gate first, so a personal dimension is refused
as PII before it is refused for want of a runtime. Profile the relations the
catalog names with explore profile instead.
Deployed semantic views and the warehouse
A semantic view on the account is not a relation Dex profiles: a plainSELECT on one fails. Checked live:
SHOW VIEWSandSHOW TABLESdo not list a semantic view, so the inventory, profiling, snapshots andmaintain grainnever reach one;SHOW COLUMNSlists its members, withkindDIMENSION,FACTorMETRIC, and the inventory skips every row that is not aCOLUMN;- the dev-namespace existence check lists semantic views with
SHOW SEMANTIC VIEWS, so a schema holding one is not read as empty.
What maintain makes of the layer
The schema axis holds each view to the columns and types Dex captured
through its own connection. Snowflake compares a semantic view against its
tables only when one is created, and Dex creates none, so the rules run on Dex’s
metadata.
Each finding names the view and the element.
declared_column_missing and
declared_type_conflict name it in data.element
(tables['orders'].dimensions['region'], relationships['orders_customers'],
tables['customers'].unique_keys[0]) and carry the column. A
declared_relation_missing names the logical table and has no column. One
declared_dependency_dropped per relation lists each element it takes, with the
reason, under data.reasons. Probed on an account, Snowflake accepts all three type conflicts when the
view is created. What happens after that sets each one’s severity and remedy:
Reconcile cuts in place.
maintain reconcile cuts each removed member,
derived metric and relationship out of its list, along with the comments
written directly above it, and stores the result as a semantic snowflake update plan. The reader validates the plan, strict rules included, before it
is stored, and every other byte is kept. An emptied list is written [], which
Snowflake accepts.
Advisory, never written:
- a logical table whose
base_tableis gone; - a key naming a dropped column;
- a
definitionquery reading one; - a time dimension over text;
- a removal the reader would refuse, or one that leaves the view no member.
semantic snowflake update. Snowflake trusts a declared key without
checking it: with one key duplicated, a count grouped through the relationship
includes that customer’s orders twice.
A read-back fingerprints as its file. SYSTEM$READ_YAML_FROM_SEMANTIC_VIEW
changes nothing that is part of the definition, so the hash covers a canonical
form. What Snowflake does to a view on read-back:
- spells every identifier in upper case;
- sorts lists;
- writes
access_modifier: public_accessand the column’sdata_typeon every member; - re-types
sample_values; - adds empty
synonymsanddescriptionto a derived metric; - moves
custom_instructionsundermodule_custom_instructions.
- names every definition in lower case, and folds columns and table parts too;
- keys lists of named elements by name;
- drops the defaulted values;
- leaves out
data_type, andsample_valuesexcept anis_enumdimension’s, which are its complete allowed set and so are hashed (never stored); - hashes expressions as written.
Authoring
semantic snowflake define|update|plan --edits-file <f> (#1292) authors the
configured files through a stored plan that transform apply writes. Dex never
deploys: nothing runs CREATE SEMANTIC VIEW or
SYSTEM$CREATE_SEMANTIC_VIEW_FROM_YAML, nothing grants a privilege, and no
authoring path opens a Snowflake connection. Check a file with
SYSTEM$CREATE_SEMANTIC_VIEW_FROM_YAML('<database>.<schema>', $$ <file> $$, TRUE)
(verify_only) before you deploy it yourself.
- The write surface is exactly
semantic.snowflake.files. A path off it, a.sqlfile and another suffix are refused by name, the first with thedex config setthat adopts it. A delete is refused. A view is named by its ownname, so there is no rule on file stems; two files declaring one view stayduplicate_view. - Edits are whole files, written byte-for-byte, all or none, and each file keeps its mode. A file changed after planning is a conflict. The set apply would leave is validated again, strict rules included.
- The whole prospective set is validated by the rules above. Every error
anywhere in the set refuses the plan (a non-text key, a key written twice and a
duplicate member name among them), and so do three warnings in a file the plan
writes: an unknown key,
dimension_access(Snowflake keeps the dimension private, which hides it from every query) andunresolved_member. - Classified by element. Each logical table (
view.table) and derived metric (view.metric) in the edited files isdefined,updatedorunchanged, compared as YAML 1.2 values, so reformatting is not a change. A change to a view’s other parts only (relationships, its description, Cortex Analyst’s keys) lists the view asupdated.definerefuses an existing view and a removal;updaterefuses a new view, and reports a table it adds to an existing one asdefined. - Derivation from a declared dbt mart, and annotation with a
declarationspayload, follow the mapping in the transform skill’sdeclarations.md. A derived table is spliced into the file whose tables its foreign keys reference, or written as a view of its own. A Semantic View has no free-form metadata key, so the population is carried only as the last sentence of the table’sdescription, which the catalog withholds, and units, NULL rules, assumptions and any additivity but a semi-additive metric’s are named as not carried. Nothing is written intocustom_instructions, which is Cortex Analyst’s prompt. A population is never written as a filter. - A semi-additive metric reads the latest date. Snowflake sorts by each
non_additive_dimensionsentry as stated and reads the rows of the last value in that order, checked on a live account:sort_direction: descendingreads the earliest date, and withnull_order: last(the default) a NULL date is last and wins. Dex therefore writessort_direction: ascendingwithnull_order: first, whose last value is the latest real date. - Annotation edits inside the table. Only the lines a declaration changes are
rewritten: the table’s
description, one member, an appended metric, an appended relationship. Comments inside the table are kept. A table or member list written in flow style is refused for that declaration rather than rewritten.
The dogfood corpus
packages/dex-core/tests/semantic_views_vendor/fixtures/ is both the offline test
corpus and what a maintainer deploys to a scratch schema with
scripts/snowflake_semantic_views.py (after
scripts/setup_snowflake_semantic_views.sh). It is split three ways:
valid/: Snowflake accepts it and Dex reads it.invalid/: both refuse it.stricter/: Snowflake accepts it and Dex refuses it by design.readback/: what Snowflake returned for each deployedvalid/view.
verify_only needs a running warehouse and resumes it, so each verify run bills
at least a warehouse resume.
Source: references/snowflake-semantic-views-compatibility.md (edit on main). This page is generated; change the source, not the site.