vibesql_parser/
lib.rs

1//! SQL:1999 Parser crate.
2//!
3//! Provides tokenization and parsing of SQL statements into the shared AST.
4//!
5//! # Arena-allocated Parser
6//!
7//! For performance-critical code paths, the [`arena_parser`] module provides
8//! an arena-based parser that allocates AST nodes from a bump allocator.
9//!
10//! # Arena Fallback Parsing
11//!
12//! The [`parse_with_arena_fallback`] function provides optimized parsing by
13//! using arena allocation for supported statement types (SELECT) while falling
14//! back to standard heap allocation for other statements. This provides the
15//! best of both worlds: arena performance where it helps most (complex queries)
16//! and full feature support for all SQL statements.
17//!
18//! ```
19//! use vibesql_parser::parse_with_arena_fallback;
20//!
21//! let stmt = parse_with_arena_fallback("SELECT * FROM users").unwrap();
22//! // Uses arena parsing internally, converts to standard Statement
23//! ```
24
25pub mod arena_parser;
26
27mod keywords;
28mod lexer;
29mod parser;
30#[cfg(test)]
31mod tests;
32mod token;
33
34pub use keywords::Keyword;
35pub use lexer::{Lexer, LexerError, Span};
36pub use parser::{ParseError, Parser};
37pub use token::Token;
38use vibesql_ast::Statement;
39
40/// Parse SQL using arena allocation where supported, falling back to standard parsing.
41///
42/// This function provides optimized parsing by:
43/// 1. Detecting the statement type from the first token
44/// 2. Using arena-allocated parsing for SELECT statements
45/// 3. Converting arena AST to standard heap-allocated AST
46/// 4. Falling back to standard parsing for unsupported statement types
47///
48/// # Performance
49///
50/// Arena parsing can provide 10-15% improvement for complex SELECT statements
51/// due to reduced allocation overhead and better cache locality. The conversion
52/// to standard AST types adds minimal overhead.
53///
54/// # Supported Statement Types
55///
56/// Currently uses arena parsing for:
57/// - SELECT statements (including CTEs, subqueries, joins)
58///
59/// Falls back to standard parsing for:
60/// - INSERT, UPDATE, DELETE (arena support planned for future)
61/// - DDL statements (CREATE, ALTER, DROP)
62/// - Transaction statements (BEGIN, COMMIT, ROLLBACK)
63/// - Other SQL statements
64///
65/// # Example
66///
67/// ```
68/// use vibesql_parser::parse_with_arena_fallback;
69///
70/// // Uses arena parsing
71/// let select = parse_with_arena_fallback("SELECT * FROM users WHERE id = 1").unwrap();
72///
73/// // Falls back to standard parsing
74/// let insert = parse_with_arena_fallback("INSERT INTO users VALUES (1, 'Alice')").unwrap();
75/// ```
76pub fn parse_with_arena_fallback(sql: &str) -> Result<Statement, ParseError> {
77    // Tokenize to detect statement type
78    let mut lexer = Lexer::new(sql);
79    let tokens = lexer.tokenize().map_err(|e| ParseError { message: e.to_string() })?;
80
81    // Check first token to determine statement type
82    if let Some(first_token) = tokens.first() {
83        if matches!(
84            first_token,
85            Token::Keyword { keyword: Keyword::Select, .. }
86                | Token::Keyword { keyword: Keyword::With, .. }
87        ) {
88            // Use arena parsing for SELECT statements (including WITH CTEs)
89            match arena_parser::parse_select_to_owned(sql) {
90                Ok(select_stmt) => {
91                    return Ok(Statement::Select(Box::new(select_stmt)));
92                }
93                Err(_) => {
94                    // Arena parsing failed, fall back to standard parser
95                    // This can happen with edge cases the arena parser doesn't support yet
96                }
97            }
98        }
99    }
100
101    // Fall back to standard parser for all other statements
102    // or if arena parsing failed
103    Parser::parse_sql(sql)
104}
105
106#[cfg(test)]
107mod arena_fallback_tests {
108    use super::*;
109
110    #[test]
111    fn test_arena_fallback_simple_select() {
112        let result = parse_with_arena_fallback("SELECT * FROM users");
113        assert!(result.is_ok());
114        assert!(matches!(result.unwrap(), Statement::Select(_)));
115    }
116
117    #[test]
118    fn test_arena_fallback_select_with_where() {
119        let result = parse_with_arena_fallback("SELECT id, name FROM users WHERE active = TRUE");
120        assert!(result.is_ok());
121        assert!(matches!(result.unwrap(), Statement::Select(_)));
122    }
123
124    #[test]
125    fn test_arena_fallback_select_with_cte() {
126        let result = parse_with_arena_fallback(
127            "WITH active_users AS (SELECT * FROM users WHERE active = TRUE) \
128             SELECT * FROM active_users",
129        );
130        assert!(result.is_ok());
131        assert!(matches!(result.unwrap(), Statement::Select(_)));
132    }
133
134    #[test]
135    fn test_arena_fallback_insert() {
136        let result = parse_with_arena_fallback("INSERT INTO users (id, name) VALUES (1, 'Alice')");
137        assert!(result.is_ok());
138        assert!(matches!(result.unwrap(), Statement::Insert(_)));
139    }
140
141    #[test]
142    fn test_arena_fallback_update() {
143        let result = parse_with_arena_fallback("UPDATE users SET name = 'Bob' WHERE id = 1");
144        assert!(result.is_ok());
145        assert!(matches!(result.unwrap(), Statement::Update(_)));
146    }
147
148    #[test]
149    fn test_arena_fallback_delete() {
150        let result = parse_with_arena_fallback("DELETE FROM users WHERE id = 1");
151        assert!(result.is_ok());
152        assert!(matches!(result.unwrap(), Statement::Delete(_)));
153    }
154
155    #[test]
156    fn test_arena_fallback_create_table() {
157        let result = parse_with_arena_fallback("CREATE TABLE users (id INT PRIMARY KEY)");
158        assert!(result.is_ok());
159        assert!(matches!(result.unwrap(), Statement::CreateTable(_)));
160    }
161
162    #[test]
163    fn test_arena_fallback_qualified_wildcard() {
164        let result = parse_with_arena_fallback("SELECT t1.* FROM t1");
165        assert!(result.is_ok());
166        if let Statement::Select(select) = result.unwrap() {
167            assert_eq!(select.select_list.len(), 1);
168            match &select.select_list[0] {
169                vibesql_ast::SelectItem::QualifiedWildcard { qualifier, alias: _ } => {
170                    assert_eq!(qualifier, "t1");
171                }
172                other => panic!("Expected QualifiedWildcard, got {:?}", other),
173            }
174        } else {
175            panic!("Expected SELECT statement");
176        }
177    }
178}