sqlglot

package module
v0.20.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 24, 2026 License: MIT Imports: 8 Imported by: 0

README

sqlglot-go

CI Go Reference Go Report Card Release

A Go port of the parse → AST → generate core of tobymao/sqlglot v30.12.0, plus the qualify and scope optimizer passes that column qualification and lineage are built on. Ported file-by-file from the pinned Python source, as close to 1:1 as Go allows, so upstream tests port across directly. Zero third-party dependencies (Go stdlib only).

Scope — what this is (and isn't)

This is not a port of all of sqlglot. It ports the front half of the pipeline in full and a targeted slice of the optimizer:

Ported (faithful, ~1:1):

  • Tokenizer, AST/node model, parser, generator — the full round-trip. Anything upstream sqlglot parses and regenerates for base + MySQL + Postgres, this port does identically: 100% round-trip parity — 1847/1847 corpus cases (base 955/955, MySQL 424/424, Postgres 468/468), checked against the ported upstream test suite. No statement fakes a round-trip via a raw-text fallback where upstream builds a structured node (guarded by a committed fidelity test).
  • SchemaMappingSchema, DataType.build, type-category sets.
  • qualify optimizer pass — qualify_tables → normalize_identifiers → qualify_columns → quote_identifiers → validate.
  • scope / traverse_scope + the full Scope API — sources, columns, unions, CTEs, subqueries: the foundation for column lineage.

Not ported (yet):

  • The rest of sqlglot's optimizer: simplify (full), normalize, pushdown_predicates, pushdown_projections, eliminate_ctes / eliminate_joins / eliminate_subqueries, merge_subqueries, unnest_subqueries, optimize_joins, canonicalize, and the top-level optimize() rule orchestrator. (annotate_types and simplify are only partially present.)
  • Cross-dialect transpilation — the generator can target a dialect, but only same-dialect round-trip is a goal and is tested; reading one dialect and writing another is not verified.
  • Dialects beyond base / MySQL / Postgres (upstream ships 30+).

Beyond upstream — opt-in, additive (default output unchanged):

  • Search-path table qualificationQualifyOpts.SearchPath resolves an unqualified table against an ordered schema list by proven existence (fail-closed).
  • Top-level UPDATE/DELETE/MERGE scopesTraverseScope/BuildScope bind DML targets and their FROM/USING/JOIN sources for analysis (fail-closed; upstream yields none).
  • Qualify resolution reportQualifyOpts.ResolutionReport surfaces each source's SourceKind (Physical / CTE / Derived / Subquery / Unresolved) + identity.
  • MySQL version/executable commentsmysql_version=<MYSQL_VERSION_ID> activates /*!… */ bodies into the token stream; default-off strips them as upstream does.

See CHANGELOG.md for the per-version history, ROADMAP.md for the remaining work and resolved-findings ledger, and DEVIATIONS.md for every place the port intentionally behaves differently from upstream sqlglot (headline: ASCII-only identifier case-folding, to match real engines).

What works today

Capability Package Notes
Tokenize tokens base + MySQL + Postgres tokenizers
Parse → AST parser, expressions full: SELECT/set-ops/CTE/subqueries, all query clauses, predicates, functions, DML + DDL (INSERT/UPDATE/DELETE/MERGE/CREATE + properties), CAST/DataType, PIVOT/LATERAL/VALUES/TABLESAMPLE, INTERVAL, JSON/JSONB ops, window functions
Generate (AST → SQL) generator 100% round-trip on the upstream identity corpus for base/MySQL/Postgres (1847/1847)
Schema schema MappingSchema, DataType.build, type-category sets
Qualify optimizer qualify (qualify_tables, normalize_identifiers, qualify_columns, quote_identifiers, validate)
Scope / lineage optimizer traverse_scope + full Scope API (sources, columns, unions, CTEs)
Dialects dialects MySQL + Postgres (tokenizer, normalization, quoting, per-dialect functions/flags)

Quick start

go get github.com/ridi-oss/sqlglot-go
package main

import (
	"fmt"

	sqlglot "github.com/ridi-oss/sqlglot-go"
	"github.com/ridi-oss/sqlglot-go/generator"
	"github.com/ridi-oss/sqlglot-go/optimizer"
	"github.com/ridi-oss/sqlglot-go/schema"
)

func main() {
	// Parse
	expr, _ := sqlglot.ParseOne("SELECT id, name FROM users WHERE id = 1", "postgres")

	// Qualify against a schema (bind columns to sources, expand *, validate)
	sch := schema.M("users", schema.M("id", "INT", "name", "TEXT"))
	opts := optimizer.DefaultQualifyOpts()
	opts.Schema = sch
	opts.Dialect = "postgres"
	qualified := optimizer.Qualify(expr, opts)

	// Generate SQL back out
	sql, _ := sqlglot.Generate(qualified, "postgres", generator.Options{})
	fmt.Println(sql)
	// SELECT "users"."id" AS "id", "users"."name" AS "name" FROM "users" AS "users" WHERE "users"."id" = 1

	// Walk scopes (sources, columns, unions, …) for lineage
	for _, s := range optimizer.TraverseScope(qualified) {
		fmt.Printf("scope: %d columns, union=%v\n", len(s.Columns()), s.IsUnion())
	}
}

Development

go test ./...          # green
gofmt -l . && go vet ./...

The upstream test suite is ported alongside the code (*_test.go + testdata/*.sql fixtures reused verbatim) and is the correctness oracle. A round-trip parity corpus and an AST-fidelity gate keep the port honest — both are monotonic (they can only tighten). For a live differential check against the pinned Python source, scripts/fetch-reference.sh fetches sqlglot 30.12.0 into .reference/, then e.g. PYTHONPATH=.reference/sqlglot-v30.12.0 python3 -c "import sqlglot; print(sqlglot.parse_one('…','postgres').sql())".

Continuing the port

Read AGENTS.md and ROADMAP.md. The reference Python source (.reference/, fetched via scripts/fetch-reference.sh) is the source of truth — port from it 1:1, port the matching upstream tests, keep go test ./... green. ROADMAP.md records the remaining slices, every known divergence, and resolved-findings so settled decisions aren't re-litigated.

License & attribution

MIT — see LICENSE. This is a derivative work: a Go translation of tobymao/sqlglot (© Toby Mao, MIT). The upstream MIT license is preserved. sqlglot-go is not affiliated with or endorsed by the upstream project.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Generate

Generate renders e to SQL under dialect (see dialects.DialectType for the accepted forms).

func Parse

func Parse(sql string, dialect dialects.DialectType) ([]expressions.Expression, error)

Parse tokenizes and parses sql under dialect (see dialects.DialectType for the accepted forms: nil | string | *dialects.Dialect), returning one Expression per statement.

func ParseInto

func ParseInto(sql string, dialect dialects.DialectType, into expressions.Kind) (expressions.Expression, error)

func ParseOne

func ParseOne(sql string, dialect dialects.DialectType) (expressions.Expression, error)

ParseOne parses a single statement from sql under dialect (see dialects.DialectType).

func Tokenize

func Tokenize(sql string, dialect dialects.DialectType) ([]tokens.Token, error)

Tokenize lexes sql under dialect into a token stream. It is a pure lexer, independent of the parser, so it tokenizes input the parser would reject — and it never silently truncates: an unterminated string, quoted identifier, block comment, or Postgres dollar-quote returns a non-nil error, which a consumer should treat as fail-closed. Ordinary comments are attached to a token's Comments (not emitted as stream tokens). For the byte-exact source lexeme of a token, slice the source by its Start/End offsets rather than reading Token.Text (see tokens.Token).

func Transpile

func Transpile(sql string, read dialects.DialectType, write dialects.DialectType, opts generator.Options) ([]string, error)

Transpile parses sql under the read dialect and re-renders each statement under the write dialect. read and write are each a dialects.DialectType (nil | string | *dialects.Dialect).

Types

This section is empty.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL