STRUMENTA LANGUAGE ENGINES · ANALYTIC SQL, IN TYPESCRIPT

A language-services engine for warehouse SQL, not a migration parser.

A TypeScript library on npm that runs in Node and in the browser. Completion, a pluggable formatter, symbol resolution with positioned referents and expression typing across eight warehouse dialects — over SELECT, INSERT and UPDATE. 257 automated tests, and the package is only published if they all pass.

This is a different product from the other four SQL engines we license. Those read estates in batches. This one sits behind an editor and has to produce a usable tree from a query the user has not finished typing.

The engine at a glance
RUNTIMETypeScript · npm · Node and browser
DIALECTSeight, behind one flag
STATEMENTSSELECT · INSERT · UPDATE
GRAMMAR716 lines across eight files
TESTS257, gating every release
SERVICEScompletion · format · symbols · types

Eight warehouse dialects, one model

SQLSelect.g4 · 333 lines · the whole query language · 716 lines in total

There is no such thing as “the SQL language” as one parseable artifact, and this engine does not pretend there is. What it has is a dialect flag with eight values, five dialect modules behind it, and one AST that all of them produce.

Eight SQL dialects producing one abstract syntax tree Generic, Snowflake, Databricks, MSSQL, MS Fabric, BigQuery, PostgreSQL and Redshift are selected by a dialect flag; Redshift is handled as the PostgreSQL dialect. All eight produce one AST, resolved by one symbol resolver and typed by one type system. GENERIC SNOWFLAKE DATABRICKS MSSQL MS_FABRIC BIGQUERY POSTGRESQL REDSHIFT → POSTGRESQL ONE AST · ONE SYMBOL RESOLVER ONE TYPE SYSTEM · ONE FORMATTER SELECTED BY A DIALECT FLAG AT PARSE TIME

Snowflake and Databricks have their own AST transformers; Microsoft SQL Server gets its own lexer variant; BigQuery, MS Fabric, PostgreSQL and Snowflake each ship a database model and a built-in function catalog. Redshift is handled as the PostgreSQL dialect rather than as a grammar of its own — which is a real engineering decision, so it belongs on the page rather than hidden behind an eighth row in a feature list.

The grammar is deliberately permissive. QUALIFY, MINUS, EXCEPT and INTERSECT are all admitted regardless of the dialect flag, so the parser accepts a superset and leaves dialect legality to you. For an editor that is the right design — a red squiggle on valid Snowflake would be worse than none. For a validator it is the wrong one, and you should know which you are buying.

Test depth is not even across the eight, and there is no honest way to present it as if it were. Databricks has seventeen dialect tests behind it, BigQuery eight, MS Fabric three, Redshift two and Snowflake one, plus two cross-dialect cases inside a 257-case suite. Read that as a map of where the engine has been exercised hardest, and tell us which dialects have to work on day one.

What is not claimed anywhere: conformance to the ISO SQL standard. There is no certification here, no “any SQL dialect”, and the engine will reject the Oracle, Teradata and Firebird examples on our other four pages. It is an analytic-SQL engine for cloud warehouses, and that is the whole of it.

Input — Databricks query

dialect: DATABRICKS

SELECT u.user_id, t.tag
  FROM analytics.events u
LATERAL VIEW OUTER explode(u.tags) t AS tag
 WHERE u.ts > '2026-01-01'
QUALIFY ROW_NUMBER() OVER (PARTITION BY u.user_id
                     ORDER BY u.ts DESC) = 1;
LATERAL VIEW is a Hive-descended explode clause with no ANSI analogue, and it sits in the same clause alternation as WHERE and GROUP BY. QUALIFY filters on a window function without a subquery. Neither exists in Oracle or Firebird.

Output — parse tree, by grammar rule

intermediate rules elided

script
└── statement
    └── select_statement
        ├── select_clause u.user_id, t.tag
        ├── relation analytics.events AS u
        ├── relation LATERAL VIEW OUTER … AS tag
        ├── query_clause WHERE, line 4
        └── query_clause QUALIFY, line 5
