Project

sqlglot

0.0
The project is in a healthy, maintained state
A Ruby gem that wraps the sql-glot-rust library via FFI, providing SQL parsing, transpilation between 30+ dialects, and query metadata extraction (tables, columns, aliases, subqueries, CTEs, etc.) for use in Rails applications.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

~> 1.15
 Project Readme

Sqlglot

A Ruby gem that wraps sql-glot-rust via FFI, providing:

  • Parse SQL into a structured AST (Ruby Hash)
  • Transpile SQL between 30 dialects (Postgres, MySQL, BigQuery, Snowflake, T-SQL, ...)
  • Generate SQL from an AST
  • Extract metadata from queries -- tables, columns, aliases, subqueries, CTEs, values, and more (inspired by Python's sql-metadata)

Requirements

  • Ruby >= 3.0
  • Rust toolchain (cargo 1.85+) -- only needed when building from source
  • git -- only needed when building from source

Installation

Add to your Gemfile:

gem "sqlglot"

Then run bundle install. Precompiled native gems are published to rubygems.org for these platforms:

Platform Architecture
Linux (glibc) x86_64, aarch64
macOS x86_64 (Intel), arm64 (Apple Silicon)

On these platforms, no Rust toolchain is needed -- Bundler automatically picks the right precompiled gem.

Building from source

On other platforms (e.g. Alpine/musl), the Rust library is compiled automatically during gem install via extconf.rb. This requires the Rust toolchain (cargo 1.85+) and git.

The github: and path: Gemfile options always build from source, since Bundler bypasses RubyGems platform selection for these sources:

gem "sqlglot", github: "AccountAim/sql-glot-ruby"  # requires Rust
gem "sqlglot", path: "/path/to/this/repo"           # requires Rust

For local development, you can also build manually:

bundle install
rake cargo:build

rake cargo:build clones sql-glot-rust at a pinned tag, runs cargo build --release, and copies the resulting .so/.dylib into lib/sqlglot/. This only needs to be done once (or when upgrading the Rust library version).

If the .so already exists, extconf.rb skips the Rust build automatically.

Usage

Transpile between dialects

Sqlglot.transpile("SELECT NOW()", from: :postgres, to: :bigquery)
# => "SELECT CURRENT_TIMESTAMP()"

Sqlglot.transpile("SELECT * FROM t LIMIT 10", from: :mysql, to: :tsql)
# => "SELECT TOP 10 * FROM t"

Sqlglot.transpile("SELECT NVL(a, b) FROM t", from: :oracle, to: :tsql)
# => "SELECT ISNULL(a, b) FROM t"

Dialect arguments accept symbols, strings, or Sqlglot::Dialect constants:

Sqlglot.transpile(sql, from: :postgres, to: :bigquery)
Sqlglot.transpile(sql, from: "postgres", to: "bigquery")
Sqlglot.transpile(sql, from: Sqlglot::Dialect::POSTGRES, to: Sqlglot::Dialect::BIGQUERY)

Parse SQL to an AST

ast = Sqlglot.parse("SELECT name, age FROM users WHERE active = true", dialect: :postgres)
# => {"Select" => {"columns" => [...], "from" => {...}, "where_clause" => {...}, ...}}

The return value is a plain Ruby Hash deserialized from the Rust library's JSON AST.

Generate SQL from an AST

ast = Sqlglot.parse("SELECT name FROM users WHERE id = 1")

# Roundtrip back to SQL
Sqlglot.generate(ast)
# => "SELECT name FROM users WHERE id = 1"

# Generate for a different dialect
Sqlglot.generate(ast, dialect: :tsql)

Modify an AST and regenerate

ast = Sqlglot.parse("SELECT name FROM users WHERE id = 1")

# Change the WHERE value
ast["Select"]["where_clause"]["BinaryOp"]["right"] = { "Number" => "42" }

Sqlglot.generate(ast)
# => "SELECT name FROM users WHERE id = 42"

Library version

Sqlglot.version
# => "0.10.0"

Query Metadata Extraction

Sqlglot::Query parses SQL once, then provides lazy-evaluated properties for extracting tables, columns, aliases, and more.

q = Sqlglot::Query.new(<<~SQL, dialect: :postgres)
  SELECT u.name, COUNT(o.id) AS order_count
  FROM users AS u
  JOIN orders AS o ON u.id = o.user_id
  WHERE u.active = true
  ORDER BY order_count DESC
  LIMIT 10 OFFSET 20
SQL

Query type

q.query_type  # => :select
# Also: :insert, :update, :delete, :create_table, :merge, etc.

Tables

q.tables          # => ["users", "orders"]
q.tables_aliases  # => {"u" => "users", "o" => "orders"}

CTE names are automatically excluded from the tables list:

q = Sqlglot::Query.new("WITH cte AS (SELECT id FROM real_table) SELECT * FROM cte")
q.tables  # => ["real_table"]

Columns

q.columns
# => ["users.name", "orders.id", "users.id", "orders.user_id", "users.active"]

# Table aliases are resolved: u.name becomes users.name

Columns by clause

q.columns_dict
# => {
#   select:   ["users.name", "orders.id"],
#   join:     ["users.id", "orders.user_id"],
#   where:    ["users.active"],
#   order_by: ["orders.id"]   # resolved from the "order_count" alias
# }

Output columns

What the SELECT would produce (uses aliases where present):

q.output_columns  # => ["name", "order_count"]

Column aliases

q = Sqlglot::Query.new("SELECT a, b + c AS total FROM t ORDER BY total")

q.columns_aliases        # => {"total" => ["b", "c"]}
q.columns_aliases_names  # => ["total"]
q.columns_aliases_dict   # => {select: ["total"], order_by: ["total"]}

CTEs

q = Sqlglot::Query.new(<<~SQL)
  WITH active AS (SELECT * FROM users WHERE active = true)
  SELECT * FROM active
SQL

q.with_names    # => ["active"]
q.with_queries  # => {"active" => "SELECT * FROM users WHERE active = true"}

Subqueries

q = Sqlglot::Query.new(<<~SQL)
  SELECT * FROM (SELECT id FROM users) AS sub
  JOIN orders ON sub.id = orders.user_id
SQL

q.subqueries       # => {"sub" => "SELECT id FROM users"}
q.subqueries_names # => ["sub"]

LIMIT and OFFSET

q.limit_and_offset  # => [10, 20]

# Returns nil when no LIMIT is present
Sqlglot::Query.new("SELECT 1").limit_and_offset  # => nil

INSERT values

q = Sqlglot::Query.new("INSERT INTO users (name, age) VALUES ('Alice', 30)")

q.values       # => ["Alice", 30]
q.values_dict  # => {"name" => "Alice", "age" => 30}

# Auto-generates column names when not specified
q = Sqlglot::Query.new("INSERT INTO t VALUES (1, 'hello')")
q.values_dict  # => {"column_1" => 1, "column_2" => "hello"}

Query generalization

Replaces literals with placeholders for query fingerprinting:

q = Sqlglot::Query.new("SELECT * FROM t WHERE id = 42 AND name = 'Alice'")
q.generalize
# => "SELECT * FROM t WHERE id = N AND name = 'X'"

Raw AST access

q.ast  # => the full parsed Hash (same as Sqlglot.parse)

Rails Integration

The gem includes a Railtie that loads automatically when Rails is present.

# config/application.rb (or an initializer)
config.sqlglot.default_dialect = :sqlite

Then dialect can be omitted from calls:

Sqlglot.parse("SELECT 1")             # uses :sqlite
Sqlglot.transpile(sql, to: :bigquery) # from: defaults to :sqlite

Without Rails, use the configure block:

Sqlglot.configure do |c|
  c.default_dialect = :sqlite
end

Supported Dialects

Official

Dialect Constant
ANSI SQL Sqlglot::Dialect::ANSI
Athena Sqlglot::Dialect::ATHENA
BigQuery Sqlglot::Dialect::BIGQUERY
ClickHouse Sqlglot::Dialect::CLICKHOUSE
Databricks Sqlglot::Dialect::DATABRICKS
DuckDB Sqlglot::Dialect::DUCKDB
Hive Sqlglot::Dialect::HIVE
MySQL Sqlglot::Dialect::MYSQL
Oracle Sqlglot::Dialect::ORACLE
PostgreSQL Sqlglot::Dialect::POSTGRES
Presto Sqlglot::Dialect::PRESTO
Redshift Sqlglot::Dialect::REDSHIFT
Snowflake Sqlglot::Dialect::SNOWFLAKE
Spark Sqlglot::Dialect::SPARK
SQLite Sqlglot::Dialect::SQLITE
StarRocks Sqlglot::Dialect::STARROCKS
Trino Sqlglot::Dialect::TRINO
T-SQL Sqlglot::Dialect::TSQL

Community

Dialect Constant
Doris Sqlglot::Dialect::DORIS
Dremio Sqlglot::Dialect::DREMIO
Drill Sqlglot::Dialect::DRILL
Druid Sqlglot::Dialect::DRUID
Exasol Sqlglot::Dialect::EXASOL
Fabric Sqlglot::Dialect::FABRIC
Materialize Sqlglot::Dialect::MATERIALIZE
PRQL Sqlglot::Dialect::PRQL
RisingWave Sqlglot::Dialect::RISINGWAVE
SingleStore Sqlglot::Dialect::SINGLESTORE
Tableau Sqlglot::Dialect::TABLEAU
Teradata Sqlglot::Dialect::TERADATA

Aliases are also accepted: :postgresql, :mssql, :sqlserver, :mariadb.

Configuration

Setting Description
Sqlglot.configure { |c| c.default_dialect = :sqlite } Default dialect when none is specified
ENV["SQLGLOT_LIB_PATH"] Override the path to libsqlglot_rust.so / .dylib

Development

git clone <this-repo>
cd sqlglot
bundle install
rake cargo:build    # build the Rust shared library
bundle exec rspec   # run tests (49 examples)

License

MIT