Skip to main content

qusql_parse/
lib.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License at
4//
5// http://www.apache.org/licenses/LICENSE-2.0
6//
7// Unless required by applicable law or agreed to in writing, software
8// distributed under the License is distributed on an "AS IS" BASIS,
9// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10// See the License for the specific language governing permissions and
11// limitations under the License.
12
13//! Parse SQL into an AST
14//!
15//! This crate provides an lexer and parser that can parse SQL
16//! into an Abstract Syntax Tree (AST). Currently primarily focused
17//! on MariaDB/Mysql.
18//!
19//! Example code:
20//!
21//! ```
22//! use qusql_parse::{SQLDialect, SQLArguments, ParseOptions, parse_statement, Issues};
23//!
24//! let options = ParseOptions::new()
25//!     .dialect(SQLDialect::MariaDB)
26//!     .arguments(SQLArguments::QuestionMark)
27//!     .warn_unquoted_identifiers(true);
28//!
29//!
30//! let sql = "SELECT `monkey`,
31//!            FROM `t1` LEFT JOIN `t2` ON `t2`.`id` = `t1.two`
32//!            WHERE `t1`.`id` = ?";
33//! let mut issues = Issues::new(sql);
34//! let ast = parse_statement(sql, &mut issues, &options);
35//!
36//! println!("{}", issues);
37//! println!("AST: {:#?}", ast);
38//! ```
39//!
40
41#![no_std]
42extern crate alloc;
43
44use alloc::vec::Vec;
45use lexer::Token;
46use parser::Parser;
47mod alter_role;
48mod alter_table;
49mod alter_type;
50mod copy;
51mod create;
52mod create_constraint_trigger;
53mod create_function;
54mod create_index;
55mod create_option;
56mod create_role;
57mod create_table;
58mod create_trigger;
59mod create_view;
60mod data_type;
61mod delete;
62mod drop;
63mod expression;
64mod flush;
65mod function_expression;
66mod identifier;
67mod insert_replace;
68mod issue;
69mod keywords;
70mod kill;
71mod lexer;
72mod lock;
73mod operator;
74mod parser;
75mod qualified_name;
76mod rename;
77mod select;
78mod show;
79mod span;
80mod sstring;
81mod statement;
82mod truncate;
83mod update;
84mod values;
85mod with_query;
86
87pub use alter_role::{AlterRole, AlterRoleAction, AlterRoleValue};
88pub use alter_table::{
89    AddColumn, AddForeignKey, AddIndex, AddTableConstraint, Algorithm, AlterAlgorithm, AlterColumn,
90    AlterColumnAction, AlterLock, AlterSpecification, AlterTable, AlterTableOwner, AutoIncrement,
91    Change, DisableRowLevelSecurity, DisableRule, DisableTrigger, DropColumn, DropForeignKey,
92    DropPrimaryKey, EnableRowLevelSecurity, EnableRule, EnableTrigger, ForceRowLevelSecurity,
93    ForeignKeyMatch, ForeignKeyOn, ForeignKeyOnAction, ForeignKeyOnType, IndexCol, IndexColExpr,
94    IndexOption, IndexType, ModifyColumn, NoForceRowLevelSecurity, OwnerTo, RenameColumn,
95    RenameConstraint, RenameIndex, RenameTo, ReplicaIdentity, ReplicaIdentityOption,
96    TableConstraintType, TriggerName, ValidateConstraint,
97};
98pub use alter_type::{AlterType, AlterTypeAction, AttributeAction};
99pub use copy::{
100    CopyColumnList, CopyFrom, CopyHeaderValue, CopyLocation, CopyOption, CopySource, CopyTo,
101};
102pub use create::{
103    CreateDatabase, CreateDatabaseOption, CreateDomain, CreateExtension, CreateSchema,
104    CreateSequence, CreateServer, CreateTypeEnum, DomainConstraint, SequenceOption,
105};
106pub use create_constraint_trigger::{AfterEvent, CreateConstraintTrigger, Deferrable, Initially};
107pub use create_function::{
108    CreateFunction, CreateProcedure, FunctionBody, FunctionCharacteristic, FunctionLanguage,
109    FunctionParallel, FunctionParam, FunctionParamDirection,
110};
111pub use create_index::{
112    CreateIndex, CreateIndexOption, IncludeClause, UsingIndexMethod, WithOption,
113};
114pub use create_option::{CreateAlgorithm, CreateOption};
115pub use create_role::{CreateRole, RoleMembership, RoleMembershipType, RoleOption};
116pub use create_table::{
117    CreateDefinition, CreateTable, CreateTableAs, CreateTablePartitionOf, OnCommitAction,
118    PartitionBoundExpr, PartitionBoundSpec, PartitionBy, PartitionMethod, PartitionOfBound,
119    TableOption,
120};
121pub use create_trigger::{
122    CreateTrigger, TriggerEvent, TriggerReference, TriggerReferenceDirection, TriggerTime,
123};
124pub use create_view::CreateView;
125pub use data_type::{
126    DataType, DataTypeProperty, Interval, IntervalField, RangeSubtype, Timestamp, Type,
127};
128pub use delete::{Delete, DeleteFlag};
129pub use drop::{
130    CascadeOrRestrict, DropDatabase, DropDomain, DropEvent, DropExtension, DropFunction,
131    DropFunctionArg, DropFunctionArgMode, DropIndex, DropOperator, DropOperatorClass,
132    DropOperatorFamily, DropOperatorItem, DropProcedure, DropSequence, DropServer, DropTable,
133    DropTrigger, DropView,
134};
135pub use expression::{
136    ArgExpression, ArrayExpression, ArraySubscriptExpression, BetweenExpression, BinaryExpression,
137    BinaryOperator, BoolExpression, CaseExpression, CastExpression, ConvertExpression,
138    DefaultExpression, ExistsExpression, Expression, ExtractExpression, FieldAccessExpression,
139    FloatExpression, GroupConcatExpression, IdentifierExpression, IdentifierPart, InExpression,
140    IntegerExpression, IntervalExpression, InvalidExpression, Is, IsExpression, ListHackExpression,
141    MatchAgainstExpression, MatchMode, MemberOfExpression, NullExpression, Quantifier,
142    QuantifierExpression, SubqueryExpression, TimeUnit, TimestampAddExpression,
143    TimestampDiffExpression, TrimDirection, TrimExpression, TypeCastExpression, UnaryExpression,
144    UnaryOperator, UserVariableExpression, Variable, VariableExpression, When,
145};
146pub use flush::{Flush, FlushOption};
147pub use function_expression::{
148    AggregateFunctionCallExpression, CharFunctionExpression, Function, FunctionCallExpression,
149    WindowClause, WindowFrame, WindowFrameBound, WindowFrameMode, WindowFunctionCallExpression,
150    WindowSpec,
151};
152pub use identifier::Identifier;
153pub use insert_replace::{
154    InsertReplace, InsertReplaceFlag, InsertReplaceOnDuplicateKeyUpdate, InsertReplaceSet,
155    InsertReplaceSetPair, InsertReplaceType, OnConflict, OnConflictAction, OnConflictTarget,
156};
157pub use issue::{Fragment, Issue, IssueHandle, Issues, Level};
158pub use kill::{Kill, KillType};
159pub use lock::{Lock, LockMember, LockType, Unlock};
160pub use operator::{
161    AlterOperator, AlterOperatorAction, AlterOperatorClass, AlterOperatorClassAction,
162    AlterOperatorFamily, AlterOperatorFamilyAction, CreateOperator, CreateOperatorClass,
163    CreateOperatorFamily, LeftOperatorType, OperatorClassItem, OperatorClassOperatorOption,
164    OperatorFamilyDropItem, OperatorFamilyItem, OperatorOption, OperatorRef,
165};
166pub use qualified_name::QualifiedName;
167pub use rename::{RenameTable, TableToTable};
168pub use select::{
169    IndexHint, IndexHintFor, IndexHintType, IndexHintUse, JoinSpecification, JoinType,
170    JsonTableColumn, JsonTableOnErrorEmpty, LockStrength, LockWait, Locking, OrderFlag, Select,
171    SelectExpr, SelectFlag, TableFunctionName, TableReference,
172};
173pub use show::{
174    ShowCharacterSet, ShowCollation, ShowColumns, ShowCreateDatabase, ShowCreateTable,
175    ShowCreateView, ShowDatabases, ShowEngines, ShowProcessList, ShowStatus, ShowTables,
176    ShowVariables,
177};
178pub use span::{OptSpanned, Span, Spanned};
179pub use sstring::SString;
180pub use statement::{
181    AlterSchema, AlterSchemaAction, Begin, Block, Call, CaseStatement, CloseCursor, Commit,
182    CompoundOperator, CompoundQuantifier, CompoundQuery, CompoundQueryBranch, CursorHold,
183    CursorScroll, CursorSensitivity, DeclareCursor, DeclareCursorMariaDb, DeclareHandler,
184    DeclareVariable, Do, DoBody, End, Explain, ExplainFormat, ExplainOption, FetchCursor,
185    HandlerAction, HandlerCondition, If, IfCondition, Invalid, Iterate, Leave, Loop, OpenCursor,
186    Prepare, RefreshMaterializedView, Repeat, Return, Set, Signal, SignalConditionInformationName,
187    StartTransaction, Statement, Stdin, WhenStatement, While,
188};
189pub use truncate::{IdentityOption, TruncateTable, TruncateTableSpec};
190pub use update::{Update, UpdateFlag};
191pub use values::{Fetch, FetchDirection, Values};
192pub use with_query::{MaterializedHint, WithBlock, WithQuery};
193
194/// What sql diarect to parse as
195#[derive(Clone, Debug)]
196pub enum SQLDialect {
197    /// Parse MariaDB/Mysql SQL
198    MariaDB,
199    PostgreSQL,
200    Sqlite,
201}
202
203impl SQLDialect {
204    pub fn is_postgresql(&self) -> bool {
205        matches!(self, SQLDialect::PostgreSQL)
206    }
207
208    pub fn is_maria(&self) -> bool {
209        matches!(self, SQLDialect::MariaDB)
210    }
211
212    pub fn is_sqlite(&self) -> bool {
213        matches!(self, SQLDialect::Sqlite)
214    }
215}
216
217/// What kinds or arguments
218#[derive(Clone, Debug)]
219pub enum SQLArguments {
220    /// The statements do not contain arguments
221    None,
222    /// Arguments are %s or %d
223    Percent,
224    /// Arguments are ?
225    QuestionMark,
226    /// Arguments ar #i
227    Dollar,
228}
229
230/// Options used when parsing sql
231#[derive(Clone, Debug)]
232pub struct ParseOptions {
233    dialect: SQLDialect,
234    arguments: SQLArguments,
235    warn_unquoted_identifiers: bool,
236    warn_none_capital_keywords: bool,
237    list_hack: bool,
238}
239
240impl Default for ParseOptions {
241    fn default() -> Self {
242        Self {
243            dialect: SQLDialect::MariaDB,
244            arguments: SQLArguments::None,
245            warn_none_capital_keywords: false,
246            warn_unquoted_identifiers: false,
247            list_hack: false,
248        }
249    }
250}
251
252impl ParseOptions {
253    pub fn new() -> Self {
254        Default::default()
255    }
256
257    /// Change whan SQL dialect to use
258    pub fn dialect(self, dialect: SQLDialect) -> Self {
259        Self { dialect, ..self }
260    }
261
262    pub fn get_dialect(&self) -> SQLDialect {
263        self.dialect.clone()
264    }
265
266    /// Change what kinds of arguments are supplied
267    pub fn arguments(self, arguments: SQLArguments) -> Self {
268        Self { arguments, ..self }
269    }
270
271    /// Should we warn about unquoted identifiers
272    pub fn warn_unquoted_identifiers(self, warn_unquoted_identifiers: bool) -> Self {
273        Self {
274            warn_unquoted_identifiers,
275            ..self
276        }
277    }
278
279    /// Should we warn about unquoted identifiers
280    pub fn warn_none_capital_keywords(self, warn_none_capital_keywords: bool) -> Self {
281        Self {
282            warn_none_capital_keywords,
283            ..self
284        }
285    }
286
287    /// Parse _LIST_ as special expression
288    pub fn list_hack(self, list_hack: bool) -> Self {
289        Self { list_hack, ..self }
290    }
291}
292
293/// Construct an "Internal compiler error" issue, containing the current file and line
294#[macro_export]
295macro_rules! issue_ice {
296    ( $issues: expr, $spanned:expr ) => {{
297        $issues.err(
298            alloc::format!("Internal compiler error in {}:{}", file!(), line!()),
299            $spanned,
300        );
301    }};
302}
303
304/// Construct an "Not yet implemented" issue, containing the current file and line
305#[macro_export]
306macro_rules! issue_todo {
307    ( $issues: expr, $spanned:expr ) => {{
308        $issues.err(
309            alloc::format!("Not yet implemented {}:{}", file!(), line!()),
310            $spanned,
311        );
312    }};
313}
314
315/// Parse multiple statements,
316/// return an Vec of Statements even if there are parse errors.
317/// The statements are free of errors if no Error issues are
318/// added to issues
319pub fn parse_statements<'a>(
320    src: &'a str,
321    issues: &mut Issues<'a>,
322    options: &ParseOptions,
323) -> Vec<Statement<'a>> {
324    let mut parser = Parser::new(src, issues, options);
325    statement::parse_statements(&mut parser)
326}
327
328/// Parse a single statement,
329/// A statement may be returned even if there where parse errors.
330/// The statement is free of errors if no Error issues are
331/// added to issues
332pub fn parse_statement<'a>(
333    src: &'a str,
334    issues: &mut Issues<'a>,
335    options: &ParseOptions,
336) -> Option<Statement<'a>> {
337    let mut parser = Parser::new(src, issues, options);
338    match statement::parse_statement(&mut parser) {
339        Ok(Some(v)) => {
340            if parser.token != Token::Eof {
341                parser.expected_error("Unexpected token after statement")
342            }
343            Some(v)
344        }
345        Ok(None) => {
346            parser.expected_error("Statement");
347            None
348        }
349        Err(_) => None,
350    }
351}
352
353#[test]
354pub fn test_parse_alter_sql() {
355    let sql = "ALTER TABLE `test` ADD COLUMN `test1` VARCHAR (128) NULL DEFAULT NULL";
356    let options = ParseOptions::new()
357        .dialect(SQLDialect::MariaDB)
358        .arguments(SQLArguments::QuestionMark)
359        .warn_unquoted_identifiers(false);
360
361    let mut issues = Issues::new(sql);
362    parse_statement(sql, &mut issues, &options);
363    assert!(issues.is_ok(), "{}", issues);
364}
365
366#[test]
367pub fn test_parse_delete_sql_with_schema() {
368    let sql = "DROP TABLE IF EXISTS `test_schema`.`test`";
369    let options = ParseOptions::new()
370        .dialect(SQLDialect::MariaDB)
371        .arguments(SQLArguments::QuestionMark)
372        .warn_unquoted_identifiers(false);
373
374    let mut issues = Issues::new(sql);
375    parse_statement(sql, &mut issues, &options);
376    assert!(issues.is_ok(), "{}", issues);
377}
378
379#[test]
380pub fn parse_create_index_sql_with_schema() {
381    let sql = "CREATE INDEX `idx_test` ON  test_schema.test(`col_test`)";
382    let options = ParseOptions::new()
383        .dialect(SQLDialect::MariaDB)
384        .arguments(SQLArguments::QuestionMark)
385        .warn_unquoted_identifiers(false);
386
387    let mut issues = Issues::new(sql);
388    parse_statement(sql, &mut issues, &options);
389    assert!(issues.is_ok(), "{}", issues);
390}
391
392#[test]
393pub fn parse_create_index_sql_with_opclass() {
394    let sql = "CREATE INDEX idx_test ON test(path text_pattern_ops)";
395    let options = ParseOptions::new()
396        .dialect(SQLDialect::PostgreSQL)
397        .arguments(SQLArguments::Dollar)
398        .warn_unquoted_identifiers(false);
399
400    let mut issues = Issues::new(sql);
401    parse_statement(sql, &mut issues, &options);
402    assert!(issues.is_ok(), "{}", issues);
403}
404
405#[test]
406pub fn parse_drop_index_sql_with_schema() {
407    let sql = "DROP INDEX `idx_test` ON  test_schema.test";
408    let options = ParseOptions::new()
409        .dialect(SQLDialect::MariaDB)
410        .arguments(SQLArguments::QuestionMark)
411        .warn_unquoted_identifiers(false);
412
413    let mut issues = Issues::new(sql);
414    let _result = parse_statement(sql, &mut issues, &options);
415    // assert!(result.is_none(), "result: {:#?}", &result);
416    assert!(issues.is_ok(), "{}", issues);
417}
418
419#[test]
420pub fn parse_create_view_sql_with_schema() {
421    let sql =
422        "CREATE OR REPLACE VIEW `test_schema`.`view_test` AS SELECT * FROM `test_schema`.`test`";
423    let options = ParseOptions::new()
424        .dialect(SQLDialect::MariaDB)
425        .arguments(SQLArguments::QuestionMark)
426        .warn_unquoted_identifiers(false);
427
428    let mut issues = Issues::new(sql);
429    let _result = parse_statement(sql, &mut issues, &options);
430    // assert!(result.is_none(), "result: {:#?}", &result);
431    assert!(issues.is_ok(), "{}", issues);
432}
433
434#[test]
435pub fn parse_drop_view_sql_with_schema() {
436    let sql = "DROP VIEW `test_schema`.`view_test`";
437    let options = ParseOptions::new()
438        .dialect(SQLDialect::MariaDB)
439        .arguments(SQLArguments::QuestionMark)
440        .warn_unquoted_identifiers(false);
441
442    let mut issues = Issues::new(sql);
443    let _result = parse_statement(sql, &mut issues, &options);
444    // assert!(result.is_none(), "result: {:#?}", &result);
445    assert!(issues.is_ok(), "{}", issues);
446}
447
448#[test]
449pub fn parse_truncate_table_sql_with_schema() {
450    let sql = "TRUNCATE TABLE `test_schema`.`table_test`";
451    let options = ParseOptions::new()
452        .dialect(SQLDialect::MariaDB)
453        .arguments(SQLArguments::QuestionMark)
454        .warn_unquoted_identifiers(false);
455
456    let mut issues = Issues::new(sql);
457    let _result = parse_statement(sql, &mut issues, &options);
458    // assert!(result.is_none(), "result: {:#?}", &result);
459    assert!(issues.is_ok(), "{}", issues);
460}
461
462#[test]
463pub fn parse_rename_table_sql_with_schema() {
464    let sql = "RENAME TABLE `test_schema`.`table_test` To `test_schema`.`table_new_test`";
465    let options = ParseOptions::new()
466        .dialect(SQLDialect::MariaDB)
467        .arguments(SQLArguments::QuestionMark)
468        .warn_unquoted_identifiers(false);
469
470    let mut issues = Issues::new(sql);
471    let _result = parse_statement(sql, &mut issues, &options);
472    // assert!(result.is_none(), "result: {:#?}", &result);
473    assert!(issues.is_ok(), "{}", issues);
474}
475
476#[test]
477pub fn parse_with_statement() {
478    let sql = "
479        WITH monkeys AS (DELETE FROM thing RETURNING id),
480        baz AS (SELECT id FROM cats WHERE comp IN (monkeys))
481        DELETE FROM dogs WHERE cat IN (cats)";
482    let options = ParseOptions::new()
483        .dialect(SQLDialect::PostgreSQL)
484        .arguments(SQLArguments::QuestionMark)
485        .warn_unquoted_identifiers(false);
486
487    let mut issues = Issues::new(sql);
488    let _result = parse_statement(sql, &mut issues, &options);
489    // assert!(result.is_none(), "result: {:#?}", &result);
490    assert!(issues.is_ok(), "{}", issues);
491}
492
493#[test]
494pub fn parse_use_index() {
495    let sql = "SELECT `a` FROM `b` FORCE INDEX FOR GROUP BY (`b`, `c`)";
496    let options = ParseOptions::new()
497        .dialect(SQLDialect::MariaDB)
498        .arguments(SQLArguments::QuestionMark)
499        .warn_unquoted_identifiers(false);
500
501    let mut issues = Issues::new(sql);
502    let _result = parse_statement(sql, &mut issues, &options);
503    assert!(issues.is_ok(), "{}", issues);
504}