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)
- Ruby >= 3.0
- Rust toolchain (cargo 1.85+) -- only needed when building from source
- git -- only needed when building from source
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.
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 RustFor local development, you can also build manually:
bundle install
rake cargo:buildrake 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.
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)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.
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)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"Sqlglot.version
# => "0.10.12"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
SQLq.query_type # => :select
# Also: :insert, :update, :delete, :create_table, :merge, etc.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"]q.columns
# => ["users.name", "orders.id", "users.id", "orders.user_id", "users.active"]
# Table aliases are resolved: u.name becomes users.nameq.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
# }What the SELECT would produce (uses aliases where present):
q.output_columns # => ["name", "order_count"]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"]}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"}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"]q.limit_and_offset # => [10, 20]
# Returns nil when no LIMIT is present
Sqlglot::Query.new("SELECT 1").limit_and_offset # => nilq = 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"}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'"q.ast # => the full parsed Hash (same as Sqlglot.parse)The gem includes a Railtie that loads automatically when Rails is present.
# config/application.rb (or an initializer)
config.sqlglot.default_dialect = :sqliteThen dialect can be omitted from calls:
Sqlglot.parse("SELECT 1") # uses :sqlite
Sqlglot.transpile(sql, to: :bigquery) # from: defaults to :sqliteWithout Rails, use the configure block:
Sqlglot.configure do |c|
c.default_dialect = :sqlite
end| 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 |
| 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.
| 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 |
git clone <this-repo>
cd sqlglot
bundle install
rake cargo:build # build the Rust shared library
bundle exec rspec # run tests (49 examples)MIT