Skip to main content

polyglot_sql/
lib.rs

1//! Polyglot Core - SQL parsing and dialect translation library
2//!
3//! This library provides the core functionality for parsing SQL statements,
4//! building an abstract syntax tree (AST), and generating SQL in different dialects.
5//!
6//! # Architecture
7//!
8//! The library follows a pipeline architecture:
9//! 1. **Tokenizer** - Converts SQL string to token stream
10//! 2. **Parser** - Builds AST from tokens
11//! 3. **Generator** - Converts AST back to SQL string
12//!
13//! Each stage can be customized per dialect.
14
15pub mod builder;
16pub mod dialects;
17pub mod diff;
18pub mod error;
19pub mod expressions;
20pub mod generator;
21pub mod helper;
22pub mod lineage;
23pub mod optimizer;
24pub mod parser;
25pub mod planner;
26pub mod resolver;
27pub mod schema;
28pub mod scope;
29pub mod time;
30pub mod tokens;
31pub mod transforms;
32pub mod traversal;
33pub mod trie;
34
35pub use dialects::{Dialect, DialectType};
36pub use error::{Error, Result, ValidationError, ValidationResult, ValidationSeverity};
37pub use expressions::Expression;
38pub use generator::Generator;
39pub use helper::{
40    csv, find_new_name, is_date_unit, is_float, is_int, is_iso_date, is_iso_datetime,
41    merge_ranges, name_sequence, seq_get, split_num_words, tsort, while_changing, DATE_UNITS,
42};
43pub use parser::Parser;
44pub use resolver::{is_column_ambiguous, resolve_column, Resolver, ResolverError, ResolverResult};
45pub use schema::{ensure_schema, from_simple_map, normalize_name, MappingSchema, Schema, SchemaError};
46pub use scope::{
47    build_scope, find_all_in_scope, find_in_scope, traverse_scope, walk_in_scope, ColumnRef, Scope,
48    ScopeType, SourceInfo,
49};
50pub use time::{format_time, is_valid_timezone, subsecond_precision, TIMEZONES};
51pub use tokens::{Token, TokenType, Tokenizer};
52pub use traversal::{
53    contains_aggregate, contains_subquery, contains_window_function, find_ancestor, find_parent,
54    get_columns, get_tables, is_aggregate, is_column, is_function, is_literal, is_select,
55    is_subquery, is_window_function, transform, transform_map, BfsIter, DfsIter, ExpressionWalk,
56    ParentInfo, TreeContext,
57};
58pub use trie::{new_trie, new_trie_from_keys, Trie, TrieResult};
59pub use optimizer::{annotate_types, TypeAnnotator, TypeCoercionClass};
60
61/// Transpile SQL from one dialect to another.
62///
63/// # Arguments
64/// * `sql` - The SQL string to transpile
65/// * `read` - The source dialect to parse with
66/// * `write` - The target dialect to generate
67///
68/// # Returns
69/// A vector of transpiled SQL statements
70///
71/// # Example
72/// ```
73/// use polyglot_sql::{transpile, DialectType};
74///
75/// let result = transpile(
76///     "SELECT EPOCH_MS(1618088028295)",
77///     DialectType::DuckDB,
78///     DialectType::Hive
79/// );
80/// ```
81pub fn transpile(sql: &str, read: DialectType, write: DialectType) -> Result<Vec<String>> {
82    let read_dialect = Dialect::get(read);
83    let write_dialect = Dialect::get(write);
84
85    let expressions = read_dialect.parse(sql)?;
86
87    expressions
88        .into_iter()
89        .map(|expr| {
90            let transformed = write_dialect.transform(expr)?;
91            write_dialect.generate(&transformed)
92        })
93        .collect()
94}
95
96/// Parse SQL into an AST.
97///
98/// # Arguments
99/// * `sql` - The SQL string to parse
100/// * `dialect` - The dialect to use for parsing
101///
102/// # Returns
103/// A vector of parsed expressions
104pub fn parse(sql: &str, dialect: DialectType) -> Result<Vec<Expression>> {
105    let d = Dialect::get(dialect);
106    d.parse(sql)
107}
108
109/// Parse a single SQL statement.
110///
111/// # Arguments
112/// * `sql` - The SQL string containing a single statement
113/// * `dialect` - The dialect to use for parsing
114///
115/// # Returns
116/// The parsed expression, or an error if multiple statements found
117pub fn parse_one(sql: &str, dialect: DialectType) -> Result<Expression> {
118    let mut expressions = parse(sql, dialect)?;
119
120    if expressions.len() != 1 {
121        return Err(Error::Parse(format!(
122            "Expected 1 statement, found {}",
123            expressions.len()
124        )));
125    }
126
127    Ok(expressions.remove(0))
128}
129
130/// Generate SQL from an AST.
131///
132/// # Arguments
133/// * `expression` - The expression to generate SQL from
134/// * `dialect` - The target dialect
135///
136/// # Returns
137/// The generated SQL string
138pub fn generate(expression: &Expression, dialect: DialectType) -> Result<String> {
139    let d = Dialect::get(dialect);
140    d.generate(expression)
141}
142
143/// Validate SQL syntax.
144///
145/// # Arguments
146/// * `sql` - The SQL string to validate
147/// * `dialect` - The dialect to use for validation
148///
149/// # Returns
150/// A validation result with any errors found
151pub fn validate(sql: &str, dialect: DialectType) -> ValidationResult {
152    let d = Dialect::get(dialect);
153    match d.parse(sql) {
154        Ok(_) => ValidationResult::success(),
155        Err(e) => {
156            let error = match &e {
157                Error::Syntax {
158                    message,
159                    line,
160                    column,
161                } => ValidationError::error(message.clone(), "E001").with_location(*line, *column),
162                Error::Tokenize {
163                    message,
164                    line,
165                    column,
166                } => ValidationError::error(message.clone(), "E002").with_location(*line, *column),
167                Error::Parse(msg) => ValidationError::error(msg.clone(), "E003"),
168                _ => ValidationError::error(e.to_string(), "E000"),
169            };
170            ValidationResult::with_errors(vec![error])
171        }
172    }
173}