Skip to main content

radixdb_sql/
parse.rs

1// Copyright 2026 RadixDB Contributors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::{ParseError, ParseErrors, Parser, Position, Statement};
16
17/// Parse SQL and return statements
18///
19/// This is the main entry point for parsing SQL strings.
20///
21/// # Arguments
22///
23/// * `sql` - The SQL string to parse
24///
25/// # Returns
26///
27/// * `Ok(Vec<Statement>)` - Successfully parsed statements
28/// * `Err(ParseErrors)` - Parse errors encountered
29///
30/// # Example
31///
32/// ```
33/// use radixdb_sql::parse_sql;
34///
35/// let statements = parse_sql("SELECT 1").unwrap();
36/// assert_eq!(statements.len(), 1);
37/// ```
38pub fn parse_sql(sql: &str) -> Result<Vec<Statement>, ParseErrors> {
39    if sql.starts_with('\u{feff}') {
40        return Err(ParseErrors::from_errors_with_sql(
41            vec![ParseError::new(
42                "UTF-8 BOM is not allowed in SQL source".to_string(),
43                Position::new(0, 1, 1),
44            )],
45            sql,
46        ));
47    }
48    if sql.trim().is_empty() {
49        return Err(ParseErrors::from_errors_with_sql(
50            vec![ParseError::new(
51                "No statements found in query".to_string(),
52                Position::new(0, 1, 1),
53            )],
54            sql,
55        ));
56    }
57
58    let mut parser = Parser::new(sql);
59    let program = parser.parse_program()?;
60
61    if program.statements.is_empty() {
62        return Err(ParseErrors::from_errors_with_sql(
63            vec![ParseError::new(
64                "No statements found in query".to_string(),
65                Position::new(0, 1, 1),
66            )],
67            sql,
68        ));
69    }
70
71    Ok(program.statements)
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn test_parse_simple_select() {
80        let result = parse_sql("SELECT 1");
81        assert!(result.is_ok());
82        let statements = result.unwrap();
83        assert_eq!(statements.len(), 1);
84    }
85
86    #[test]
87    fn test_parse_select_from() {
88        let result = parse_sql("SELECT * FROM users");
89        assert!(result.is_ok());
90        let statements = result.unwrap();
91        assert_eq!(statements.len(), 1);
92        match &statements[0] {
93            Statement::Select(s) => {
94                assert!(!s.distinct);
95                assert_eq!(s.columns.len(), 1);
96            }
97            _ => panic!("Expected SELECT statement"),
98        }
99    }
100
101    #[test]
102    fn test_parse_empty_string() {
103        let result = parse_sql("");
104        assert!(result.is_err());
105    }
106
107    #[test]
108    fn test_parse_whitespace_only() {
109        let result = parse_sql("   \n\t  ");
110        assert!(result.is_err());
111    }
112
113    #[test]
114    fn test_parse_select_with_where() {
115        let result = parse_sql("SELECT id, name FROM users WHERE id = 1");
116        assert!(result.is_ok());
117        let statements = result.unwrap();
118        assert_eq!(statements.len(), 1);
119        match &statements[0] {
120            Statement::Select(s) => {
121                assert_eq!(s.columns.len(), 2);
122                assert!(s.where_clause.is_some());
123            }
124            _ => panic!("Expected SELECT statement"),
125        }
126    }
127
128    #[test]
129    fn test_parse_insert() {
130        let result = parse_sql("INSERT INTO users (id, name) VALUES (1, 'Alice')");
131        assert!(result.is_ok());
132        let statements = result.unwrap();
133        assert_eq!(statements.len(), 1);
134        match &statements[0] {
135            Statement::Insert(s) => {
136                assert_eq!(s.table_name.value, "users");
137                assert_eq!(s.columns.len(), 2);
138                assert_eq!(s.values.len(), 1);
139            }
140            _ => panic!("Expected INSERT statement"),
141        }
142    }
143
144    #[test]
145    fn test_parse_update() {
146        let result = parse_sql("UPDATE users SET name = 'Bob' WHERE id = 1");
147        assert!(result.is_ok());
148        let statements = result.unwrap();
149        assert_eq!(statements.len(), 1);
150        match &statements[0] {
151            Statement::Update(s) => {
152                assert_eq!(s.table_name.value, "users");
153                assert!(s.where_clause.is_some());
154            }
155            _ => panic!("Expected UPDATE statement"),
156        }
157    }
158
159    #[test]
160    fn test_parse_delete() {
161        let result = parse_sql("DELETE FROM users WHERE id = 1");
162        assert!(result.is_ok());
163        let statements = result.unwrap();
164        assert_eq!(statements.len(), 1);
165        match &statements[0] {
166            Statement::Delete(s) => {
167                assert_eq!(s.table_name.value, "users");
168                assert!(s.where_clause.is_some());
169            }
170            _ => panic!("Expected DELETE statement"),
171        }
172    }
173
174    #[test]
175    fn test_parse_create_table() {
176        let result = parse_sql("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)");
177        assert!(result.is_ok());
178        let statements = result.unwrap();
179        assert_eq!(statements.len(), 1);
180        match &statements[0] {
181            Statement::CreateTable(s) => {
182                assert_eq!(s.table_name.value, "users");
183                assert_eq!(s.columns.len(), 2);
184            }
185            _ => panic!("Expected CREATE TABLE statement"),
186        }
187    }
188
189    #[test]
190    fn test_parse_complex_query() {
191        let result = parse_sql(
192            r#"
193            SELECT u.id, u.name, COUNT(o.id) as order_count
194            FROM users u
195            LEFT JOIN orders o ON u.id = o.user_id
196            WHERE u.active = TRUE
197            GROUP BY u.id, u.name
198            HAVING COUNT(o.id) > 0
199            ORDER BY order_count DESC
200            LIMIT 10
201        "#,
202        );
203        assert!(result.is_ok());
204    }
205
206    #[test]
207    fn test_parse_cte() {
208        let result = parse_sql("WITH temp AS (SELECT * FROM users) SELECT * FROM temp");
209        assert!(result.is_ok());
210        let statements = result.unwrap();
211        match &statements[0] {
212            Statement::Select(s) => {
213                assert!(s.with.is_some());
214            }
215            _ => panic!("Expected SELECT statement"),
216        }
217    }
218
219    #[test]
220    fn test_parse_transaction() {
221        let begin = parse_sql("BEGIN TRANSACTION").unwrap();
222        assert!(matches!(begin[0], Statement::Begin(_)));
223
224        let commit = parse_sql("COMMIT").unwrap();
225        assert!(matches!(commit[0], Statement::Commit(_)));
226
227        let rollback = parse_sql("ROLLBACK").unwrap();
228        assert!(matches!(rollback[0], Statement::Rollback(_)));
229    }
230}