Skip to content

About

Javascript port of sqlglot

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

sqlglot-js

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.

Install

npm install github:airbrx/sqlglot-js#main

Or, 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().

Quick start

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.

Dialects (33 of 33 upstream modules)

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.

The exported surface, at a glance

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

Status, measured

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.

Known gaps

Specific, current, and linked from docs/api.md#known-gaps:

  • The one CURRENT_ROLE row 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.json is "private": true; consume via a git dependency or ./index.js from a repo checkout. No semver guarantee yet — see docs/stability.md.

How verification works

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.

Stability

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.

contrib/

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.

Zero runtime dependencies

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.

License

MIT, same as upstream sqlglot. See LICENSE, LICENSE-sqlglot, and NOTICE for full attribution.

About

Javascript port of sqlglot

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages