1#![no_std]
42#![forbid(unsafe_code)]
43extern crate alloc;
44
45use alloc::vec::Vec;
46use lexer::Token;
47use parser::Parser;
48mod alter;
49mod create;
50mod data_type;
51mod delete;
52mod drop;
53mod expression;
54mod identifier;
55mod insert_replace;
56mod issue;
57mod keywords;
58mod lexer;
59mod parser;
60mod qualified_name;
61mod rename;
62mod select;
63mod span;
64mod sstring;
65mod statement;
66mod truncate;
67mod update;
68mod with_query;
69
70pub use data_type::{DataType, DataTypeProperty, Type};
71pub use identifier::Identifier;
72pub use issue::{Fragment, Issue, IssueHandle, Issues, Level};
73pub use qualified_name::QualifiedName;
74pub use span::{OptSpanned, Span, Spanned};
75pub use sstring::SString;
76pub use statement::{Statement, Union, UnionType, UnionWith};
77
78pub use alter::{
79 AlterColumnAction, AlterSpecification, AlterTable, ForeignKeyOn, ForeignKeyOnAction,
80 ForeignKeyOnType, IndexCol, IndexOption, IndexType,
81};
82pub use create::{
83 CreateAlgorithm, CreateDefinition, CreateFunction, CreateOption, CreateTable, CreateTrigger,
84 CreateView, TableOption,
85};
86pub use delete::{Delete, DeleteFlag};
87pub use drop::{
88 DropDatabase, DropEvent, DropFunction, DropIndex, DropProcedure, DropServer, DropTable,
89 DropTrigger, DropView,
90};
91pub use expression::{
92 BinaryOperator, Expression, Function, IdentifierPart, Is, UnaryOperator, Variable, When,
93};
94pub use insert_replace::{
95 InsertReplace, InsertReplaceFlag, InsertReplaceOnDuplicateKeyUpdate, InsertReplaceSet,
96 InsertReplaceSetPair, InsertReplaceType, OnConflict, OnConflictAction, OnConflictTarget,
97};
98pub use rename::{RenameTable, TableToTable};
99pub use select::{
100 IndexHint, IndexHintFor, IndexHintType, IndexHintUse, JoinSpecification, JoinType, Select,
101 SelectExpr, SelectFlag, TableReference,
102};
103pub use truncate::TruncateTable;
104pub use update::{Update, UpdateFlag};
105pub use with_query::{WithBlock, WithQuery};
106
107#[derive(Clone, Debug)]
109pub enum SQLDialect {
110 MariaDB,
112 PostgreSQL,
113 Sqlite,
114}
115
116impl SQLDialect {
117 pub fn is_postgresql(&self) -> bool {
118 matches!(self, SQLDialect::PostgreSQL)
119 }
120
121 pub fn is_maria(&self) -> bool {
122 matches!(self, SQLDialect::MariaDB)
123 }
124
125 pub fn is_sqlite(&self) -> bool {
126 matches!(self, SQLDialect::Sqlite)
127 }
128}
129
130#[derive(Clone, Debug)]
132pub enum SQLArguments {
133 None,
135 Percent,
137 QuestionMark,
139 Dollar,
141}
142
143#[derive(Clone, Debug)]
145pub struct ParseOptions {
146 dialect: SQLDialect,
147 arguments: SQLArguments,
148 warn_unquoted_identifiers: bool,
149 warn_none_capital_keywords: bool,
150 list_hack: bool,
151}
152
153impl Default for ParseOptions {
154 fn default() -> Self {
155 Self {
156 dialect: SQLDialect::MariaDB,
157 arguments: SQLArguments::None,
158 warn_none_capital_keywords: false,
159 warn_unquoted_identifiers: false,
160 list_hack: false,
161 }
162 }
163}
164
165impl ParseOptions {
166 pub fn new() -> Self {
167 Default::default()
168 }
169
170 pub fn dialect(self, dialect: SQLDialect) -> Self {
172 Self { dialect, ..self }
173 }
174
175 pub fn get_dialect(&self) -> SQLDialect {
176 self.dialect.clone()
177 }
178
179 pub fn arguments(self, arguments: SQLArguments) -> Self {
181 Self { arguments, ..self }
182 }
183
184 pub fn warn_unquoted_identifiers(self, warn_unquoted_identifiers: bool) -> Self {
186 Self {
187 warn_unquoted_identifiers,
188 ..self
189 }
190 }
191
192 pub fn warn_none_capital_keywords(self, warn_none_capital_keywords: bool) -> Self {
194 Self {
195 warn_none_capital_keywords,
196 ..self
197 }
198 }
199
200 pub fn list_hack(self, list_hack: bool) -> Self {
202 Self { list_hack, ..self }
203 }
204}
205
206#[macro_export]
208macro_rules! issue_ice {
209 ( $issues: expr, $spanned:expr ) => {{
210 $issues.err(
211 alloc::format!("Internal compiler error in {}:{}", file!(), line!()),
212 $spanned,
213 );
214 }};
215}
216
217#[macro_export]
219macro_rules! issue_todo {
220 ( $issues: expr, $spanned:expr ) => {{
221 $issues.err(
222 alloc::format!("Not yet implemented {}:{}", file!(), line!()),
223 $spanned,
224 );
225 }};
226}
227
228pub fn parse_statements<'a>(
233 src: &'a str,
234 issues: &mut Issues<'a>,
235 options: &ParseOptions,
236) -> Vec<Statement<'a>> {
237 let mut parser = Parser::new(src, issues, options);
238 statement::parse_statements(&mut parser)
239}
240
241pub fn parse_statement<'a>(
246 src: &'a str,
247 issues: &mut Issues<'a>,
248 options: &ParseOptions,
249) -> Option<Statement<'a>> {
250 let mut parser = Parser::new(src, issues, options);
251 match statement::parse_statement(&mut parser) {
252 Ok(Some(v)) => {
253 if parser.token != Token::Eof {
254 parser.expected_error("Unexpected token after statement")
255 }
256 Some(v)
257 }
258 Ok(None) => {
259 parser.expected_error("Statement");
260 None
261 }
262 Err(_) => None,
263 }
264}
265
266#[test]
267pub fn test_parse_alter_sql() {
268 let sql = "ALTER TABLE `test` ADD COLUMN `test1` VARCHAR (128) NULL DEFAULT NULL";
269 let options = ParseOptions::new()
270 .dialect(SQLDialect::MariaDB)
271 .arguments(SQLArguments::QuestionMark)
272 .warn_unquoted_identifiers(false);
273
274 let mut issues = Issues::new(sql);
275 parse_statement(sql, &mut issues, &options);
276 assert!(issues.is_ok(), "{}", issues);
277}
278
279#[test]
280pub fn test_parse_delete_sql_with_schema() {
281 let sql = "DROP TABLE IF EXISTS `test_schema`.`test`";
282 let options = ParseOptions::new()
283 .dialect(SQLDialect::MariaDB)
284 .arguments(SQLArguments::QuestionMark)
285 .warn_unquoted_identifiers(false);
286
287 let mut issues = Issues::new(sql);
288 parse_statement(sql, &mut issues, &options);
289 assert!(issues.is_ok(), "{}", issues);
290}
291#[test]
292pub fn parse_create_index_sql_with_schema() {
293 let sql = "CREATE INDEX `idx_test` ON test_schema.test(`col_test`)";
294 let options = ParseOptions::new()
295 .dialect(SQLDialect::MariaDB)
296 .arguments(SQLArguments::QuestionMark)
297 .warn_unquoted_identifiers(false);
298
299 let mut issues = Issues::new(sql);
300 parse_statement(sql, &mut issues, &options);
301 assert!(issues.is_ok(), "{}", issues);
302}
303
304#[test]
305pub fn parse_drop_index_sql_with_schema() {
306 let sql = "DROP INDEX `idx_test` ON test_schema.test";
307 let options = ParseOptions::new()
308 .dialect(SQLDialect::MariaDB)
309 .arguments(SQLArguments::QuestionMark)
310 .warn_unquoted_identifiers(false);
311
312 let mut issues = Issues::new(sql);
313 let _result = parse_statement(sql, &mut issues, &options);
314 assert!(issues.is_ok(), "{}", issues);
316}
317
318#[test]
319pub fn parse_create_view_sql_with_schema() {
320 let sql =
321 "CREATE OR REPLACE VIEW `test_schema`.`view_test` AS SELECT * FROM `test_schema`.`test`";
322 let options = ParseOptions::new()
323 .dialect(SQLDialect::MariaDB)
324 .arguments(SQLArguments::QuestionMark)
325 .warn_unquoted_identifiers(false);
326
327 let mut issues = Issues::new(sql);
328 let _result = parse_statement(sql, &mut issues, &options);
329 assert!(issues.is_ok(), "{}", issues);
331}
332
333#[test]
334pub fn parse_drop_view_sql_with_schema() {
335 let sql = "DROP VIEW `test_schema`.`view_test`";
336 let options = ParseOptions::new()
337 .dialect(SQLDialect::MariaDB)
338 .arguments(SQLArguments::QuestionMark)
339 .warn_unquoted_identifiers(false);
340
341 let mut issues = Issues::new(sql);
342 let _result = parse_statement(sql, &mut issues, &options);
343 assert!(issues.is_ok(), "{}", issues);
345}
346
347#[test]
348pub fn parse_truncate_table_sql_with_schema() {
349 let sql = "TRUNCATE TABLE `test_schema`.`table_test`";
350 let options = ParseOptions::new()
351 .dialect(SQLDialect::MariaDB)
352 .arguments(SQLArguments::QuestionMark)
353 .warn_unquoted_identifiers(false);
354
355 let mut issues = Issues::new(sql);
356 let _result = parse_statement(sql, &mut issues, &options);
357 assert!(issues.is_ok(), "{}", issues);
359}
360
361#[test]
362pub fn parse_rename_table_sql_with_schema() {
363 let sql = "RENAME TABLE `test_schema`.`table_test` To `test_schema`.`table_new_test`";
364 let options = ParseOptions::new()
365 .dialect(SQLDialect::MariaDB)
366 .arguments(SQLArguments::QuestionMark)
367 .warn_unquoted_identifiers(false);
368
369 let mut issues = Issues::new(sql);
370 let _result = parse_statement(sql, &mut issues, &options);
371 assert!(issues.is_ok(), "{}", issues);
373}
374
375#[test]
376pub fn parse_with_statement() {
377 let sql = "
378 WITH monkeys AS (DELETE FROM thing RETURNING id),
379 baz AS (SELECT id FROM cats WHERE comp IN (monkeys))
380 DELETE FROM dogs WHERE cat IN (cats)";
381 let options = ParseOptions::new()
382 .dialect(SQLDialect::PostgreSQL)
383 .arguments(SQLArguments::QuestionMark)
384 .warn_unquoted_identifiers(false);
385
386 let mut issues = Issues::new(sql);
387 let _result = parse_statement(sql, &mut issues, &options);
388 assert!(issues.is_ok(), "{}", issues);
390}
391
392#[test]
393pub fn parse_use_index() {
394 let sql = "SELECT `a` FROM `b` FORCE INDEX FOR GROUP BY (`b`, `c`)";
395 let options = ParseOptions::new()
396 .dialect(SQLDialect::MariaDB)
397 .arguments(SQLArguments::QuestionMark)
398 .warn_unquoted_identifiers(false);
399
400 let mut issues = Issues::new(sql);
401 let _result = parse_statement(sql, &mut issues, &options);
402 assert!(issues.is_ok(), "{}", issues);
403}