Real rule names from the shipped grammar. Above the tree sits a Tylasu AST rooted at Script, with the parse tree still reachable from it — which is what the formatter walks — plus a positioned issues list rather than an exception.

Dialects, and how deeply each is exercised

All eight values of the dialect flag the code implements. The test counts are dialect-specific cases inside a 257-case suite — they measure where the engine has been pushed hardest, not what it refuses.
Dialect flag What it selects Dialect-specific tests
GENERIC The default: the shared grammar with no dialect specialization The baseline the whole suite exercises
DATABRICKS Its own AST transformer, an imported grammar file, a database model and a built-in function catalog 17 — the deepest of the eight
BIGQUERY A database model and a built-in function catalog; lower-cased identifiers 8
MS_FABRIC Its own database model, over the Microsoft lexer 3 — tell us if this is your day-one dialect
REDSHIFT Maps to the PostgreSQL dialect — no separate grammar 2
SNOWFLAKE Its own AST transformer, database model and function catalog; upper-cased identifiers 1 dialect-specific case — the transformer is real, the dedicated suite is thin
MSSQL A separate lexer variant and a database model Covered by the shared suite; no dedicated cases
POSTGRESQL A database model and a function catalog; lower-folding identifiers Covered through the Redshift cases
Oracle, Teradata, Firebird, Db2 Not modeled — those are separate engines

What it handles

The hard constructs on this page are not “hard SQL”. They are the problems that only appear when one component has to serve several warehouses at once, and when the input is a query somebody is still typing.

Parsing what is not finished yet

Three parser variants and four entry points exist for one reason: completion. You can parse a whole script, a single statement, a bare expression, or a bare FROM clause with nothing in front of it — which is exactly the state an editor is in when the cursor is mid-query.

standalone_expression:
  expression SEMI? EOF;
standalone_from:
  FROM relation (COMMA relation)*
  query_clause* select_clause* EOF;

Column exclusion that changes the type

SELECT * EXCEPT (a, b) in BigQuery and EXCLUDE in Snowflake remove columns from the result, so they change the shape of the row and therefore its type. A parser that only builds a tree can ignore that. One that computes a TabularType cannot.

SELECT * EXCEPT (internal_id, load_ts)
  FROM analytics.events
       AT (OFFSET => -300);

Identifier case, per warehouse

Snowflake folds unquoted identifiers to upper case, PostgreSQL folds to lower, BigQuery does neither. Get it wrong and symbol resolution fails silently — the query parses, the columns simply never match the schema. This is a real bug class, and one of the changelog entries is exactly it.

// BigQuery: preserved, not upper-cased
SELECT userId FROM proj.ds.Events
// Snowflake: folds to USERID
SELECT userId FROM DB.SCHEMA.EVENTS

Three more belong on the same list. Time travel — Snowflake’s AT and BEFORE, Databricks’s VERSION AS OF and SQL Server’s FOR SYSTEM_TIME — attaches a clause to a table reference in three different spellings. OVER clauses and window frames are where symbol scope, operator precedence and parser performance all go wrong at once, and three separate releases of this package are about exactly that. And Snowflake streams, STREAM tablename, put a change feed where a table belongs.

One query that only this engine takes

We license five SQL-family engines. This is a lakehouse query, and it is the exact inverse of the Oracle and Teradata examples on the sibling pages: they are estate code, this is what an analyst types into a notebook.

Input — analytic query across two warehouse idioms

rolling_spend.sql

WITH ranked AS (
    SELECT * EXCEPT (internal_id)
    FROM   analytics.events AT (OFFSET => -300)
)
SELECT   r.user_id,
         SUM(r.amount) OVER (PARTITION BY r.user_id ORDER BY r.ts
                             ROWS BETWEEN 3 PRECEDING AND CURRENT ROW) AS rolling
