You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Attached SQL comments (populated by parse_with_comments)
ctes
Vec<Cte>
Common Table Expressions (WITH clause)
distinct
bool
SELECT DISTINCT flag
top
Option<Box<Expr>>
T-SQL TOP N expression
columns
Vec<SelectItem>
Select list items
from
Option<FromClause>
Primary FROM source
joins
Vec<JoinClause>
All JOIN clauses
where_clause
Option<Expr>
WHERE predicate
group_by
Vec<Expr>
GROUP BY expressions
having
Option<Expr>
HAVING predicate
order_by
Vec<OrderByItem>
ORDER BY items
limit
Option<Expr>
LIMIT value
offset
Option<Expr>
OFFSET value
fetch_first
Option<Expr>
Oracle FETCH FIRST N ROWS ONLY
qualify
Option<Expr>
BigQuery/Snowflake QUALIFY clause
window_definitions
Vec<WindowDefinition>
Named WINDOW specs
Example:
use sqlglot_rust::{parse,Dialect,Statement};let stmt = parse("WITH cte AS (SELECT 1 AS x) SELECT DISTINCT x FROM cte WHERE x > 0 ORDER BY x LIMIT 5",Dialect::Ansi,).unwrap();ifletStatement::Select(s) = &stmt {assert_eq!(s.ctes.len(),1);// 1 CTEassert!(s.distinct);// DISTINCTassert_eq!(s.columns.len(),1);// 1 columnassert!(s.where_clause.is_some());// has WHEREassert_eq!(s.order_by.len(),1);// 1 ORDER BYassert!(s.limit.is_some());// has LIMIT}
let stmt = parse("UPDATE users SET name = 'Bob' WHERE id = 1",Dialect::Ansi).unwrap();// => Statement::Update(UpdateStatement { table: "users", assignments: [("name", StringLiteral("Bob"))], ... })
let stmt = parse("DELETE FROM users WHERE id = 1",Dialect::Ansi).unwrap();// => Statement::Delete(DeleteStatement { table: "users", where_clause: Some(...) })
One or more WHEN MATCHED / WHEN NOT MATCHED clauses
output
T-SQL OUTPUT clause items
Supported dialect extensions:
WHEN NOT MATCHED BY SOURCE (T-SQL)
INSERT ROW (BigQuery)
OUTPUT clause (T-SQL)
Multiple WHEN clauses with AND conditions
Example:
use sqlglot_rust::{parse, generate, transpile,Dialect};// Parse a standard MERGE statementlet stmt = parse("MERGE INTO target AS t USING source AS s ON t.id = s.id \ WHEN MATCHED THEN UPDATE SET t.name = s.name \ WHEN NOT MATCHED THEN INSERT (id, name) VALUES (s.id, s.name)",Dialect::Ansi,).unwrap();// Roundtrip back to SQLlet sql = generate(&stmt,Dialect::Ansi);// Transpile to another dialectlet snowflake_sql = transpile("MERGE INTO dst USING src ON dst.id = src.id WHEN MATCHED THEN DELETE",Dialect::Ansi,Dialect::Snowflake,).unwrap();
let stmt = parse("CREATE TABLE IF NOT EXISTS items (id INT NOT NULL, name VARCHAR(100), price DECIMAL(10, 2))",Dialect::Ansi,).unwrap();// => Statement::CreateTable(CreateTableStatement { if_not_exists: true, columns: [3 ColumnDefs], ... })
BEGIN, COMMIT, ROLLBACK, SAVEPOINT x, RELEASE SAVEPOINT x, ROLLBACK TO x
ExplainStatement
EXPLAIN [ANALYZE] statement
UseStatement
USE database_name
Expr Enum
Expr Variants
Variant
Fields
SQL Example
Column
{ name, table?, quote_style, table_quote_style }
t.col, "col"
Number
(String)
42, 3.14
StringLiteral
(String)
'hello'
Boolean
(bool)
TRUE, FALSE
Null
—
NULL
BinaryOp
{ left, op, right }
a + b, x AND y
UnaryOp
{ op, expr }
-x, NOT a, ~b
Function
{ name, args, distinct, filter?, over? }
COUNT(DISTINCT x), SUM(a) OVER (...)
TypedFunction
{ func: TypedFunction, filter?, over? }
SUBSTRING(x, 1, 3), DATE_TRUNC('month', d)
Between
{ expr, low, high, negated }
x BETWEEN 1 AND 10
InList
{ expr, list, negated }
x IN (1, 2, 3)
InSubquery
{ expr, subquery, negated }
x IN (SELECT ...)
AnyOp
{ expr, op, right }
x = ANY(ARRAY[1, 2]), x = ANY(SELECT ...)
AllOp
{ expr, op, right }
x > ALL(SELECT ...)
IsNull
{ expr, negated }
x IS NULL, x IS NOT NULL
Like
{ expr, pattern, negated, escape? }
name LIKE '%test%'
ILike
{ expr, pattern, negated, escape? }
name ILIKE '%test%'
Case
{ operand?, when_clauses, else_clause? }
CASE WHEN ... THEN ... END
Nested
(Box<Expr>)
(a + b)
Wildcard
—
* (in function args)
Star
—
* (select list)
QualifiedWildcard
{ table }
t.*
Subquery
(Box<Statement>)
(SELECT 1)
Exists
{ subquery, negated }
EXISTS (SELECT ...)
Cast
{ expr, data_type }
CAST(x AS INT)
TryCast
{ expr, data_type }
TRY_CAST(x AS INT)
Extract
{ field, expr }
EXTRACT(YEAR FROM d)
Interval
{ value, unit? }
INTERVAL '1' DAY
ArrayLiteral
(Vec<Expr>)
ARRAY[1, 2, 3]
Tuple
(Vec<Expr>)
(1, 'a', true)
Coalesce
(Vec<Expr>)
COALESCE(a, b, c)
If
{ condition, true_val, false_val? }
IF(a > 0, a, 0)
NullIf
{ expr, else }
NULLIF(a, b)
Collate
{ expr, collation }
col COLLATE utf8
AtTimeZone
{ expr, zone }
ts AT TIME ZONE 'UTC'
Parameter
(String)
$1, ?, :name
TypeExpr
(DataType)
Used in DDL contexts
Alias
{ expr, name }
expr AS alias
ArrayIndex
{ expr, index }
arr[0]
JsonAccess
{ expr, path, as_text }
data->'key', data->>'key'
Lambda
{ params, body }
x -> x + 1
Default
—
DEFAULT
Cube
{ exprs }
CUBE(a, b)
Rollup
{ exprs }
ROLLUP(a, b)
GroupingSets
{ sets }
GROUPING SETS((a, b), (a), ())
Commented
{ expr, comments }
Expression with attached SQL comments
Expr Methods
Method
Signature
Returns
Description
walk
(&self, visitor: &mut F) where F: FnMut(&Expr) -> bool
()
Depth-first traversal. Visitor returns true to recurse.
find
(&self, predicate: &F) -> Option<&Expr> where F: Fn(&Expr) -> bool
Option<&Expr>
First matching node
find_all
(&self, predicate: &F) -> Vec<&Expr> where F: Fn(&Expr) -> bool
Vec<&Expr>
All matching nodes
transform
(self, func: &F) -> Expr where F: Fn(Expr) -> Expr
Expr
Bottom-up tree rewrite (consumes self)
is_column
(&self) -> bool
bool
true if Column variant
is_literal
(&self) -> bool
bool
true if Number, StringLiteral, or Boolean
sql
(&self) -> String
String
Quick SQL output (ANSI dialect)
Examples:
use sqlglot_rust::Expr;// sql()assert_eq!(Expr::Number("42".into()).sql(),"42");assert_eq!(Expr::StringLiteral("hello".into()).sql(),"'hello'");assert_eq!(Expr::Boolean(true).sql(),"TRUE");assert_eq!(Expr::Null.sql(),"NULL");// is_column / is_literalassert!(Expr::Column{ name:"a".into(), table:None,
quote_style:Default::default(), table_quote_style:Default::default()}.is_column());assert!(Expr::Number("1".into()).is_literal());assert!(Expr::Boolean(false).is_literal());assert!(!Expr::Null.is_literal());
TypedFunction Enum
Typed function variants enable per-function transpilation rules and dialect-specific code generation.
Each variant carries semantically typed arguments rather than a generic Vec<Expr>.
When a recognized function name is parsed (e.g., SUBSTRING, NOW, COUNT), the parser creates
Expr::TypedFunction { func, filter, over } instead of a generic Expr::Function. This allows
the generator to emit the correct SQL for each target dialect without relying on string-based
function renaming.
TypedFunction Variants
Date/Time
Variant
Fields
SQL
DateAdd
{ expr, interval, unit? }
DATE_ADD(d, INTERVAL 1 DAY)
DateDiff
{ start, end, unit? }
DATE_DIFF(d1, d2)
DateTrunc
{ unit, expr }
DATE_TRUNC('month', d)
DateSub
{ expr, interval, unit? }
DATE_SUB(d, INTERVAL 1 DAY)
CurrentDate
—
CURRENT_DATE
CurrentTimestamp
—
CURRENT_TIMESTAMP / NOW() / GETDATE()
StrToTime
{ expr, format }
STR_TO_TIME(s, fmt)
TimeToStr
{ expr, format }
TIME_TO_STR(t, fmt)
TsOrDsToDate
{ expr }
TS_OR_DS_TO_DATE(expr)
Year
{ expr }
YEAR(d)
Month
{ expr }
MONTH(d)
Day
{ expr }
DAY(d)
String
Variant
Fields
SQL
Trim
{ expr, trim_type, trim_chars? }
TRIM(LEADING 'x' FROM s)
Substring
{ expr, start, length? }
SUBSTRING(s, 1, 3) / SUBSTR(s, 1, 3)
Upper
{ expr }
UPPER(s)
Lower
{ expr }
LOWER(s)
RegexpLike
{ expr, pattern, flags? }
REGEXP_LIKE(s, '^A')
RegexpExtract
{ expr, pattern, group_index? }
REGEXP_EXTRACT(s, '(\\d+)')
RegexpReplace
{ expr, pattern, replacement, flags? }
REGEXP_REPLACE(s, '\\d', 'X')
ConcatWs
{ separator, exprs }
CONCAT_WS(',', a, b, c)
Split
{ expr, delimiter }
SPLIT(s, ',')
Initcap
{ expr }
INITCAP(s)
Length
{ expr }
LENGTH(s) / LEN(s)
Replace
{ expr, from, to }
REPLACE(s, 'old', 'new')
Reverse
{ expr }
REVERSE(s)
Left
{ expr, n }
LEFT(s, 3)
Right
{ expr, n }
RIGHT(s, 3)
Lpad
{ expr, length, pad? }
LPAD(s, 10, '0')
Rpad
{ expr, length, pad? }
RPAD(s, 10, '0')
Aggregate
Variant
Fields
SQL
Count
{ expr, distinct }
COUNT(*), COUNT(DISTINCT x)
Sum
{ expr, distinct }
SUM(x), SUM(DISTINCT x)
Avg
{ expr, distinct }
AVG(x)
Min
{ expr }
MIN(x)
Max
{ expr }
MAX(x)
ArrayAgg
{ expr, distinct }
ARRAY_AGG(x) / LIST(x) / COLLECT_LIST(x)
ApproxDistinct
{ expr }
APPROX_DISTINCT(x)
Variance
{ expr }
VARIANCE(x) / VAR_SAMP(x)
VariancePop
{ expr }
VAR_POP(x) (T-SQL VARP(x))
Stddev
{ expr }
STDDEV(x) / STDDEV_SAMP(x) (T-SQL STDEV(x))
StddevPop
{ expr }
STDDEV_POP(x) (T-SQL STDEVP(x))
Array
Variant
Fields
SQL
ArrayConcat
{ arrays }
ARRAY_CONCAT(a, b)
ArrayContains
{ array, element }
ARRAY_CONTAINS(arr, 1)
ArraySize
{ expr }
ARRAY_SIZE(arr) / ARRAY_LENGTH(arr)
Explode
{ expr }
EXPLODE(arr)
GenerateSeries
{ start, stop, step? }
GENERATE_SERIES(1, 10)
Flatten
{ expr }
FLATTEN(arr)
JSON
Variant
Fields
SQL
JSONExtract
{ expr, path }
JSON_EXTRACT(doc, '$.key')
JSONExtractScalar
{ expr, path }
JSON_EXTRACT_SCALAR(doc, '$.key')
ParseJSON
{ expr }
PARSE_JSON(s)
JSONFormat
{ expr }
JSON_FORMAT(obj) / TO_JSON(obj)
Window
Variant
Fields
SQL
RowNumber
—
ROW_NUMBER()
Rank
—
RANK()
DenseRank
—
DENSE_RANK()
NTile
{ n }
NTILE(4)
Lead
{ expr, offset?, default? }
LEAD(x, 1, 0)
Lag
{ expr, offset?, default? }
LAG(x, 1, 0)
FirstValue
{ expr }
FIRST_VALUE(x)
LastValue
{ expr }
LAST_VALUE(x)
Math
Variant
Fields
SQL
Abs
{ expr }
ABS(x)
Ceil
{ expr }
CEIL(x) / CEILING(x)
Floor
{ expr }
FLOOR(x)
Round
{ expr, decimals? }
ROUND(x, 2)
Log
{ expr, base? }
LOG(x)
Ln
{ expr }
LN(x)
Pow
{ base, exponent }
POW(x, 2) / POWER(x, 2)
Sqrt
{ expr }
SQRT(x)
Greatest
{ exprs }
GREATEST(a, b, c)
Least
{ exprs }
LEAST(a, b, c)
Mod
{ left, right }
MOD(a, b)
Conversion
Variant
Fields
SQL
Hex
{ expr }
HEX(x) / TO_HEX(x)
Unhex
{ expr }
UNHEX(x) / FROM_HEX(x)
Md5
{ expr }
MD5(x)
Sha
{ expr }
SHA(x) / SHA1(x)
Sha2
{ expr, bit_length }
SHA2(x, 256)
TrimType Enum
pubenumTrimType{Leading,Trailing,Both,}
Dialect-Specific Function Generation
The generator emits different SQL for each TypedFunction variant depending on the target dialect:
use sqlglot_rust::{parse, generate,Dialect};let ast = parse("SELECT EXTRACT(YEAR FROM hire_date) FROM employees",Dialect::Ansi).unwrap();let sql = generate(&ast,Dialect::Ansi);assert_eq!(sql,"SELECT EXTRACT(YEAR FROM hire_date) FROM employees");
Used internally to classify comment tokens. Comments are stored as raw strings
(including delimiters) on the comments: Vec<String> field of each statement
struct and on Expr::Commented.
QuoteStyle Enum
pubenumQuoteStyle{None,// bare identifierDoubleQuote,// "identifier"Backtick,// `identifier`Bracket,// [identifier]}
use sqlglot_rust::{QuoteStyle,Dialect};assert_eq!(QuoteStyle::for_dialect(Dialect::Mysql),QuoteStyle::Backtick);assert_eq!(QuoteStyle::for_dialect(Dialect::Postgres),QuoteStyle::DoubleQuote);assert_eq!(QuoteStyle::for_dialect(Dialect::Tsql),QuoteStyle::Bracket);assert!(QuoteStyle::Backtick.is_quoted());assert!(!QuoteStyle::None.is_quoted());
String-Literal Aliases
Some dialects accept a string literal as an alias after an explicit AS
(SELECT col AS 'Record Id') — notably SQLite, MySQL, Snowflake, and T-SQL
(legacy syntax). The parser accepts this form for every dialect (it is lenient
on input) and normalizes the alias to a quoted identifier, so the generator
re-emits it in the target dialect's canonical quote style with embedded quotes
escaped. The alias is treated as quoted, so its case is always preserved
(never folded).
use sqlglot_rust::{transpile,Dialect};// SQLite string-literal alias → the target dialect's canonical quoted identifierassert_eq!(transpile("SELECT 7 AS 'Mixed'",Dialect::Sqlite,Dialect::Postgres).unwrap(),r#"SELECT 7 AS "Mixed""#);// double quoteassert_eq!(transpile("SELECT 7 AS 'Mixed'",Dialect::Sqlite,Dialect::Mysql).unwrap(),"SELECT 7 AS `Mixed`");// backtickassert_eq!(transpile("SELECT 7 AS 'Mixed'",Dialect::Sqlite,Dialect::Tsql).unwrap(),"SELECT 7 AS [Mixed]");// bracket
A trailing string with noAS is never treated as an alias (in MySQL,
adjacent string literals are concatenation).
Dialect Enum
Dialect List
30 dialects in two support tiers:
#
Variant
Display Name
Support
1
Ansi
ANSI SQL
Official
2
Athena
Athena
Official
3
BigQuery
BigQuery
Official
4
ClickHouse
ClickHouse
Official
5
Databricks
Databricks
Official
6
DuckDb
DuckDB
Official
7
Hive
Hive
Official
8
Mysql
MySQL
Official
9
Oracle
Oracle
Official
10
Postgres
PostgreSQL
Official
11
Presto
Presto
Official
12
Redshift
Redshift
Official
13
Snowflake
Snowflake
Official
14
Spark
Spark
Official
15
Sqlite
SQLite
Official
16
StarRocks
StarRocks
Official
17
Trino
Trino
Official
18
Tsql
T-SQL
Official
19
Doris
Doris
Community
20
Dremio
Dremio
Community
21
Drill
Drill
Community
22
Druid
Druid
Community
23
Exasol
Exasol
Community
24
Fabric
Fabric
Community
25
Materialize
Materialize
Community
26
Prql
PRQL
Community
27
RisingWave
RisingWave
Community
28
SingleStore
SingleStore
Community
29
Tableau
Tableau
Community
30
Teradata
Teradata
Community
Dialect Methods
Method
Signature
Description
all()
-> &'static [Dialect]
All 30 dialect variants
from_str(s)
(s: &str) -> Option<Dialect>
Case-insensitive lookup with aliases
support_level()
(&self) -> &'static str
"Official" or "Community"
Display trait
format!("{}", dialect)
Human-readable name
Dialect Aliases (from_str)
Input String(s)
Resolved Dialect
postgres, postgresql
Dialect::Postgres
tsql, mssql, sqlserver
Dialect::Tsql
bigquery
Dialect::BigQuery
clickhouse
Dialect::ClickHouse
hive
Dialect::Hive
spark
Dialect::Spark
mysql
Dialect::Mysql
sqlite
Dialect::Sqlite
duckdb
Dialect::DuckDb
(All lookups are case-insensitive.)
Example:
use sqlglot_rust::Dialect;assert_eq!(Dialect::from_str("postgresql"),Some(Dialect::Postgres));assert_eq!(Dialect::from_str("mssql"),Some(Dialect::Tsql));assert_eq!(Dialect::from_str("BIGQUERY"),Some(Dialect::BigQuery));assert_eq!(Dialect::from_str("unknown"),None);assert_eq!(Dialect::Postgres.support_level(),"Official");assert_eq!(Dialect::Teradata.support_level(),"Community");assert_eq!(format!("{}",Dialect::DuckDb),"DuckDB");assert_eq!(Dialect::all().len(),30);
Function Mapping Matrix
Transformations applied automatically during transpilation via typed function expressions:
Date/time format strings are automatically converted during transpilation. Each dialect family uses different format specifiers:
Style
Dialects
Year
Month
Day
Hour (24h)
Minute
Second
strftime
SQLite, BigQuery, DuckDB
%Y
%m
%d
%H
%M
%S
MySQL
MySQL, Doris, SingleStore, StarRocks
%Y
%m
%d
%H
%i
%s
Postgres
PostgreSQL, Oracle, Redshift
YYYY
MM
DD
HH24
MI
SS
Snowflake
Snowflake
YYYY
MM
DD
HH24
MI
SS
Java
Spark, Hive, Databricks, Presto, Trino
yyyy
MM
dd
HH
mm
ss
Example Transpilation:
use sqlglot_rust::{transpile,Dialect};// MySQL → PostgreSQL: format strings are converted automaticallylet result = transpile("SELECT DATE_FORMAT(created_at, '%Y-%m-%d %H:%i:%s')",Dialect::Mysql,Dialect::Postgres).unwrap();assert_eq!(result,"SELECT TO_CHAR(created_at, 'YYYY-MM-DD HH24:MI:SS')");// PostgreSQL → Sparklet result = transpile("SELECT TO_CHAR(dt, 'YYYY-MM-DD HH24:MI:SS')",Dialect::Postgres,Dialect::Spark).unwrap();assert_eq!(result,"SELECT DATE_FORMAT(dt, 'yyyy-MM-dd HH:mm:ss')");
Direct Format Conversion:
use sqlglot_rust::{format_time, format_time_dialect,TimeFormatStyle,Dialect};// Convert format strings directlylet pg_format = format_time("%Y-%m-%d",TimeFormatStyle::Strftime,TimeFormatStyle::Postgres);assert_eq!(pg_format,"YYYY-MM-DD");// Or use dialect-to-dialect conversionlet spark_format = format_time_dialect("YYYY-MM-DD HH24:MI:SS",Dialect::Postgres,Dialect::Spark);assert_eq!(spark_format,"yyyy-MM-dd HH:mm:ss");
Format Function Mapping:
Source Function
Postgres
MySQL
BigQuery
Spark
T-SQL
Time → String
TO_CHAR()
DATE_FORMAT()
FORMAT_TIMESTAMP()
DATE_FORMAT()
FORMAT()
String → Time
TO_TIMESTAMP()
STR_TO_DATE()
PARSE_TIMESTAMP()
TO_TIMESTAMP()
CONVERT()
T-SQL Style Codes:
T-SQL's CONVERT function uses numeric style codes instead of format patterns. The TsqlStyleCode enum provides mappings:
Code
Format
Example
101
mm/dd/yyyy (USA)
03/17/2026
102
yyyy.mm.dd (ANSI)
2026.03.17
103
dd/mm/yyyy (British)
17/03/2026
120
yyyy-mm-dd hh:mi:ss (ODBC)
2026-03-17 22:15:30
126
yyyy-mm-ddThh:mi:ss.mmm (ISO8601)
2026-03-17T22:15:30.123
Custom Dialect Plugins
The plugin system (sqlglot_rust::dialects::plugin) provides extensibility for
custom SQL dialects without modifying the library source.
All variants implement std::fmt::Display and std::error::Error via thiserror.
Example:
use sqlglot_rust::{parse,Dialect,SqlglotError};let err = parse("SELECT ???",Dialect::Ansi).unwrap_err();match err {SqlglotError::ParserError{ message } => println!("Parse: {message}"),SqlglotError::TokenizerError{ message, position } => {println!("Token error at byte {position}: {message}")}
other => println!("{other}"),}
Free Functions (ast module)
Accessed via use sqlglot_rust::ast::*.
Function
Signature
Returns
Description
find_columns
(expr: &Expr) -> Vec<&Expr>
Vec<&Expr>
All Column nodes in an expression tree
find_tables
(stmt: &Statement) -> Vec<&TableRef>
Vec<&TableRef>
All TableRef nodes in a statement
Example:
use sqlglot_rust::{parse,Dialect};use sqlglot_rust::ast::find_tables;let stmt = parse("SELECT a FROM t1 INNER JOIN t2 ON t1.id = t2.id",Dialect::Ansi,).unwrap();let tables = find_tables(&stmt);assert_eq!(tables.len(),2);assert_eq!(tables[0].name,"t1");assert_eq!(tables[1].name,"t2");
Schema System
Accessed via use sqlglot_rust::schema::*.
The schema module provides dialect-aware table and column metadata management, serving as the foundation for type annotation, column qualification, and lineage analysis.
Build schema from catalog → db → table → column → type map
normalize_identifier
(name: &str, dialect: Dialect)
String
Normalize an identifier per dialect rules
is_case_sensitive_dialect
(dialect: Dialect)
bool
Check if a dialect is case-sensitive
SchemaError
Variant
Description
TableNotFound(String)
The referenced table was not found
ColumnNotFound { table, column }
The column was not found in the table
DuplicateTable(String)
Table is already registered (use replace_table instead)
SchemaError implements From<SchemaError> for SqlglotError.
Optimizer
Accessed via use sqlglot_rust::optimizer::optimize.
Function
Signature
Returns
Description
optimize
(stmt: Statement) -> Result<Statement>
Statement
Apply all optimization passes
build_scope
(stmt: &Statement) -> Scope
Scope
Build a scope tree from a parsed statement
find_all_in_scope
(scope: &Scope, predicate: &F) -> Vec<&ColumnRef>
Vec<&ColumnRef>
Find columns matching a predicate within a single scope
annotate_types
(stmt: &Statement, schema: &S) -> TypeAnnotations
TypeAnnotations
Infer SQL types for all expression nodes
Optimization Passes
Pass
Description
Example
Constant Folding
Evaluate compile-time expressions
1 + 2 → 3
Boolean Simplification
Eliminate tautologies / contradictions
TRUE AND x → x
Dead Predicate Elimination
Remove trivially-true WHERE clauses
WHERE TRUE → removed
Subquery Unnesting
Decorrelate subqueries into JOINs
WHERE EXISTS (… WHERE b.id = a.id) → INNER JOIN
Qualify Columns
Resolve and qualify column references
SELECT col FROM t → SELECT t.col FROM t
Annotate Types
Infer SQL types for all AST expression nodes
Column(id) → Int, COUNT(*) → BigInt
Subquery Unnesting Details
unnest_subqueries rewrites correlated subqueries in WHERE clauses into JOINs.
Accessed via use sqlglot_rust::optimizer::unnest_subqueries::unnest_subqueries.
Function
Signature
Returns
Description
unnest_subqueries
(stmt: Statement) -> Statement
Statement
Decorrelate WHERE subqueries into JOINs
Pattern
Rewrite
WHERE EXISTS (SELECT … WHERE b.id = a.id)
INNER JOIN (SELECT DISTINCT …) ON a.id = _u0.id
WHERE NOT EXISTS (…)
LEFT JOIN … WHERE _u0._sentinel IS NULL
WHERE x IN (SELECT col FROM …)
INNER JOIN (SELECT DISTINCT col AS _col0 …)
WHERE x NOT IN (SELECT col FROM …)
LEFT JOIN … WHERE _u0._col0 IS NULL
The pass bails out (no-op) when:
No equality correlation exists in the subquery.
Non-equality correlations are present (e.g., <, >) — would need LATERAL.
The subquery is embedded in a SELECT-list function (e.g., COALESCE).
Example:
use sqlglot_rust::{parse, generate,Dialect};use sqlglot_rust::optimizer::optimize;let stmt = parse("SELECT 1 + 2 * 3",Dialect::Ansi).unwrap();let opt = optimize(stmt).unwrap();assert_eq!(generate(&opt,Dialect::Ansi),"SELECT 7");let stmt = parse("SELECT a FROM t WHERE TRUE AND x > 1",Dialect::Ansi).unwrap();let opt = optimize(stmt).unwrap();assert_eq!(generate(&opt,Dialect::Ansi),"SELECT a FROM t WHERE x > 1");
Qualify Columns
qualify_columns resolves column references against the schema, adds table qualifiers to
unqualified columns, and expands wildcard selects (*, t.*) into explicit column lists.
Accessed via use sqlglot_rust::optimizer::qualify_columns::qualify_columns.
Function
Signature
Returns
Description
qualify_columns
(stmt: Statement, schema: &S) -> Statement
Statement
Qualify column references and expand wildcards
The schema parameter must implement the Schema trait. The most common implementation
is MappingSchema (see Schema System).
Transformation
Before
After
Qualify unqualified column
SELECT col FROM t
SELECT t.col FROM t
Expand *
SELECT * FROM t
SELECT t.id, t.name FROM t
Expand t.*
SELECT t.* FROM t
SELECT t.id, t.name FROM t
Qualify WHERE / GROUP BY / ORDER BY
WHERE col = 1
WHERE t.col = 1
Qualify JOIN ON
ON id = other_id
ON a.id = b.other_id
CTE column resolution
WITH cte AS (...) SELECT col FROM cte
WITH cte AS (...) SELECT cte.col FROM cte
Derived table columns
SELECT col FROM (SELECT ...) AS sub
SELECT sub.col FROM (SELECT ...) AS sub
Subquery in WHERE
WHERE id IN (SELECT fk FROM t2)
WHERE t.id IN (SELECT t2.fk FROM t2)
Ambiguous columns (present in multiple sources) are left unqualified.
Example:
use sqlglot_rust::{parse, generate,Dialect};use sqlglot_rust::optimizer::qualify_columns::qualify_columns;use sqlglot_rust::schema::MappingSchema;let schema = MappingSchema::new().with_table(vec!["users"],vec!["id","name","email"]).with_table(vec!["orders"],vec!["id","user_id","amount"]);let stmt = parse("SELECT name FROM users WHERE id = 1",Dialect::Ansi).unwrap();let qualified = qualify_columns(stmt,&schema);assert_eq!(
generate(&qualified,Dialect::Ansi),"SELECT users.name FROM users WHERE users.id = 1");// Expand wildcardlet stmt = parse("SELECT * FROM users",Dialect::Ansi).unwrap();let qualified = qualify_columns(stmt,&schema);assert_eq!(
generate(&qualified,Dialect::Ansi),"SELECT users.id, users.name, users.email FROM users");
Scope Analysis
Scope analysis tracks the sources, columns, and inter-scope relationships
in a query tree. It is the foundation for qualify_columns, pushdown_predicates,
annotate_types, and column lineage analysis.
Accessed via use sqlglot_rust::optimizer::scope_analysis::{build_scope, find_all_in_scope, Scope, ScopeType}
or the crate-level re-exports use sqlglot_rust::{build_scope, find_all_in_scope, Scope, ScopeType}.
ScopeType
Variant
Description
Root
The outermost query
Subquery
A scalar or lateral subquery (WHERE / SELECT / HAVING)
DerivedTable
A subquery in FROM
Cte
A CTE definition (WITH name AS (...))
Union
One branch of a UNION / INTERSECT / EXCEPT
Udtf
A user-defined table function / LATERAL
Scope Fields
Field
Type
Description
scope_type
ScopeType
Kind of scope
sources
HashMap<String, Source>
Source name/alias → table or child scope
columns
Vec<ColumnRef>
Column references in this scope (not child scopes)
external_columns
Vec<ColumnRef>
Columns referencing an outer scope (correlations)
derived_table_scopes
Vec<Scope>
Child scopes from subqueries in FROM
subquery_scopes
Vec<Scope>
Child scopes from scalar/EXISTS/IN subqueries
union_scopes
Vec<Scope>
Child scopes for UNION/INTERSECT/EXCEPT branches
cte_scopes
Vec<Scope>
Child scopes for CTE definitions
selected_sources
HashMap<String, Source>
Sources referenced by SELECT columns
is_correlated
bool
Whether scope references outer columns
Example
use sqlglot_rust::{parse,Dialect, build_scope, find_all_in_scope};use sqlglot_rust::optimizer::scope_analysis::ScopeType;let ast = parse("SELECT a FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.id = t1.id)",Dialect::Ansi,).unwrap();let scope = build_scope(&ast);assert_eq!(scope.scope_type,ScopeType::Root);assert!(scope.sources.contains_key("t1"));assert_eq!(scope.subquery_scopes.len(),1);let sub = &scope.subquery_scopes[0];assert!(sub.is_correlated);assert!(sub.external_columns.iter().any(|c| c.table.as_deref() == Some("t1")));// Find all columns referencing t1 in the root scopelet t1_cols = find_all_in_scope(&scope,&|c| c.table.as_deref() == Some("t1"));assert!(!t1_cols.is_empty());
Annotate Types
annotate_types infers and propagates SQL data types across all AST expression nodes
using schema metadata. It is the foundation for type-aware transpilation and validation.
Accessed via use sqlglot_rust::optimizer::annotate_types::{annotate_types, TypeAnnotations}
or the crate-level re-exports use sqlglot_rust::{annotate_types, TypeAnnotations}.
Function
Signature
Returns
Description
annotate_types
(stmt: &Statement, schema: &S) -> TypeAnnotations
TypeAnnotations
Annotate all expressions with inferred types
TypeAnnotations
Method
Signature
Returns
Description
get_type
(&self, expr: &Expr) -> Option<&DataType>
Option<&DataType>
Get the inferred type of an expression node
len
(&self) -> usize
usize
Number of annotated nodes
is_empty
(&self) -> bool
bool
Whether any annotations exist
Important:TypeAnnotations stores raw pointer references. The statement must not be
moved or dropped while the annotations are in use. Work with the statement by reference
after calling annotate_types.
Type Propagation Rules
Expression
Inferred Type
Rule
42 (integer literal)
Int / BigInt
Fits i32 → Int, otherwise BigInt
3.14 (decimal literal)
Double
Contains ., e, or E
'hello' (string literal)
Varchar
All string literals
TRUE / FALSE
Boolean
Boolean literals
NULL
Null
Null literal
Column(name)
schema type
Looked up from Schema
a + b (arithmetic)
coerced type
Wider of operand types
a > b (comparison)
Boolean
All comparison operators
a AND b (logical)
Boolean
Logical operators
a || b (concat)
Varchar
String concatenation
CAST(x AS T)
T
Target type
CASE WHEN ... THEN a ELSE b END
common type
Widest of all THEN/ELSE branches
COUNT(*)
BigInt
Always BigInt
SUM(int_col)
BigInt
Integer inputs → BigInt
SUM(decimal_col)
Decimal
Preserves precision/scale
AVG(x)
Double
Always Double
MIN(x) / MAX(x)
input type
Same as argument type
UPPER(x) / LOWER(x)
Varchar
String functions
LENGTH(x)
Int
Returns integer
EXISTS (...)
Boolean
Existence check
x BETWEEN a AND b
Boolean
Boolean predicate
x IN (...)
Boolean
Boolean predicate
x IS NULL
Boolean
Boolean predicate
EXTRACT(YEAR FROM x)
Int
Date part extraction
ROW_NUMBER() / RANK()
BigInt
Window ranking
LEAD(x) / LAG(x)
input type
Same as argument
JSON_EXTRACT(...)
Json
JSON type
JSON_EXTRACT_SCALAR(...)
Varchar
Text extraction
UDF
registered type
From schema.add_udf(name, type)
Numeric Coercion Precedence
Wider types supersede narrower ones in arithmetic expressions:
use sqlglot_rust::{parse,Dialect, annotate_types};use sqlglot_rust::ast::DataType;use sqlglot_rust::schema::{MappingSchema,Schema};letmut schema = MappingSchema::new(Dialect::Ansi);
schema.add_table(&["users"],vec![("id".to_string(),DataType::Int),("name".to_string(),DataType::Varchar(Some(255))),("salary".to_string(),DataType::Double),]).unwrap();let stmt = parse("SELECT id, name, salary * 1.1 FROM users WHERE id > 5",Dialect::Ansi).unwrap();let ann = annotate_types(&stmt,&schema);// Query types from the annotationsiflet sqlglot_rust::Statement::Select(sel) = &stmt {for col in&sel.columns{iflet sqlglot_rust::ast::SelectItem::Expr{ expr, .. } = col {ifletSome(dt) = ann.get_type(expr){println!("Column type: {:?}", dt);}}}}
Column Lineage
Column lineage tracking traces data flow from source columns through query
transformations to output columns. Essential for data governance, impact
analysis, and compliance.
Accessed via use sqlglot_rust::optimizer::lineage::{lineage, lineage_sql, LineageConfig, LineageError, LineageGraph, LineageNode}
or the crate-level re-exports use sqlglot_rust::{lineage, lineage_sql, LineageConfig, LineageError, LineageGraph, LineageNode}.
use sqlglot_rust::{parse,Dialect};use sqlglot_rust::optimizer::lineage::{lineage_sql,LineageConfig};use sqlglot_rust::schema::MappingSchema;use sqlglot_rust::ast::DataType;letmut schema = MappingSchema::new(Dialect::Ansi);
schema.add_table(&["orders"],vec![("id".to_string(),DataType::Int),("user_id".to_string(),DataType::Int),("amount".to_string(),DataType::Double),]).unwrap();let config = LineageConfig::new(Dialect::Ansi);let sql = "WITH totals AS (SELECT user_id, SUM(amount) AS total FROM orders GROUP BY user_id) \ SELECT user_id, total FROM totals";let graph = lineage_sql("total", sql,&schema,&config).unwrap();// Root node is the output columnassert_eq!(graph.node.name,"total");// Get source tableslet sources = graph.source_tables();assert!(sources.contains(&"orders".to_string()));// Generate visualizationprintln!("{}", graph.to_mermaid());
Query Planner
The planner module generates a logical execution plan (a DAG of steps) from a parsed SQL AST. This sits between the optimizer and the executor, providing a structured representation of how a query should be executed.
Plan / StepId
use sqlglot_rust::planner::{plan,Plan,StepId};use sqlglot_rust::{parse,Dialect};let ast = parse("SELECT a, b FROM t WHERE a > 1 ORDER BY b",Dialect::Ansi).unwrap();let p = plan(&ast).unwrap();// Inspect the planprintln!("Steps: {}", p.len());println!("Root: {:?}", p.root());println!("{p}");// Display shows all steps and dependencies
Method
Return Type
Description
plan(&Statement)
Result<Plan>
Build a plan from a parsed statement
Plan::root()
StepId
The root step that produces the final result
Plan::steps()
&[Step]
All steps in topological order
Plan::get(StepId)
Option<&Step>
Look up a step by ID
Plan::len()
usize
Number of steps
Plan::is_empty()
bool
Whether the plan has zero steps
Plan::to_mermaid()
String
Render as Mermaid flowchart
Plan::to_dot()
String
Render as DOT (Graphviz) digraph
Step Enum
Each step in the plan represents a logical operation.
pubstructProjection{pubexpr:Expr,// The expression being projectedpubalias:Option<String>,// Output alias (if any)}
Visualization (Mermaid / DOT)
use sqlglot_rust::planner::plan;use sqlglot_rust::{parse,Dialect};let ast = parse("SELECT a, SUM(b) FROM t JOIN u ON t.id = u.id WHERE a > 0 GROUP BY a ORDER BY a",Dialect::Ansi,).unwrap();let p = plan(&ast).unwrap();// Mermaid flowchart (for docs, GitHub, etc.)println!("{}", p.to_mermaid());// graph TD// step_0["Scan(t)"]// step_1["Scan(u)"]// step_0 --> step_2// step_1 --> step_2// step_2["Join(Inner)"]// ...// DOT / Graphviz digraphprintln!("{}", p.to_dot());// digraph plan {// rankdir=BT;// step_0 [label="Scan(t)"];// ...// }
AST Diff
Semantic comparison of SQL expression trees. Computes structured differences between two
parsed AST statements using a tree edit distance algorithm inspired by the Change Distiller
approach from Python sqlglot's diff.py.
Accessed via use sqlglot_rust::diff::{diff, diff_sql, ChangeAction, AstNode} or the
re-exported use sqlglot_rust::{diff_ast, diff_sql, ChangeAction, AstNode}.
A node inserted into target that was not in source
Keep
(AstNode, AstNode)
A node structurally identical in both trees
Move
(AstNode, AstNode)
A node moved to a different position in the tree
Update
(AstNode, AstNode)
A node in source replaced by a different node in target
AstNode Enum
Wraps AST nodes of different types for uniform diff output.
Variant
Description
Statement(Box<Statement>)
A full SQL statement
Expr(Expr)
An expression node
SelectItem(SelectItem)
A SELECT list item
JoinClause(JoinClause)
A JOIN clause
OrderByItem(OrderByItem)
An ORDER BY item
Cte(Box<Cte>)
A Common Table Expression
ColumnDef(ColumnDef)
A column definition (DDL)
TableConstraint(TableConstraint)
A table constraint (DDL)
Example:
use sqlglot_rust::{parse,Dialect};use sqlglot_rust::diff::{diff, diff_sql,ChangeAction};// Diff two parsed ASTslet source = parse("SELECT a, b FROM t WHERE a > 1",Dialect::Ansi).unwrap();let target = parse("SELECT a, c FROM t WHERE a > 2",Dialect::Ansi).unwrap();let changes = diff(&source,&target);for change in&changes {match change {ChangeAction::Keep(s, _t) => println!(" kept: {s}"),ChangeAction::Insert(n) => println!("+ insert: {n}"),ChangeAction::Remove(n) => println!("- remove: {n}"),ChangeAction::Update(s, t) => println!("~ update: {s} -> {t}"),ChangeAction::Move(s, t) => println!("⇄ move: {s} -> {t}"),}}// Or diff directly from SQL stringslet changes = diff_sql("SELECT a FROM t","SELECT a, b FROM t",Dialect::Ansi,).unwrap();assert!(changes.iter().any(|c| matches!(c,ChangeAction::Insert(_))));
C/C++ FFI API
The library exposes a C-compatible API via extern "C" functions, allowing sqlglot-rust to be used from C, C++, Python (ctypes/cffi), Go (cgo), or any language that supports the C calling convention.
Header
The C header is generated by cbindgen and placed at target/ffi/include/sqlglot.h (via make ffi or make ffi-header).
Build lineage for one requested output column using a schema and lineage configuration. Returns JSON or NULL on error.
sqlglot_version
const char *sqlglot_version(void)
Return the library version as a static string. Do not free.
sqlglot_free
void sqlglot_free(char *ptr)
Release an owned string returned by the API. NULL-safe.
Memory Management
The caller owns every non-NULLchar * returned by the API and must release it with sqlglot_free.
sqlglot_version returns a borrowed pointer to static storage and must not be freed.
Passing NULL to sqlglot_free is a safe no-op.
Dialect Parameter
All dialect / from_dialect / to_dialect parameters accept a null-terminated string matching one of the dialect names (case-insensitive). Pass NULL for ANSI SQL.
Build Targets
Make Target
Rust Target Triple
Platform
make ffi
(host)
Current machine
make ffi-macos-arm64
aarch64-apple-darwin
macOS ARM64
make ffi-macos-amd64
x86_64-apple-darwin
macOS x86-64
make ffi-linux-amd64
x86_64-unknown-linux-gnu
Linux x86-64
make ffi-linux-arm64
aarch64-unknown-linux-gnu
Linux ARM64
make ffi-all
all four
All platforms
Output Artefacts
File
Description
target/ffi/include/sqlglot.h
Auto-generated C header
target/ffi/lib/libsqlglot_rust.a
Static library
target/ffi/lib/libsqlglot_rust.so / .dylib
Shared (dynamic) library
SQL Execution Engine
The executor module provides an in-memory SQL execution engine that can run queries against Rust data structures. This is useful for testing SQL transformations, validating optimizer output, and unit-testing SQL pipelines without a real database.
Value Enum
Represents a single cell value in the execution engine.
Variant
Rust Type
Description
Null
—
SQL NULL
Boolean(bool)
bool
TRUE / FALSE
Int(i64)
i64
Integer numbers
Float(f64)
f64
Floating-point numbers
String(String)
String
Text values
Value implements Display, PartialEq, Eq, Hash, PartialOrd, Ord, Clone, and Serialize.
Table / Tables
use sqlglot_rust::executor::{Table,Tables};// A Table is a named collection of rowslet table = Table{columns:vec!["id".into(),"name".into(),"salary".into()],rows:vec![
vec![Value::Int(1),Value::String("Alice".into()),Value::Float(100000.0)],
vec![Value::Int(2),Value::String("Bob".into()),Value::Float(95000.0)],],};// Tables is a HashMap<String, Table>letmut tables:Tables = std::collections::HashMap::new();
tables.insert("employees".to_string(), table);
ResultSet
Returned by execute and execute_statement. Provides access to column names and rows.
Method
Return Type
Description
columns()
&[String]
Column names in the result
rows()
&[Vec<Value>]
All result rows
row_count()
usize
Number of rows
column_count()
usize
Number of columns
execute / execute_statement
use sqlglot_rust::executor::{execute,Value,Table,Tables};use std::collections::HashMap;letmut tables:Tables = HashMap::new();
tables.insert("employees".into(),Table{columns:vec!["name".into(),"department".into(),"salary".into()],rows:vec![
vec![Value::String("Alice".into()),Value::String("Engineering".into()),Value::Float(100000.0)],
vec![Value::String("Bob".into()),Value::String("Engineering".into()),Value::Float(95000.0)],
vec![Value::String("Carol".into()),Value::String("Marketing".into()),Value::Float(80000.0)],],});// Execute a SQL string directlylet result = execute("SELECT name, salary FROM employees WHERE department = 'Engineering' ORDER BY salary DESC",&tables,).unwrap();assert_eq!(result.row_count(),2);assert_eq!(result.rows()[0][0],Value::String("Alice".into()));