Skip to main content

sqlglot_rust/
lib.rs

1//! # sqlglot-rust
2//!
3//! A SQL parser, optimizer, and transpiler library written in Rust,
4//! inspired by Python's [sqlglot](https://github.com/tobymao/sqlglot).
5//!
6//! ## Features
7//!
8//! - Parse SQL strings into a structured AST
9//! - Generate SQL from AST nodes
10//! - Transpile between SQL dialects (30 dialects including MySQL, PostgreSQL, BigQuery, Snowflake, DuckDB, Hive, Spark, Presto, Trino, T-SQL, Oracle, ClickHouse, Redshift, and more)
11//! - Optimize SQL queries
12//! - CTEs, subqueries, UNION/INTERSECT/EXCEPT
13//! - Window functions, CAST, EXTRACT, EXISTS
14//! - Pretty-print SQL output
15//! - AST traversal (walk, find, transform)
16//! - AST diff for semantic SQL comparison
17//!
18//! ## Quick Start
19//!
20//! ```rust
21//! use sqlglot_rust::{parse, generate, transpile, Dialect};
22//!
23//! // Parse a SQL query
24//! let ast = parse("SELECT a, b FROM t WHERE a > 1", Dialect::Ansi).unwrap();
25//!
26//! // Generate SQL for a specific dialect
27//! let sql = generate(&ast, Dialect::Postgres);
28//! assert_eq!(sql, "SELECT a, b FROM t WHERE a > 1");
29//!
30//! // One-step transpile between dialects
31//! let result = transpile("SELECT a, b FROM t", Dialect::Ansi, Dialect::Postgres).unwrap();
32//! ```
33
34pub mod ast;
35pub mod builder;
36pub mod dialects;
37pub mod diff;
38pub mod errors;
39pub mod executor;
40pub mod ffi;
41pub mod generator;
42pub mod optimizer;
43pub mod parser;
44pub mod planner;
45pub mod schema;
46pub mod tokens;
47
48pub use ast::{CommentType, Expr, MergeClauseKind, QuoteStyle, Statement};
49pub use builder::{
50    ConditionBuilder,
51    SelectBuilder,
52    // Arithmetic helpers
53    add,
54    alias,
55    and_all,
56    between,
57    boolean,
58    // Operators and expressions
59    cast,
60    // Expression factory functions
61    column,
62    // Builders
63    condition,
64    condition_dialect,
65    div,
66    // Comparison helpers
67    eq,
68    exists,
69    func,
70    func_distinct,
71    gt,
72    gte,
73    in_list,
74    in_subquery,
75    is_not_null,
76    is_null,
77    like,
78    literal,
79    lt,
80    lte,
81    mul,
82    neq,
83    not,
84    not_in_list,
85    null,
86    or_all,
87    parse_condition,
88    parse_condition_dialect,
89    // Parse helpers
90    parse_expr,
91    parse_expr_dialect,
92    qualified_star,
93    select,
94    select_all,
95    select_distinct,
96    // Other helpers
97    star,
98    string_literal,
99    sub,
100    subquery,
101    table,
102    table_full,
103};
104pub use dialects::Dialect;
105pub use dialects::plugin::{
106    DialectPlugin, DialectRef, DialectRegistry, register_dialect, resolve_dialect, transpile_ext,
107    transpile_statements_ext,
108};
109pub use dialects::time::{
110    FormatConversionResult, TimeFormatStyle, TsqlStyleCode, format_time, format_time_dialect,
111    format_time_with_warnings,
112};
113pub use diff::{AstNode, ChangeAction, diff as diff_ast, diff_sql};
114pub use errors::SqlglotError;
115pub use generator::{generate, generate_pretty};
116pub use optimizer::annotate_types::{TypeAnnotations, annotate_types};
117pub use optimizer::lineage::{
118    LineageConfig, LineageError, LineageGraph, LineageNode, lineage, lineage_sql,
119};
120pub use optimizer::pushdown_predicates::pushdown_predicates;
121pub use optimizer::scope_analysis::{Scope, ScopeType, build_scope, find_all_in_scope};
122pub use parser::parse;
123pub use parser::{parse_statements_with_comments, parse_with_comments};
124pub use planner::{Plan, Projection, Step, StepId, plan};
125
126/// Validate that a transformed AST doesn't contain constructs unsupported by the target dialect.
127fn validate_dialect_support(stmt: &Statement, target: Dialect) -> errors::Result<()> {
128    use crate::ast::Expr;
129    use crate::dialects::Dialect::{Fabric, Tsql};
130
131    if !matches!(target, Tsql | Fabric) {
132        return Ok(());
133    }
134
135    // Walk the AST looking for unsupported constructs
136    let mut found_array = false;
137    fn check_expr(expr: &Expr, found: &mut bool) {
138        match expr {
139            Expr::ArrayLiteral(_) => {
140                *found = true;
141            }
142            _ => {
143                expr.walk(&mut |e| {
144                    if matches!(e, Expr::ArrayLiteral(_)) {
145                        *found = true;
146                        false
147                    } else {
148                        true
149                    }
150                });
151            }
152        }
153    }
154
155    match stmt {
156        Statement::Select(sel) => {
157            for item in &sel.columns {
158                if let ast::SelectItem::Expr { expr, .. } = item {
159                    check_expr(expr, &mut found_array);
160                }
161            }
162            if let Some(wh) = &sel.where_clause {
163                check_expr(wh, &mut found_array);
164            }
165        }
166        Statement::Expression(expr) => check_expr(expr, &mut found_array),
167        _ => {}
168    }
169
170    if found_array {
171        return Err(errors::SqlglotError::UnsupportedDialectFeature(
172            "ARRAY constructor has no T-SQL equivalent".to_string(),
173        ));
174    }
175
176    Ok(())
177}
178
179/// Transpile a SQL string from one dialect to another.
180///
181/// This is the primary high-level API, corresponding to Python sqlglot's
182/// `sqlglot.transpile()`.
183///
184/// # Example
185///
186/// ```rust
187/// use sqlglot_rust::{transpile, Dialect};
188///
189/// let result = transpile(
190///     "SELECT CAST(x AS INT) FROM t",
191///     Dialect::Ansi,
192///     Dialect::Postgres,
193/// ).unwrap();
194/// ```
195///
196/// # Errors
197///
198/// Returns a [`SqlglotError`] if parsing fails.
199pub fn transpile(
200    sql: &str,
201    read_dialect: Dialect,
202    write_dialect: Dialect,
203) -> errors::Result<String> {
204    let ast = parse(sql, read_dialect)?;
205    let transformed = dialects::transform(&ast, read_dialect, write_dialect);
206    validate_dialect_support(&transformed, write_dialect)?;
207    Ok(generate(&transformed, write_dialect))
208}
209
210/// Transpile a SQL string, returning multiple statements if the input
211/// contains semicolons.
212///
213/// # Errors
214///
215/// Returns a [`SqlglotError`] if parsing fails.
216pub fn transpile_statements(
217    sql: &str,
218    read_dialect: Dialect,
219    write_dialect: Dialect,
220) -> errors::Result<Vec<String>> {
221    let stmts = parser::parse_statements(sql, read_dialect)?;
222    let mut results = Vec::with_capacity(stmts.len());
223    for stmt in &stmts {
224        let transformed = dialects::transform(stmt, read_dialect, write_dialect);
225        results.push(generate(&transformed, write_dialect));
226    }
227    Ok(results)
228}
229
230/// Transpile a SQL string preserving comments through the pipeline.
231///
232/// # Errors
233///
234/// Returns a [`SqlglotError`] if parsing fails.
235pub fn transpile_with_comments(
236    sql: &str,
237    read_dialect: Dialect,
238    write_dialect: Dialect,
239) -> errors::Result<String> {
240    let ast = parse_with_comments(sql, read_dialect)?;
241    let transformed = dialects::transform(&ast, read_dialect, write_dialect);
242    Ok(generate(&transformed, write_dialect))
243}