FROM     ranked r
LATERAL VIEW OUTER explode(r.tags) t AS tag
QUALIFY  rolling > 100
MINUS
SELECT   user_id, 0 FROM analytics.excluded;
Every marked element maps to a rule in the shipped grammar: column exclusion, the time-travel clause, the window frame, the lateral view, QUALIFY as a query clause and MINUS as a set operator. It is deliberately not legal on any single warehouse — the permissive grammar accepts the union, which is what an editor needs. Oracle has none of these constructs; Teradata has QUALIFY and nothing else here; Transact-SQL and Firebird reject all of it.

You do not have to take this on trust. Paste a query of your own into the Strumenta Playground, pick SQL, and read the tree the engine actually produces — including the positioned issues when it does not like something.

WHAT THIS ENGINE IS FOR

Completion, formatting, hover types, error markers.

If you are building a SQL editor into a data product, this is the list of features your users will judge you on — and the list you were about to spend three quarters writing an ANTLR TypeScript grammar and a symbol resolver for.

Completion is designed in rather than bolted on: a completion-oriented lexer ships in the package, the partial-input entry points exist for it, and one release is specifically about improving the grammar for completion in contexts where the syntax is invalid because the user is still typing.

Formatting is a registrable, per-node-type formatter with configurable indentation, walking the tree the parser produced. It is the closest thing to code generation this product has, and it is real — it is also the honest ceiling: this engine formats SQL, it does not translate it.

Symbols and types are the same layer the other engines have, in TypeScript: a database model you supply, referenced columns that resolve with their relation and positioned referents, getType on any node, and a TabularType giving the row shape and per-column types of a SELECT. That is a hover tooltip, and it is also column-level lineage for a data catalog.

import { parse, Dialect, format }
  from "@strumenta/sql-parser";

const result = parse(sql, { dialect: Dialect.SNOWFLAKE });
result.issues.forEach(i => mark(i.position, i.message));

const cols = result.root.getReferencedColumns(ctx);
cols[0].referents[0].position;   // lineage
result.root.getType(ctx);        // TabularType

editor.setValue(format(result.root));

One import, one dialect flag, and the four services behind it. The package ships type definitions, so the editor you are building gets the same completion you are trying to give your users.

257
TESTS THAT GATE EVERY PUBLISHED BUILD
8
DIALECTS BEHIND ONE FLAG
4
ENTRY POINTS, TWO FOR PARTIAL INPUT
716
LINES OF GRAMMAR — THE CHEAP PART

Buy this, or buy a dialect engine

These five products get confused with each other, and it costs everybody a wasted proof of concept. So here is the routing table, written to send you away when that is the right answer.

What you are trying to do, and which of the five engines does it. The right-hand column is a link, because sending you to the correct product is worth more than a demo of the wrong one.
What you need This engine Where to go instead
A SQL editor, notebook or web IDE with completion, formatting and hover types — in JavaScript, in the browser or in Node This is the product
One component covering several cloud warehouses behind a flag This is the product
“Which columns and tables does this query touch?”, given a schema you already have Supported — referenced columns with positioned referents
DDL — CREATE TABLE and dialect-specific physical design Not supported at all Teradata SQL, Firebird or Oracle PL/SQL
Procedural code — packages, stored procedures, triggers No procedural language of any kind Oracle PL/SQL, or Teradata for SPL
Regenerating source code after transforming the tree A formatter, not a code generator Oracle PL/SQL — the only engine with a printer
Running on the JVM or on .NET, with no Node in the build Node package; there is no JVM or .NET build Transact-SQL for both, Firebird for .NET
Oracle, Teradata, Firebird or Db2 as a dialect Not modeled The catalog — each is its own engine
Sizing a migration by classifying which queries parse against a target dialect Yes — it sizes the work, it does not perform it Teradata SQL for the Spark SQL transpiler

What you receive

An npm package, its type definitions, its generated API documentation and its changelog — and a person to write to. It runs in your build and in your users’ browsers, with no connection back to us and none to any warehouse.

01 The package

A TypeScript library, from a private registry

Built on Tylasu — the TypeScript implementation of Starlasu — with the ANTLR TypeScript runtime. Types, ES modules and CommonJS entry points all ship. Nothing in the parser path needs Node built-ins, so the same build runs in a browser tab.

