A zero-dependency JavaScript port of sqlglot — a no-dependency SQL parser, transpiler, and AST toolkit.
This is a derivative work of sqlglot (MIT License, Copyright (c) 2026 Toby Mao). See
NOTICE and LICENSE-sqlglot for attribution, and
UPSTREAM.txt for the current upstream pin
(91119bcaac977ede6f4a641bdda593b0015ef998).
What this is, concretely. Every file under src/ is a line-by-line transliteration of
the matching upstream Python file — same layout, same class/method names and order, same
control flow — with a // py: sqlglot/<file>.py:<line> anchor on every ported method. The
goal is that a future upstream diff can be read directly against the JS file. The test corpus
(corpus/) is harvested verbatim from upstream's own tests/dialects/*.py suite, so the port
is checked against the tests sqlglot already trusts, not a hand-written approximation of
them. PORT_PLAN.md is the full plan, phase history, and every measured number this README
summarizes; CONTRACTS.md records every place a literal transliteration was impossible and
why; CHANGELOG.md is a one-line-per-round index into PORT_PLAN.md,
generated by tools/changelog_from_port_plan.mjs.
Status: every one of upstream's 33 dialect modules at the pin now exists in the port
(src/dialects/*.js), each with its own Tokenizer/Parser/Generator and differentially
tested settings. Not published to any registry yet — consume it as a git dependency.
npm install github:airbrx/sqlglot-js#mainOr, from a checkout of this repo, import ./index.js directly. This package is ESM-only
("type": "module"); see docs/consuming-from-cjs.md for using it
from a CommonJS project via dynamic import().
import { parseOne, transpile, optimize, MappingSchema, exp } from "sqlglot-js";
// Parse
const ast = parseOne("SELECT id, name FROM users WHERE active = TRUE", { read: "snowflake" });
ast.constructor.name; // "Select"
// Transpile across dialects
const [sql] = transpile("SELECT `id` FROM t WHERE active = TRUE", {
read: "databricks",
write: "postgres",
});
sql; // 'SELECT "id" FROM t WHERE active = TRUE'
// Optimize (qualifies columns, expands `*`, folds constants — against a schema)
const schema = new MappingSchema({ users: { id: "INT", email: "VARCHAR" } });
optimize("SELECT * FROM users WHERE 1 = 1 AND id = 1", { schema }).sql();
// 'SELECT "users"."id" AS "id", "users"."email" AS "email" FROM "users" AS "users" WHERE "users"."id" = 1'
// Build a tree directly, without SQL text
exp.select("id", "name").from_("users").where(exp.column("active").eq(true)).sql();
// "SELECT id, name FROM users WHERE active = TRUE"All four outputs above are copy-pasted from a real run of this tree (node -e '...' at the
repo root). See docs/getting-started.md for a walkthrough and
docs/api.md for the full reference.
| Family | Dialects |
|---|---|
| Databricks chain | hive, spark2, spark, databricks |
| Snowflake | snowflake |
| DuckDB | duckdb |
| Postgres family | postgres, redshift |
| BigQuery | bigquery |
| TSQL | tsql |
| MySQL family | mysql, singlestore, starrocks, doris |
| Presto family | presto, trino, athena, dune |
| ClickHouse | clickhouse |
| Exasol | exasol |
| Oracle | oracle |
| SQLite / Teradata | sqlite, teradata |
| Small / standalone | materialize, risingwave, fabric, solr, druid, dax, tableau, drill, prql, dremio |
A family's later members subclass an earlier one the same way upstream does (e.g. Spark
subclasses Hive, SingleStore/StarRocks/Doris subclass MySQL) — see PORT_PLAN.md's
Epic 12 entries (R101–R114) for the landing order and per-dialect notes. Two dialects have no
bare SELECT grammar at all, faithfully (upstream raises the same error): dax only parses
EVALUATE ..., and prql is a pipeline language (from x | ...), not SQL.
Everything below is importable from the package root (import { ... } from "sqlglot-js").
Full detail, including JS-specific shape notes, is in docs/api.md.
| Group | Exports |
|---|---|
| Parsing / transpiling | parse, parseOne, transpile, tokenize, Dialect |
| Expression tree | exp (the namespace — 1,048 classes plus builders like select, column, cast) |
| Errors | ParseError, TokenError, UnsupportedError, ErrorLevel |
| Schema | Schema, MappingSchema |
| Optimizer | optimize, RULES |
| Lineage | lineage, Node |
| Anonymize | anonymize, render |
| Diff | diff, Insert, Remove, Move, Update, Keep |
Every number below comes from a command run on this tree at 65c9054 (R117) — see
PORT_PLAN.md's R115–R117 entries for how each was produced, and
docs/api.md for the honest list of what's still open.
| Check | Command | Result |
|---|---|---|
| Whole-corpus parse | node spike/p3/fuzz_ast_coverage.mjs |
15,539 PASS / 1 MISMATCH / 0 STUB / 0 ERROR of 15,540 |
| Whole-corpus generate | node spike/p5/fuzz_dialect_generate.mjs |
15,540 PASS / 0 MISMATCH / 0 STUB / 0 ERROR of 15,540 — full parity with the pin |
| End-to-end ratchet | node test/runner.mjs |
15,539 pass-listed |
| v0 scorecard | node test/runner.mjs --v0 |
6,521 pass-listed of 6,522 |
| Honest generate oracle | node spike/p4/fuzz_generate_oracle.mjs |
0 MISMATCH / 0 STUB / 0 ERROR (green since R100) |
| Honest AST oracle | node spike/p3/fuzz_ast_coverage.mjs --strict |
1 MISMATCH / 0 ERROR (the one standing row, see below) |
| Dialect-defaults settings | node spike/p5/fuzz_dialect_defaults.mjs |
428/428, with 2 named GAP notes (see api.md) |
| Native test suite | npm test |
768/768 |
| Lints (6) | node tools/lint_*.mjs |
all clean |
The one standing row, on every axis above, is the deliberate CURRENT_ROLE divergence —
see docs/stability.md. It is
not a defect: Ben decided (2026-10-16, Linear AIR-1864) to keep this port's typed
exp.CurrentRole node rather than matching the pin's untyped Column, because
contrib/gatewaySqlMetadata.js's non-determinism classifier depends on it. spike/run_all.sh
accordingly carries one documented red (spike/p3's full-population, no-exclusions
diagnostic) rather than an exemption — the production ratchets above (what a real
read/write call actually does today) are the release-relevant numbers, and both hold
zero regressions on every merge.
Specific, current, and linked from docs/api.md#known-gaps:
- The one
CURRENT_ROLErow named above — accepted, not a gap. - 76% of the 3,589 optimizer fixture pairs have no replaying differential oracle in this repo
(measured by
tools/fixture_rollup.mjs, R95/AIR-1864) — the optimizer's rule-level logic is independently tested, but that specific fixture corpus isn't replayed end-to-end. - Not published to any registry —
package.jsonis"private": true; consume via a git dependency or./index.jsfrom a repo checkout. No semver guarantee yet — see docs/stability.md.
Two independent ratchets protect every merge: a row ratchet (per-atom parse/generate
pass IDs, test/differential-{parse,generate}-baseline.json) and an end-to-end ratchet
(test/ratchet.json, driven by test/runner.mjs) — a PR cannot delete an accepted pass ID or
change an accepted expectation from either (tools/check_ratchet_change.mjs,
tools/check_differential_change.mjs). On top of the corpus ratchets, a set of honest
oracles (spike/p3–spike/p10) test the real production entry points against the full,
unfiltered corpus with no exclusions — these are allowed to be red (and one still is, see
above) precisely because they can't be gamed by narrowing scope. See
docs/verification/ for the detailed gate history and
PORT_PLAN.md §3 for the verification architecture.
Not published to any registry yet, and no semver guarantee before 1.0 — see
docs/stability.md for what's public vs internal, what "parity" means,
the one accepted divergence (CURRENT_ROLE), and the no-promise areas.
Airbrx-specific application code built on top of the port, not part of it (no py: anchors,
not tracked by PORT_PLAN.md's closure numbers). See contrib/README.md
— currently gatewaySqlMetadata.js, a Dialect.get_or_raise(...).parse(sql)-based cache-key
metadata extractor for airbrx-gateway's regex-based SqlParser.js. See
docs/proposals/gateway-tier2-normalization.md
for the (undecided, awaiting Ben) tier-2 semantic-normalization proposal built on top of it.
Enforced by CI lint (tools/lint_license.mjs and the empty dependencies/devDependencies
in package.json). Node ≥ 20 and modern browsers, ESM only. Build-time verification tooling
(the differential oracles) depends on a pinned Python sqlglot install; the runtime library
itself does not.
MIT, same as upstream sqlglot. See LICENSE, LICENSE-sqlglot, and NOTICE for full attribution.