polyglot: a Rust and Wasm SQL transpiler for many dialects
Rust/Wasm-powered SQL transpiler for more than 30 SQL dialects
At a glance
- What is it?
- polyglot parses SQL into a typed AST and regenerates it for more than 30 dialects, shipping as a Rust crate, a TypeScript and WebAssembly SDK, a Python package and a Go binding over a native FFI library. It is a good fit for tools that need dialect portability without a Python runtime, and the Go path requires you to supply the shared library yourself.
- Who is it for?
- Adopt polyglot when you need dialect-portable SQL inside a Rust service, a browser bundle or a Go binary, and want lineage and query analysis on the same AST you transpile, and skip it when a single dialect is all you need, because a hand written mapping is often less work than the dependency.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One SQL engine with four language surfaces
polyglot is a SQL transpiler written in Rust and compiled to WebAssembly, described in the README as inspired by sqlglot. The core library is the Rust crate polyglot-sql on crates.io, and everything else is a binding over the same engine.
The TypeScript SDK, published as @polyglot-sql/sdk on npm, runs the engine through WebAssembly, which is what makes the browser playground possible. The Python package, also named polyglot-sql on PyPI, uses PyO3 for its bindings. The Go SDK lives at github.com/tobilg/polyglot/packages/go and is backed by the native FFI library rather than by a pure Go implementation.
That structure is the reason to care. A tool that needs to move SQL between dialects usually ends up with a Python dependency somewhere in the chain, which is awkward inside a browser bundle or a Go binary. Here the dialect logic lives in Rust once, and the question for an adopter is only which surface your language can load.
The workspace Cargo.toml shows the internal shape, with separate crates for the core library, an AST derive crate, function catalogs, the WebAssembly target, the FFI target and the Python bindings. Release notes are tracked in CHANGELOG.md, and the repository also carries a licenses/ directory, a scripts/ directory and a tools/ directory alongside the crates and packages folders.
The typed AST is the centre of the design
The feature list makes clear that polyglot is not only a string replacement tool. It parses SQL into a fully typed AST, generates SQL back from AST nodes, transpiles between dialects, formats and pretty-prints, and offers a fluent builder API for constructing queries programmatically. On top of that sit validation with syntax, semantic and schema-aware checks, column lineage with OpenLineage-compatible payload generation, compact query analysis, and AST visitor utilities for walking, transforming and analysing queries.
The AST is what makes the rest possible. Transpilation between two dialects is a parse into the AST and then a generation pass in the target dialect, and that is why the same engine can also emit lineage, since lineage is a question about the AST rather than about the text.
It also explains the existence of the function catalogs crate and the AST derive crate. Dialects differ not only in syntax but in which functions exist, so a catalog of functions per dialect is needed to translate a call that exists in MySQL into the equivalent that exists in Postgres.
One more detail is worth noting for native deployments. The README mentions stack-safety hardening on native targets through a default-on stacker setting, which is a mitigation for stack exhaustion when parsing deeply nested queries in a native build rather than under WebAssembly.
Thirty-plus dialects, from BigQuery to SAP HANA
The supported dialect table is the clearest statement of scope in the README. The named dialects are Athena, BigQuery, ClickHouse, CockroachDB, Databricks, Doris, Dremio, Drill, Druid, DuckDB, Dune, Exasol, Fabric, Hive, Materialize, MySQL, Oracle, PostgreSQL, Presto, Redshift, RisingWave, SingleStore, Snowflake, Solr, Spark, SQLite, StarRocks, Tableau, Teradata, TiDB, Trino, TSQL, Vertica, DataFusion and SAP HANA, plus a Generic SQL dialect.
The list spans three quite different worlds. The cloud warehouses, BigQuery, Snowflake, Redshift and Databricks, have their own quirks around quoting and type names. The query engines, ClickHouse, Trino, Presto, Doris, StarRocks and DuckDB, differ in function libraries and in how they handle lateral joins and array types. The operational databases, MySQL, Postgres, Oracle, TSQL and SQLite, have their own identifier and escaping rules.
Being listed is not the same as being equally supported, and the README does not document per-dialect coverage. The Makefile gives a hint about how that is handled in practice. It exposes a large set of test targets, including test-rust-dialect, test-rust-matrix, test-rust-compat, test-rust-functions and separate targets for the ClickHouse parser and its coverage, and there are comparison targets against sqlglot, which is the project this one is modelled on.
So dialect coverage is a tested surface, but the documentation a user reads will not tell you which functions are known for which engine.
Installing through npm and PyPI, and calling the transpiler
The JavaScript SDK installs from npm:
npm install @polyglot-sql/sdkAnd the call shape is the same in every language: a source dialect, a target dialect, and a list of statements back.
import { transpile, Dialect } from '@polyglot-sql/sdk';
// Transpile MySQL to PostgreSQL
const result = transpile(
'SELECT IFNULL(a, b) FROM t',
Dialect.MySQL,
Dialect.PostgreSQL,
);
console.log(result.sql[0]); // SELECT COALESCE(a, b) FROM tThe Python package installs from PyPI:
pip install polyglot-sqland uses keyword arguments for the dialects:
import polyglot_sql
result = polyglot_sql.transpile(
"SELECT IFNULL(a, b) FROM t",
read="mysql",
write="postgres",
)
print(result[0]) # SELECT COALESCE(a, b) FROM tThe Rust crate exposes the same operation with the DialectType enum:
use polyglot_sql::{transpile, DialectType};
// Transpile MySQL to PostgreSQL
let result = transpile(
"SELECT IFNULL(a, b) FROM t",
DialectType::MySQL,
DialectType::Postgres,
).unwrap();
assert_eq!(result[0], "SELECT COALESCE(a, b) FROM t");The Rust form returns a Result and has to be unwrapped, which is the one API difference between the surfaces. The README points to the per-package READMEs for more examples, including the fluent builder API, which builds a select from id and name over users with a where clause on age and a limit.
The Go binding is PureGo over a shared library you must provide
The Go SDK is the one installation with a catch, and the README is explicit about it. The module can be fetched with:
go get github.com/tobilg/polyglot/packages/goBut the module contains the PureGo wrapper only. Runtime API calls require a separate matching polyglot-sql-ffi shared library, as a .so, .dylib or .dll, taken from the same Polyglot release or from a local FFI build. The SDK does not download native libraries and does not bundle release artefacts in the Go module.
To produce that library locally, the FFI crate is built with a dedicated cargo profile:
cargo build -p polyglot-sql-ffi --profile ffi_release
export POLYGLOT_SQL_FFI_PATH="$PWD/target/ffi_release/libpolyglot_sql_ffi.so"And the Go side opens a client and transpiles:
import (
"fmt"
polyglot "github.com/tobilg/polyglot/packages/go"
)
client, err := polyglot.OpenDefault()
if err != nil {
panic(err)
}
defer client.Close()
result, err := client.Transpile(
"SELECT IFNULL(a, b) FROM t",
"mysql",
"postgres",
)
if err != nil {
panic(err)
}
fmt.Println(result[0]) // SELECT COALESCE(a, b) FROM tThe path can also be passed directly to polyglot.Open instead of going through the environment variable.
The practical consequence is a deployment concern that the other three languages do not have. A Go binary needs a shared library on the target machine, matched to the release, and the version match between the module and the library is on the operator.
OpenLineage payloads are produced, not transmitted
polyglot can trace column lineage through SQL queries and generate OpenLineage-compatible JSON payloads from that analysis. The README states the current scope precisely: columnLineage dataset facets, optional schema facets, and JobEvent and RunEvent payloads for supported query shapes such as SELECT, set-operation queries, INSERT ... SELECT and CREATE TABLE AS SELECT.
The details are the interesting part, because they show a considered model of what lineage means in SQL. Lineage can be selected by output name or by zero-based output ordinal, and ordered output metadata preserves unnamed projections and unresolved wildcards so a caller can tell whether a positional lookup is complete before trusting it. For set operations, the branch roots carry the operator, the original branch ordinal and the ALL flag, and the dependency semantics differ by operator: for OpenLineage both UNION branches are direct value dependencies, while the right branch of EXCEPT and INTERSECT is an indirect FILTER dependency.
That level of detail matters when lineage feeds a catalogue, because a naive implementation would call the right side of an INTERSECT a direct dependency and misdescribe what the query does.
One boundary is stated explicitly. OpenLineage transport and client emission are out of scope. The library builds the payloads and stops there, leaving you to inspect, persist or send them through your own infrastructure. That is a deliberate division of labour rather than a gap, and it is a fair one, since emission depends on where your metadata bus lives.
Compact query analysis for callers that do not want a full AST
Not every consumer wants a parse tree or a lineage graph, so polyglot exposes a middle ground with analyze_query in Rust and analyzeQuery elsewhere. The returned payload is a compact summary: output projections, directly visible relations, transitive physical baseTables, CTE names and top-level cteFacts, the original starProjections, set-operation branches, transform kinds, conservative projection nullability, optional type hints and upstream column references.
The API is additive and uses the same optional ValidationSchema shape as the schema-aware validation and lineage features, so a caller who already supplies a schema for validation can reuse it. Set-operation branches are classified as a value or a filter contribution, and columnUses groups references separately by how they are used: joins, filters, grouping, HAVING and QUALIFY, window partition, order and frame expressions, ordering, and aggregate filters and ordering.
The conservative nullability is worth flagging. The word conservative means the tool reports what it can prove rather than optimising, so a projection whose nullability it cannot determine is marked conservatively rather than guessed optimistically. For a tool that feeds downstream decisions, that bias is the right way round.
The validation layer fits alongside this. It offers syntax, semantic and schema-aware checks, and the schema-aware variant is the one that needs a ValidationSchema, which is why the same shape appears in all three features.
Release profiles show where the size and speed trade-offs were made
The Cargo workspace configuration is unusually informative about how this library is meant to be deployed, and it is worth reading before you pick a build.
The default release profile optimises for size, with opt-level set to z, link-time optimization enabled, a single codegen unit, panic set to abort so unwinding tables are not linked in, and symbols stripped. That is the profile for the WebAssembly build, where every kilobyte is downloaded to a browser.
Several other profiles inherit from release and change one thing each. A bench profile raises optimisation to 3 while keeping the rest. A native_release profile keeps optimisation at 3 but restores panic unwinding, which is what you want in a long running server where a panic should not kill the process. A python_release profile drops to optimisation level 2 with thin link-time optimization, which is a reasonable trade for an extension module where the import cost matters more than the last slice of speed. The ffi_release profile does the same and also unwinds panics, since an FFI boundary cannot abort the host process.
The build-system dependencies are correspondingly small: serde for serialisation, wasm-bindgen with js-sys and web-sys for the WebAssembly target, thiserror for error types, and unicode-segmentation for text handling.
Releases are frequent. Version 0.13.3 was tagged on 2026-10-09, 0.13.2 on 2026-10-08 and 0.13.1 on 2026-09-30. The version is still below one, which tells you the API surface is still settling.
Where polyglot is the wrong tool
The clearest case for not using it is a single dialect. If your application only ever emits Postgres, a mapping table in your own code is smaller, has no build step and has no FFI to deploy. The whole dialect matrix is wasted weight in that situation, and the transpiler's value comes from the breadth, not from any single mapping.
The second limitation is the Go deployment path, discussed above, and it is a genuine operational cost rather than a footnote. The module, the shared library and the version match are three things to keep in step, and CI images need the library present.
The third limitation is about what the transpiler does not know. A transpiler maps syntax between dialects, so it can rewrite IFNULL as COALESCE, but it cannot know that your target database has a different collation, a different type system, or a different behaviour for empty strings. The README does not promise semantic equivalence, and the validation layer, with its schema-aware checks, is where that limit becomes visible.
The alternative approach for a Python team is the library that inspired this one. sqlglot is pure Python, which means no build step and no WebAssembly boundary, at the cost of not being embeddable in a browser the way this project is. The difference in approach is exactly the runtime: polyglot moves the dialect logic into Rust and exposes it through bindings, sqlglot keeps it in Python and exposes it directly. Choose on where your code has to run.
Editorial conclusion
Adopt polyglot when you need dialect-portable SQL inside a Rust service, a browser bundle or a Go binary, and want lineage and query analysis on the same AST you transpile, and skip it when a single dialect is all you need, because a hand written mapping is often less work than the dependency. Verify three things before you build on it: which dialects you actually need, since the supported list is long but each dialect's coverage is not documented in detail in the README, that the Go module is paired with a matching polyglot-sql-ffi shared library from the same release and that POLYGLOT_SQL_FFI_PATH points at it, and that the OpenLineage output suits you as data only, because transport and client emission are explicitly out of scope. The last push to main was on 2026-10-09 and version 0.13.3 was tagged the same day.
Frequently asked questions
How do I install polyglot for JavaScript or Python?
Install @polyglot-sql/sdk from npm for the TypeScript and WebAssembly SDK, or pip install polyglot-sql for the Python package. The Rust core is published as the polyglot-sql crate on crates.io.
Which SQL dialects does polyglot support?
More than 30 dialects are listed, including BigQuery, Snowflake, ClickHouse, Trino, Presto, DuckDB, MySQL, PostgreSQL, Oracle, TSQL, SQLite, Snowflake, Databricks, StarRocks, DataFusion, SAP HANA and a Generic SQL dialect. The README does not document per-dialect function coverage.
Why does the Go SDK need a shared library?
The Go module contains the PureGo wrapper only, so runtime calls need a matching polyglot-sql-ffi shared library from the same release or a local FFI build. Build it with cargo build -p polyglot-sql-ffi --profile ffi_release and point POLYGLOT_SQL_FFI_PATH at it.
Does polyglot send OpenLineage events anywhere?
No. OpenLineage transport and client emission are intentionally out of scope. Polyglot builds columnLineage facets and JobEvent and RunEvent payloads for you to inspect, persist or send through your own infrastructure.
What licence is polyglot released under?
The workspace Cargo.toml declares the MIT licence for the workspace packages, and the repository carries a LICENSE file plus a licenses/ directory.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/tobilg-polyglot)Was this useful?