npm · Node · browser · typed
02 The model

An AST, a database model, symbols and types

A Tylasu AST rooted at a script node, with the parse tree still reachable, positions on every node and a positioned issue list. Then the semantic layer: a database model you populate, resolved columns with referents, and expression typing including the row shape of a SELECT. It supports LionWeb, and it interoperates with Ecore.

AST · symbols · types · LionWeb
03 The record

A dated changelog, and a suite that gates the release

Generated API documentation, and a changelog with dated entries running from the first beta to the current version — window-function handling, symbol resolution inside OVER and ORDER BY, BigQuery identifier casing, completion in invalid syntax. The publish pipeline runs the 257 tests before it packs, so a broken build is not a released build.

changelog · API docs · gated releases
04 The license

Standard, Distribution or Service — with support included

Standard for use inside your own organization, Distribution if the library ships inside a product you sell — which for an embedded editor is the usual case — and Service if it runs behind a service you operate. Support is part of the license. The guarantee runs a year and is extendable, or can be taken pay-as-you-go.

three tiers · support included
Onboarding

The first day is an install and a parse, because it is an npm package and your editor already exists.

The work that takes real thought is supplying the schema so that completion and hover types have something to resolve against — and deciding which dialects must be right on day one. Both are conversations we would rather have before you license. Write to products@strumenta.com.

THE REST OF THE CATALOG

The editor is one end of a longer pipeline.

The queries your users write in your product are new. The queries they are migrating away from were written in Teradata or Oracle, often generated by a COBOL or RPG batch that nobody has opened in a decade — and those are a different engine and a different job.

This is the one place in our SQL catalog where the family argument is a routing argument rather than a migration one. A product that lets a customer write Snowflake queries today will eventually be asked to import their old Teradata ones, and that import is not an editor feature.

What makes the handover work is that every Strumenta engine is built on Starlasu. A Teradata tree, a COBOL tree and this engine’s tree have the same shape, the same traversal model and the same API, so the analysis that classifies a legacy corpus and the editor that writes the new queries are looking at one kind of object. Five unrelated open-source parsers give you five models to reconcile first.

Every engine also supports LionWeb, so the models interchange with LionWeb-compliant tooling instead of being locked inside one process — and every engine is available through bindings from Java, Kotlin, Python, TypeScript and C#. This component ships as a Node package; what can consume the model it produces is the wider list.

Engines in the same pipeline

one model, one traversal

Teradata SQL engine →
The warehouse your users are leaving.

Transact-SQL engine →
The same job on the JVM or .NET.

COBOL engine →
Where a surprising number of those queries were generated.

The whole catalog →
15+ engines, one AST framework underneath.

Why license this rather than assemble public grammars

The free route is real and worth naming: antlr/grammars-v4 carries a grammar per SQL dialect, maintained by people who deserve the credit. Assembling them into this product is where the difficulty starts.

None of them targets TypeScript out of the box, and none of them agrees with the others on an AST — which is precisely the thing a multi-dialect editor needs. You would be writing a normalization layer per dialect before you had a single feature.

Then comes everything a grammar does not contain: a database model, symbol resolution with positions, expression typing including the row shape of a SELECT, per-dialect built-in function catalogs, a pluggable formatter, a completion-oriented lexer, and entry points that accept half-written input. That list is the multi-quarter project. The grammar is the cheap part.

And there is the maintenance argument, which only shows up in year two. A dated changelog running from the first beta to today, and a publish pipeline that will not pack a build unless 257 tests pass, is not glamorous. It is the difference between a dependency and a liability.

None of that makes the public grammars a bad choice for a prototype. It makes them a bad choice for a feature your customers will file bugs against.

The method behind this work has a name. Read the Chisel Method for how we make parser work estimable, or Parsers and transpilers for the three ways into it.

What to discuss before you license

Six things we would rather tell you now than have you find in week three.

  • SELECT, INSERT and UPDATE. That is the whole statement list. No DELETE, no MERGE, no DDL, no DCL, no transaction control and no procedural language. For an editor over analytic queries that is the right scope. For anything that has to read a schema definition it is fatal, and you should be on a different page.
  • It is not an implementation of the SQL standard. No conformance claim is possible and none is made. It models eight cloud warehouse dialects, and it will reject the Oracle, Teradata and Firebird examples on our sibling pages.
  • The parser is deliberately permissive. It accepts a superset across dialects — QUALIFY and MINUS are admitted whatever the flag says — so a query that parses here is not thereby legal on your warehouse. That is correct behavior for completion and wrong behavior for validation. If you need a validator, we should talk about what it would take.
  • Dialect depth is uneven. Databricks is the most heavily exercised, then BigQuery; MS Fabric, Redshift and Snowflake have thin dialect-specific suites. Tell us which dialects have to be right on day one and we will be specific about where you are and are not on tested ground.
  • There is no JVM and no .NET build of this package. It is a Node and browser library. Separately, its output is consumable through the Starlasu bindings and LionWeb — but if you need the parser itself in a JVM process, look at the Transact-SQL engine instead.
  • Two things stop static analysis cold. Without a database model, SELECT * cannot be expanded, unqualified columns cannot be attributed and types come back unknown. And templated SQL — Jinja in dbt, ${var} substitution in Databricks — is not SQL and is not parsed. Also worth saying plainly: the command-line script bundled in the source is an inspection tool, not a supported deliverable.

What we will ask you

  1. Which warehouses do your users query, and which of them must work on day one?
  2. Editor and IDE features, or batch analysis? The answer changes the product, not the price.
  3. Node, browser, or both?
  4. Do you need DDL or procedural SQL? If yes, we will route you to a different engine on the first call.
  5. Can you supply a schema at run time, and in what form?
  6. Do your queries pass through a templating layer before they reach the editor?
  7. Do you need to rewrite SQL, or only read and format it?
  8. Does the library ship inside a product you sell? That is a Distribution license question.

Straight answers

Does it parse any SQL dialect?

No, and we would rather lose the search term than the trust. It implements eight named dialect settings — Generic, Snowflake, Databricks, MSSQL, MS Fabric, BigQuery, PostgreSQL and Redshift, with Redshift mapping to the PostgreSQL dialect — over SELECT, INSERT and UPDATE. Oracle, Teradata, Firebird and Db2 are separate engines. There is no ISO SQL conformance claim here.

Can it tell us whether a query is valid on our warehouse?

Not reliably, and this is the most important honest answer on the page. The grammar accepts a superset across dialects, so QUALIFY and MINUS parse whatever flag you pass. That is deliberate: an editor that underlines valid Snowflake because the parser is strict is worse than one that underlines nothing. If you need a per-dialect validator rather than an editor engine, tell us and we will scope it as work.

Does it rewrite or translate SQL?

It formats. There is a real, registrable, per-node-type formatter with configurable indentation, and that is the ceiling — it prints the same query, laid out properly. It is not a transpiler. Of the five SQL engines we license, only the Oracle PL/SQL one writes its language back out from a modified tree, and the only shipped SQL-to-SQL translation we have is Teradata to Spark SQL, on a different engine.

Does it work in the browser?

Yes. It is a TypeScript package with no Node built-ins in the parser path, so it bundles into a web application and runs client-side. That matters for an embedded editor: completion and error markers on every keystroke are not something you want to make a network round trip for.

How do completion and hover types know our schema?

You supply a database model at run time, and the per-dialect modules ship models and built-in function catalogs to start from. With it, SELECT * expands, columns resolve to a relation with positioned referents, and expressions have types — including a tabular type describing the row shape of a query. Without it the parse still succeeds and everything simply resolves to unknown.

Send us fifty saved queries and tell us which warehouse

Bring us the queries your users actually write.

A sample from your product, and the dialects that matter most. We will come back with what parses, what resolves against a schema, where the dialect coverage is thin for your particular mix — and, if the honest answer is that you need a dialect engine or a DDL parser instead, which one and why. Routing you correctly is cheaper for both of us than a proof of concept that ends badly.

Scroll to Top