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, TimeUnit, UnaryOperator, Variable,
93 When,
94};
95pub use insert_replace::{
96 InsertReplace, InsertReplaceFlag, InsertReplaceOnDuplicateKeyUpdate, InsertReplaceSet,
97 InsertReplaceSetPair, InsertReplaceType, OnConflict, OnConflictAction, OnConflictTarget,
98};
99pub use rename::{RenameTable, TableToTable};
100pub use select::{
101 IndexHint, IndexHintFor, IndexHintType, IndexHintUse, JoinSpecification, JoinType, Select,
102 SelectExpr, SelectFlag, TableReference,
103};
104pub use truncate::TruncateTable;
105pub use update::{Update, UpdateFlag};
106pub use with_query::{WithBlock, WithQuery};
107
108#[derive(Clone, Debug)]
110pub enum SQLDialect {
111 MariaDB,
113 PostgreSQL,
114 Sqlite,
115}
116
117impl SQLDialect {
118 pub fn is_postgresql(&self) -> bool {
119 matches!(self, SQLDialect::PostgreSQL)
120 }
121
122 pub fn is_maria(&self) -> bool {
123 matches!(self, SQLDialect::MariaDB)
124 }
125
126 pub fn is_sqlite(&self) -> bool {
127 matches!(self, SQLDialect::Sqlite)
128 }
129}
130
131#[derive(Clone, Debug)]
133pub enum SQLArguments {
134 None,
136 Percent,
138 QuestionMark,
140 Dollar,
142}
143
144#[derive(Clone, Debug)]
146pub struct ParseOptions {
147 dialect: SQLDialect,
148 arguments: SQLArguments,
149 warn_unquoted_identifiers: bool,
150 warn_none_capital_keywords: bool,
151 list_hack: bool,
152}
153
154impl Default for ParseOptions {
155 fn default() -> Self {
156 Self {
157 dialect: SQLDialect::MariaDB,
158 arguments: SQLArguments::None,
159 warn_none_capital_keywords: false,
160 warn_unquoted_identifiers: false,
161 list_hack: false,
162 }
163 }
164}
165
166impl ParseOptions {
167 pub fn new() -> Self {
168 Default::default()
169 }
170
171 pub fn dialect(self, dialect: SQLDialect) -> Self {
173 Self { dialect, ..self }
174 }
175
176 pub fn get_dialect(&self) -> SQLDialect {
177 self.dialect.clone()
178 }
179
180 pub fn arguments(self, arguments: SQLArguments) -> Self {
182 Self { arguments, ..self }
183 }
184
185 pub fn warn_unquoted_identifiers(self, warn_unquoted_identifiers: bool) -> Self {
187 Self {
188 warn_unquoted_identifiers,
189 ..self
190 }
191 }
192
193 pub fn warn_none_capital_keywords(self, warn_none_capital_keywords: bool) -> Self {
195 Self {
196 warn_none_capital_keywords,
197 ..self
198 }
199 }
200
201 pub fn list_hack(self, list_hack: bool) -> Self {
203 Self { list_hack, ..self }
204 }
205}
206
207#[macro_export]
209macro_rules! issue_ice {
210 ( $issues: expr, $spanned:expr ) => {{
211 $issues.err(
212 alloc::format!("Internal compiler error in {}:{}", file!(), line!()),
213 $spanned,
214 );
215 }};
216}
217
218#[macro_export]
220macro_rules! issue_todo {
221 ( $issues: expr, $spanned:expr ) => {{
222 $issues.err(
223 alloc::format!("Not yet implemented {}:{}", file!(), line!()),
224 $spanned,
225 );
226 }};
227}
228
229pub fn parse_statements<'a>(
234 src: &'a str,
235 issues: &mut Issues<'a>,
236 options: &ParseOptions,
237) -> Vec<Statement<'a>> {
238 let mut parser = Parser::new(src, issues, options);
239 statement::parse_statements(&mut parser)
240}
241
242pub fn parse_statement<'a>(
247 src: &'a str,
248 issues: &mut Issues<'a>,
249 options: &ParseOptions,
250) -> Option<Statement<'a>> {
251 let mut parser = Parser::new(src, issues, options);
252 match statement::parse_statement(&mut parser) {
253 Ok(Some(v)) => {
254 if parser.token != Token::Eof {
255 parser.expected_error("Unexpected token after statement")
256 }
257 Some(v)
258 }
259 Ok(None) => {
260 parser.expected_error("Statement");
261 None
262 }
263 Err(_) => None,
264 }
265}
266
267#[test]
268pub fn test_parse_alter_sql() {
269 let sql = "ALTER TABLE `test` ADD COLUMN `test1` VARCHAR (128) NULL DEFAULT NULL";
270 let options = ParseOptions::new()
271 .dialect(SQLDialect::MariaDB)
272 .arguments(SQLArguments::QuestionMark)
273 .warn_unquoted_identifiers(false);
274
275 let mut issues = Issues::new(sql);
276 parse_statement(sql, &mut issues, &options);
277 assert!(issues.is_ok(), "{}", issues);
278}
279
280#[test]
281pub fn test_parse_delete_sql_with_schema() {
282 let sql = "DROP TABLE IF EXISTS `test_schema`.`test`";
283 let options = ParseOptions::new()
284 .dialect(SQLDialect::MariaDB)
285 .arguments(SQLArguments::QuestionMark)
286 .warn_unquoted_identifiers(false);
287
288 let mut issues = Issues::new(sql);
289 parse_statement(sql, &mut issues, &options);
290 assert!(issues.is_ok(), "{}", issues);
291}
292
293#[test]
294pub fn parse_create_index_sql_with_schema() {
295 let sql = "CREATE INDEX `idx_test` ON test_schema.test(`col_test`)";
296 let options = ParseOptions::new()
297 .dialect(SQLDialect::MariaDB)
298 .arguments(SQLArguments::QuestionMark)
299 .warn_unquoted_identifiers(false);
300
301 let mut issues = Issues::new(sql);
302 parse_statement(sql, &mut issues, &options);
303 assert!(issues.is_ok(), "{}", issues);
304}
305
306#[test]
307pub fn parse_create_index_sql_with_opclass() {
308 let sql = "CREATE INDEX idx_test ON test(path text_pattern_ops)";
309 let options = ParseOptions::new()
310 .dialect(SQLDialect::PostgreSQL)
311 .arguments(SQLArguments::Dollar)
312 .warn_unquoted_identifiers(false);
313
314 let mut issues = Issues::new(sql);
315 parse_statement(sql, &mut issues, &options);
316 assert!(issues.is_ok(), "{}", issues);
317}
318
319#[test]
320pub fn parse_drop_index_sql_with_schema() {
321 let sql = "DROP INDEX `idx_test` ON 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_create_view_sql_with_schema() {
335 let sql =
336 "CREATE OR REPLACE VIEW `test_schema`.`view_test` AS SELECT * FROM `test_schema`.`test`";
337 let options = ParseOptions::new()
338 .dialect(SQLDialect::MariaDB)
339 .arguments(SQLArguments::QuestionMark)
340 .warn_unquoted_identifiers(false);
341
342 let mut issues = Issues::new(sql);
343 let _result = parse_statement(sql, &mut issues, &options);
344 assert!(issues.is_ok(), "{}", issues);
346}
347
348#[test]
349pub fn parse_drop_view_sql_with_schema() {
350 let sql = "DROP VIEW `test_schema`.`view_test`";
351 let options = ParseOptions::new()
352 .dialect(SQLDialect::MariaDB)
353 .arguments(SQLArguments::QuestionMark)
354 .warn_unquoted_identifiers(false);
355
356 let mut issues = Issues::new(sql);
357 let _result = parse_statement(sql, &mut issues, &options);
358 assert!(issues.is_ok(), "{}", issues);
360}
361
362#[test]
363pub fn parse_truncate_table_sql_with_schema() {
364 let sql = "TRUNCATE TABLE `test_schema`.`table_test`";
365 let options = ParseOptions::new()
366 .dialect(SQLDialect::MariaDB)
367 .arguments(SQLArguments::QuestionMark)
368 .warn_unquoted_identifiers(false);
369
370 let mut issues = Issues::new(sql);
371 let _result = parse_statement(sql, &mut issues, &options);
372 assert!(issues.is_ok(), "{}", issues);
374}
375
376#[test]
377pub fn parse_rename_table_sql_with_schema() {
378 let sql = "RENAME TABLE `test_schema`.`table_test` To `test_schema`.`table_new_test`";
379 let options = ParseOptions::new()
380 .dialect(SQLDialect::MariaDB)
381 .arguments(SQLArguments::QuestionMark)
382 .warn_unquoted_identifiers(false);
383
384 let mut issues = Issues::new(sql);
385 let _result = parse_statement(sql, &mut issues, &options);
386 assert!(issues.is_ok(), "{}", issues);
388}
389
390#[test]
391pub fn parse_with_statement() {
392 let sql = "
393 WITH monkeys AS (DELETE FROM thing RETURNING id),
394 baz AS (SELECT id FROM cats WHERE comp IN (monkeys))
395 DELETE FROM dogs WHERE cat IN (cats)";
396 let options = ParseOptions::new()
397 .dialect(SQLDialect::PostgreSQL)
398 .arguments(SQLArguments::QuestionMark)
399 .warn_unquoted_identifiers(false);
400
401 let mut issues = Issues::new(sql);
402 let _result = parse_statement(sql, &mut issues, &options);
403 assert!(issues.is_ok(), "{}", issues);
405}
406
407#[test]
408pub fn parse_use_index() {
409 let sql = "SELECT `a` FROM `b` FORCE INDEX FOR GROUP BY (`b`, `c`)";
410 let options = ParseOptions::new()
411 .dialect(SQLDialect::MariaDB)
412 .arguments(SQLArguments::QuestionMark)
413 .warn_unquoted_identifiers(false);
414
415 let mut issues = Issues::new(sql);
416 let _result = parse_statement(sql, &mut issues, &options);
417 assert!(issues.is_ok(), "{}", issues);
418}