Skip to content

feat(syntax): check Snowflake views and streams against their grammar - #198

Closed
kvlonge wants to merge 12 commits into
mainfrom
feat/syntax-ddl-views-and-streams
Closed

kvlonge wants to merge 12 commits into
mainfrom
feat/syntax-ddl-views-and-streams

Conversation

@kvlonge

@kvlonge kvlonge commented Nov 12, 2026

Copy link
Copy Markdown
Contributor

Why

After the table grammar, the view, materialized view, stream and semantic view forms were the next ddl gap: Raw fallbacks, false rejects of ALTER VIEW properties, and stream options tied to sources Snowflake does not tie them to. This replaces them with checked grammars that match the receipts: where the documentation shows a shape Snowflake refuses with a syntax error, the grammar refuses it too.

Changes

  • Streams, views, materialized views and semantic views build nodes through checked grammars. A stream takes any option, AT or BEFORE (with VERSION) on every source, as Snowflake parses them.
  • ALTER VIEW reads SET and UNSET of properties, IF EXISTS and the name SET may omit; ALTER MATERIALIZED VIEW reads RECLUSTER. An ALTER VIEW without an action and a CREATE missing STREAM are refused.
  • ALTER VIEW SET or UNSET SECURE combined with another property, and RECURSIVE after a temporary scope, are refused: Snowflake's receipts for those cases are syntax errors.
  • A view's column list may follow its properties; a view COMMENT without = is refused.
  • Re-records baseline.json and corpus-verdicts.json on the pinned evidence: seven non-Snowflake view clauses (SECURITY DEFINER/INVOKER, SQL SECURITY, LOCKING, COMMENT without =) are now refused.
  • Root .gitattributes marks tests, scripts and dev-only tools as linguist-documentation, so the language bar counts shipped code.

Verification

Snapshots on the pinned evidence, main's parser → this head:

before after
satisfied obligations 78,979 80,235
syntax errors accepted (false accepts) 2,519 2,359
receipt contradictions 3,526 3,199

New false accepts against receipts: 0. No obligation or parsed case is lost. In the view and stream forms, false accepts go 201 → 42 and false rejects of cases Snowflake parsed 186 → 34.

make check-ci and make verify-recorded pass; crate tests cover each root cause, including the three refused view shapes.

… its positives

When Snowflake refuses every documented positive of a form, every
observed case of the form was marked documented, intended rejections
too. That hid real negative evidence: an unquoted reserved word or a
missing operand that Snowflake rejected could never be judged.

A case in such a form is now documented only when it renders one of
the form's positives. Its other cases are judged by their receipts
like any other observed case. An observed rendering keeps the expect
it replaced, so a positive observed for an empty clause still counts
as a positive. Marks are regenerated.
…variable

EXECUTE DCM PROJECT ... ENVIRONMENT takes a bind variable, so the hand
rows use ( ? ) instead of an identifier. Four of the six statements
then parse and fail only on the placeholder source, so they are no
longer documented contradictions and their receipts settle them. The
two that still fail at the configuration arguments stay documented.
…ases

Pins evidence 780c1ad3: the cases regenerated with documented marks
only on positives and with the DCM ENVIRONMENT bind variable, on top of
the syntax receipts for security integrations, Snowpark package lists,
account, external consumer, listing, policy keyword, unnamed user and
warehouse cases. 374 not_submitted entries now list a case with a
receipt and are removed. Two more now name the double-quoted reserved
word listing cases, which still have no receipt. Marks and the syntax
baseline are regenerated.
CREATE STREAM kept everything after STREAM as raw text, so malformed
statements were accepted. A stream on a table, view, external, dynamic
or event table or a stage now builds a CreateObject: its tags and COPY
GRANTS before ON, the source, AT or BEFORE where the source takes it,
and the properties that source documents. CREATE OR ALTER STREAM and
DEFINE STREAM read a table only. A time travel point may name a STREAM.
CREATE SEMANTIC VIEW was checked against its grammar and then kept as
raw text, and DEFINE SEMANTIC VIEW was refused. Both now build a
CreateObject whose clauses are properties: tables, relationships,
facts, dimensions, metrics and verified queries as lists of their
parts, with the item expressions parsed.
…eir grammar

Recursive, volatile, secure materialized and interactive materialized
views were kept as raw text, and view-level clauses such as COPY = ( ),
JOIN POLICY, AGGREGATION POLICY and WITH CONTACT were refused. A
Snowflake view now reads its scope, column options, view clauses and
query against the grammar of its kind: a materialized view takes
CLUSTER BY and no CHANGE_TRACKING, and CREATE OR ALTER VIEW takes only
CHANGE_TRACKING and COMMENT. Column policies after the column list,
empty column lists and string column names are refused, as Snowflake
refuses them.
…mmar

ALTER VIEW now reads Snowflake's SET and UNSET of view properties,
IF EXISTS, and the name Snowflake lets SET omit. An ALTER VIEW with no
action is refused. ALTER MATERIALIZED VIEW reads RECLUSTER and a SET
with the name omitted. A CREATE whose name is followed by ON, a stream
missing its STREAM keyword, is refused.
Snowflake parses any option, and AT or BEFORE, on every stream source
and refuses a misplaced one only after parsing, so the grammar no
longer ties options to sources. AT and BEFORE take VERSION, a tag value
may be a number, and CREATE OR ALTER STREAM takes tags.
Snowflake parses a CREATE VIEW whose column list follows a view
property, such as CHANGE_TRACKING = TRUE ( id ), and refuses a view
COMMENT written without =.
Test suites, development scripts and development-only tools (the
coverage and format checks, the fuzz targets and the Snowflake
recorder) are marked linguist-documentation, so the language bar
reflects the code SQLQuack ships.
Snowflake rejects a view's SECURE together with any other property in
one ALTER VIEW SET or UNSET, and RECURSIVE after a temporary scope in
CREATE, CREATE OR ALTER and DEFINE VIEW, although the documented
grammar shows both. Every receipt with either shape is a syntax error,
and every single-property SET and UNSET and every plain RECURSIVE view
parses, so the parser now refuses exactly these shapes.
…and stream grammar

On the pinned evidence the view, stream and semantic view grammar
satisfies 1,256 more obligations and accepts 160 fewer
statements Snowflake rejects as syntax errors, with no regression; re-record
the baseline so check-syntax holds the gain.

Seven corpus statements are now refused, as Snowflake's view grammar
has no such clause: SECURITY DEFINER or INVOKER and SQL SECURITY, a
LOCKING clause, and a view COMMENT without =.

kvlonge commented Nov 12, 2026

Copy link
Copy Markdown
Contributor Author

Replaced by #199: the same nine commits rebased onto main after the sweep merged under new SHAs, which left this branch conflicting. Opened as a new branch rather than force-pushing; make check-ci passes on the rebased head.

@kvlonge kvlonge closed this Nov 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant