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 byte_to_char;
51mod copy;
52mod create;
53mod create_constraint_trigger;
54mod create_function;
55mod create_index;
56mod create_option;
57mod create_role;
58mod create_table;
59mod create_trigger;
60mod create_view;
61mod data_type;
62mod delete;
63mod drop;
64mod expression;
65mod flush;
66mod function_expression;
67mod grant;
68mod identifier;
69mod insert_replace;
70mod issue;
71mod keywords;
72mod kill;
73mod lexer;
74mod lock;
75mod operator;
76mod parser;
77mod qualified_name;
78mod rename;
79mod select;
80mod show;
81mod span;
82mod sstring;
83mod statement;
84mod truncate;
85mod update;
86mod values;
87mod with_query;
88
89pub use alter_role::{AlterRole, AlterRoleAction, AlterRoleValue};
90pub use alter_table::{
91    AddColumn, AddForeignKey, AddIndex, AddTableConstraint, Algorithm, AlterAlgorithm, AlterColumn,
92    AlterColumnAction, AlterLock, AlterSpecification, AlterTable, AlterTableOwner, AutoIncrement,
93    Change, DisableRowLevelSecurity, DisableRule, DisableTrigger, DropColumn, DropForeignKey,
94    DropPrimaryKey, EnableRowLevelSecurity, EnableRule, EnableTrigger, ForceRowLevelSecurity,
95    ForeignKeyMatch, ForeignKeyOn, ForeignKeyOnAction, ForeignKeyOnType, IndexCol, IndexColExpr,
96    IndexOption, IndexType, ModifyColumn, NoForceRowLevelSecurity, OwnerTo, RenameColumn,
97    RenameConstraint, RenameIndex, RenameTo, ReplicaIdentity, ReplicaIdentityOption,
98    TableConstraintType, TriggerName, ValidateConstraint,
99};
100pub use alter_type::{AlterType, AlterTypeAction, AttributeAction};
101pub use byte_to_char::ByteToChar;
102pub use copy::{
103    CopyColumnList, CopyFrom, CopyHeaderValue, CopyLocation, CopyOption, CopySource, CopyTo,
104};
105pub use create::{
106    CreateDatabase, CreateDatabaseOption, CreateDomain, CreateExtension, CreateSchema,
107    CreateSequence, CreateServer, CreateTypeEnum, DomainConstraint, SequenceOption,
108};
109pub use create_constraint_trigger::{AfterEvent, CreateConstraintTrigger, Deferrable, Initially};
110pub use create_function::{
111    CreateFunction, CreateProcedure, FunctionBody, FunctionCharacteristic, FunctionLanguage,
112    FunctionParallel, FunctionParam, FunctionParamDirection,
113};
114pub use create_index::{
115    CreateIndex, CreateIndexOption, IncludeClause, UsingIndexMethod, WithOption,
116};
117pub use create_option::{CreateAlgorithm, CreateOption};
118pub use create_role::{CreateRole, RoleMembership, RoleMembershipType, RoleOption};
119pub use create_table::{
120    CreateDefinition, CreateTable, CreateTableAs, CreateTablePartitionOf, OnCommitAction,
121    PartitionBoundExpr, PartitionBoundSpec, PartitionBy, PartitionMethod, PartitionOfBound,
122    TableOption,
123};
124pub use create_trigger::{
125    CreateTrigger, ExecuteFunction, TriggerEvent, TriggerForEach, TriggerReference,
126    TriggerReferenceDirection, TriggerTime,
127};
128pub use create_view::CreateView;
129pub use data_type::{
130    DataType, DataTypeProperty, Interval, IntervalField, RangeSubtype, Timestamp, Type,
131};
132pub use delete::{Delete, DeleteFlag};
133pub use drop::{
134    CascadeOrRestrict, DropDatabase, DropDomain, DropEvent, DropExtension, DropFunction,
135    DropFunctionArg, DropFunctionArgMode, DropIndex, DropOperator, DropOperatorClass,
136    DropOperatorFamily, DropOperatorItem, DropProcedure, DropSequence, DropServer, DropTable,
137    DropTrigger, DropType, DropView,
138};
139pub use expression::{
140    ArgExpression, ArrayExpression, ArraySubscriptExpression, BetweenExpression, BinaryExpression,
141    BinaryOperator, BoolExpression, CaseExpression, CastExpression, ConvertExpression,
142    DefaultExpression, ExistsExpression, Expression, ExtractExpression, FieldAccessExpression,
143    FloatExpression, GroupConcatExpression, IdentifierExpression, IdentifierPart, InExpression,
144    IntegerExpression, IntervalExpression, InvalidExpression, Is, IsExpression, ListHackExpression,
145    MatchAgainstExpression, MatchMode, MemberOfExpression, NullExpression, Quantifier,
146    QuantifierExpression, RowExpression, SubqueryExpression, TimeUnit, TimestampAddExpression,
147    TimestampDiffExpression, TrimDirection, TrimExpression, TypeCastExpression, UnaryExpression,
148    UnaryOperator, UserVariableExpression, Variable, VariableExpression, When,
149};
150pub use flush::{Flush, FlushOption};
151pub use function_expression::{
152    AggregateFunctionCallExpression, CharFunctionExpression, Function, FunctionCallExpression,
153    WindowClause, WindowFrame, WindowFrameBound, WindowFrameMode, WindowFunctionCallExpression,
154    WindowSpec,
155};
156pub use grant::{
157    AllRoutineKind, Grant, GrantKind, GrantObject, GrantPrivilege, MembershipOption,
158    MembershipOptionKind, MembershipOptionValue, PrivilegeItem, RoleSpec, RoutineArgType,
159    RoutineKind, RoutineName,
160};
161pub use identifier::Identifier;
162pub use insert_replace::{
163    InsertReplace, InsertReplaceFlag, InsertReplaceOnDuplicateKeyUpdate, InsertReplaceSet,
164    InsertReplaceSetPair, InsertReplaceType, OnConflict, OnConflictAction, OnConflictTarget,
165};
166pub use issue::{Fragment, Issue, IssueHandle, Issues, Level};
167pub use kill::{Kill, KillType};
168pub use lock::{Lock, LockMember, LockType, Unlock};
169pub use operator::{
170    AlterOperator, AlterOperatorAction, AlterOperatorClass, AlterOperatorClassAction,
171    AlterOperatorFamily, AlterOperatorFamilyAction, CreateOperator, CreateOperatorClass,
172    CreateOperatorFamily, LeftOperatorType, OperatorClassItem, OperatorClassOperatorOption,
173    OperatorFamilyDropItem, OperatorFamilyItem, OperatorOption, OperatorRef,
174};
175pub use qualified_name::QualifiedName;
176pub use rename::{RenameTable, TableToTable};
177pub use select::{
178    IndexHint, IndexHintFor, IndexHintType, IndexHintUse, JoinSpecification, JoinType,
179    JsonTableColumn, JsonTableOnErrorEmpty, LockStrength, LockWait, Locking, OrderFlag, Select,
180    SelectExpr, SelectFlag, TableFunctionName, TableReference,
181};
182pub use show::{
183    ShowCharacterSet, ShowCollation, ShowColumns, ShowCreateDatabase, ShowCreateTable,
184    ShowCreateView, ShowDatabases, ShowEngines, ShowProcessList, ShowStatus, ShowTables,
185    ShowVariables,
186};
187pub use span::{OptSpanned, Span, Spanned};
188pub use sstring::SString;
189pub use statement::{
190    AlterSchema, AlterSchemaAction, Analyze, Assign, Begin, Block, Call, CaseStatement,
191    CloseCursor, CommentOn, CommentOnObjectType, Commit, CompoundOperator, CompoundQuantifier,
192    CompoundQuery, CompoundQueryBranch, CursorHold, CursorScroll, CursorSensitivity, DeclareCursor,
193    DeclareCursorMariaDb, DeclareHandler, DeclareVariable, Do, DoBody, End, ExceptionHandler,
194    Explain, ExplainFormat, ExplainOption, FetchCursor, HandlerAction, HandlerCondition, If,
195    IfCondition, Invalid, Iterate, Leave, Loop, OpenCursor, Perform, PlpgsqlExecute, Prepare,
196    Raise, RaiseLevel, RaiseOptionName, RefreshMaterializedView, Repeat, Return, Set, SetVariable,
197    Signal, SignalConditionInformationName, StartTransaction, Statement, Stdin, WhenStatement,
198    While,
199};
200pub use truncate::{IdentityOption, TruncateTable, TruncateTableSpec};
201pub use update::{Update, UpdateFlag};
202pub use values::{Fetch, FetchDirection, Values};
203pub use with_query::{MaterializedHint, WithBlock, WithQuery};
204
205/// What sql diarect to parse as
206#[derive(Clone, Debug)]
207pub enum SQLDialect {
208    /// Parse MariaDB/Mysql SQL
209    MariaDB,
210    PostgreSQL,
211    /// PostgreSQL with PostGIS extension functions enabled
212    PostGIS,
213    Sqlite,
214}
215
216impl SQLDialect {
217    pub fn is_postgresql(&self) -> bool {
218        matches!(self, SQLDialect::PostgreSQL | SQLDialect::PostGIS)
219    }
220
221    pub fn is_postgis(&self) -> bool {
222        matches!(self, SQLDialect::PostGIS)
223    }
224
225    pub fn is_maria(&self) -> bool {
226        matches!(self, SQLDialect::MariaDB)
227    }
228
229    pub fn is_sqlite(&self) -> bool {
230        matches!(self, SQLDialect::Sqlite)
231    }
232}
233
234/// What kinds or arguments
235#[derive(Clone, Debug)]
236pub enum SQLArguments {
237    /// The statements do not contain arguments
238    None,
239    /// Arguments are %s or %d
240    Percent,
241    /// Arguments are ?
242    QuestionMark,
243    /// Arguments ar #i
244    Dollar,
245}
246
247/// Options used when parsing sql
248#[derive(Clone, Debug)]
249pub struct ParseOptions {
250    dialect: SQLDialect,
251    arguments: SQLArguments,
252    warn_unquoted_identifiers: bool,
253    warn_none_capital_keywords: bool,
254    list_hack: bool,
255    /// When true, parse in function/procedure body mode:
256    /// allows `BEGIN ... END` blocks and other compound statements
257    /// that are only valid inside a stored function or procedure body.
258    function_body: bool,
259    /// Byte offset added to every span produced by the lexer. Set this when
260    /// parsing a sub-string that is embedded inside a larger source file so
261    /// that all spans are relative to the outer file rather than the sub-string.
262    span_offset: usize,
263}
264
265impl Default for ParseOptions {
266    fn default() -> Self {
267        Self {
268            dialect: SQLDialect::MariaDB,
269            arguments: SQLArguments::None,
270            warn_none_capital_keywords: false,
271            warn_unquoted_identifiers: false,
272            list_hack: false,
273            function_body: false,
274            span_offset: 0,
275        }
276    }
277}
278
279impl ParseOptions {
280    pub fn new() -> Self {
281        Default::default()
282    }
283
284    /// Change whan SQL dialect to use
285    pub fn dialect(self, dialect: SQLDialect) -> Self {
286        Self { dialect, ..self }
287    }
288
289    pub fn get_dialect(&self) -> SQLDialect {
290        self.dialect.clone()
291    }
292
293    /// Change what kinds of arguments are supplied
294    pub fn arguments(self, arguments: SQLArguments) -> Self {
295        Self { arguments, ..self }
296    }
297
298    /// Should we warn about unquoted identifiers
299    pub fn warn_unquoted_identifiers(self, warn_unquoted_identifiers: bool) -> Self {
300        Self {
301            warn_unquoted_identifiers,
302            ..self
303        }
304    }
305
306    /// Should we warn about unquoted identifiers
307    pub fn warn_none_capital_keywords(self, warn_none_capital_keywords: bool) -> Self {
308        Self {
309            warn_none_capital_keywords,
310            ..self
311        }
312    }
313
314    /// Parse _LIST_ as special expression
315    pub fn list_hack(self, list_hack: bool) -> Self {
316        Self { list_hack, ..self }
317    }
318
319    /// Parse in function/procedure body mode (allows BEGIN...END blocks)
320    pub fn function_body(self, function_body: bool) -> Self {
321        Self {
322            function_body,
323            ..self
324        }
325    }
326
327    pub fn get_function_body(&self) -> bool {
328        self.function_body
329    }
330
331    /// Set the byte offset of the sub-string being parsed within the outer source file.
332    /// All spans produced by the lexer will be adjusted by this offset so they remain
333    /// relative to the outer file.
334    pub fn span_offset(self, span_offset: usize) -> Self {
335        Self {
336            span_offset,
337            ..self
338        }
339    }
340
341    pub fn get_span_offset(&self) -> usize {
342        self.span_offset
343    }
344}
345
346/// Construct an "Internal compiler error" issue, containing the current file and line
347#[macro_export]
348macro_rules! issue_ice {
349    ( $issues: expr, $spanned:expr ) => {{
350        $issues.err(
351            alloc::format!("Internal compiler error in {}:{}", file!(), line!()),
352            $spanned,
353        );
354    }};
355}
356
357/// Construct an "Not yet implemented" issue, containing the current file and line
358#[macro_export]
359macro_rules! issue_todo {
360    ( $issues: expr, $spanned:expr ) => {{
361        $issues.err(
362            alloc::format!("Not yet implemented {}:{}", file!(), line!()),
363            $spanned,
364        );
365    }};
366}
367
368/// Parse multiple statements,
369/// return an Vec of Statements even if there are parse errors.
370/// The statements are free of errors if no Error issues are
371/// added to issues
372pub fn parse_statements<'a>(
373    src: &'a str,
374    issues: &mut Issues<'a>,
375    options: &ParseOptions,
376) -> Vec<Statement<'a>> {
377    let mut parser = Parser::new(src, issues, options);
378    statement::parse_statements(&mut parser)
379}
380
381/// Parse a single statement,
382/// A statement may be returned even if there where parse errors.
383/// The statement is free of errors if no Error issues are
384/// added to issues
385pub fn parse_statement<'a>(
386    src: &'a str,
387    issues: &mut Issues<'a>,
388    options: &ParseOptions,
389) -> Option<Statement<'a>> {
390    let mut parser = Parser::new(src, issues, options);
391    match statement::parse_statement(&mut parser) {
392        Ok(Some(v)) => {
393            // Allow a single trailing statement delimiter (e.g. `;` after `BEGIN...END`)
394            if parser.token == Token::Delimiter {
395                parser.consume();
396            }
397            if parser.token != Token::Eof {
398                parser.expected_error("Unexpected token after statement")
399            }
400            Some(v)
401        }
402        Ok(None) => {
403            parser.expected_error("Statement");
404            None
405        }
406        Err(_) => None,
407    }
408}
409
410#[test]
411pub fn test_parse_alter_sql() {
412    let sql = "ALTER TABLE `test` ADD COLUMN `test1` VARCHAR (128) NULL DEFAULT NULL";
413    let options = ParseOptions::new()
414        .dialect(SQLDialect::MariaDB)
415        .arguments(SQLArguments::QuestionMark)
416        .warn_unquoted_identifiers(false);
417
418    let mut issues = Issues::new(sql);
419    parse_statement(sql, &mut issues, &options);
420    assert!(issues.is_ok(), "{}", issues);
421}
422
423#[test]
424pub fn test_parse_delete_sql_with_schema() {
425    let sql = "DROP TABLE IF EXISTS `test_schema`.`test`";
426    let options = ParseOptions::new()
427        .dialect(SQLDialect::MariaDB)
428        .arguments(SQLArguments::QuestionMark)
429        .warn_unquoted_identifiers(false);
430
431    let mut issues = Issues::new(sql);
432    parse_statement(sql, &mut issues, &options);
433    assert!(issues.is_ok(), "{}", issues);
434}
435
436#[test]
437pub fn parse_create_index_sql_with_schema() {
438    let sql = "CREATE INDEX `idx_test` ON  test_schema.test(`col_test`)";
439    let options = ParseOptions::new()
440        .dialect(SQLDialect::MariaDB)
441        .arguments(SQLArguments::QuestionMark)
442        .warn_unquoted_identifiers(false);
443
444    let mut issues = Issues::new(sql);
445    parse_statement(sql, &mut issues, &options);
446    assert!(issues.is_ok(), "{}", issues);
447}
448
449#[test]
450pub fn parse_create_index_sql_with_opclass() {
451    let sql = "CREATE INDEX idx_test ON test(path text_pattern_ops)";
452    let options = ParseOptions::new()
453        .dialect(SQLDialect::PostgreSQL)
454        .arguments(SQLArguments::Dollar)
455        .warn_unquoted_identifiers(false);
456
457    let mut issues = Issues::new(sql);
458    parse_statement(sql, &mut issues, &options);
459    assert!(issues.is_ok(), "{}", issues);
460}
461
462#[test]
463pub fn parse_drop_index_sql_with_schema() {
464    let sql = "DROP INDEX `idx_test` ON  test_schema.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_create_view_sql_with_schema() {
478    let sql =
479        "CREATE OR REPLACE VIEW `test_schema`.`view_test` AS SELECT * FROM `test_schema`.`test`";
480    let options = ParseOptions::new()
481        .dialect(SQLDialect::MariaDB)
482        .arguments(SQLArguments::QuestionMark)
483        .warn_unquoted_identifiers(false);
484
485    let mut issues = Issues::new(sql);
486    let _result = parse_statement(sql, &mut issues, &options);
487    // assert!(result.is_none(), "result: {:#?}", &result);
488    assert!(issues.is_ok(), "{}", issues);
489}
490
491#[test]
492pub fn parse_drop_view_sql_with_schema() {
493    let sql = "DROP VIEW `test_schema`.`view_test`";
494    let options = ParseOptions::new()
495        .dialect(SQLDialect::MariaDB)
496        .arguments(SQLArguments::QuestionMark)
497        .warn_unquoted_identifiers(false);
498
499    let mut issues = Issues::new(sql);
500    let _result = parse_statement(sql, &mut issues, &options);
501    // assert!(result.is_none(), "result: {:#?}", &result);
502    assert!(issues.is_ok(), "{}", issues);
503}
504
505#[test]
506pub fn parse_truncate_table_sql_with_schema() {
507    let sql = "TRUNCATE TABLE `test_schema`.`table_test`";
508    let options = ParseOptions::new()
509        .dialect(SQLDialect::MariaDB)
510        .arguments(SQLArguments::QuestionMark)
511        .warn_unquoted_identifiers(false);
512
513    let mut issues = Issues::new(sql);
514    let _result = parse_statement(sql, &mut issues, &options);
515    // assert!(result.is_none(), "result: {:#?}", &result);
516    assert!(issues.is_ok(), "{}", issues);
517}
518
519#[test]
520pub fn parse_rename_table_sql_with_schema() {
521    let sql = "RENAME TABLE `test_schema`.`table_test` To `test_schema`.`table_new_test`";
522    let options = ParseOptions::new()
523        .dialect(SQLDialect::MariaDB)
524        .arguments(SQLArguments::QuestionMark)
525        .warn_unquoted_identifiers(false);
526
527    let mut issues = Issues::new(sql);
528    let _result = parse_statement(sql, &mut issues, &options);
529    // assert!(result.is_none(), "result: {:#?}", &result);
530    assert!(issues.is_ok(), "{}", issues);
531}
532
533#[test]
534pub fn parse_with_statement() {
535    let sql = "
536        WITH monkeys AS (DELETE FROM thing RETURNING id),
537        baz AS (SELECT id FROM cats WHERE comp IN (monkeys))
538        DELETE FROM dogs WHERE cat IN (cats)";
539    let options = ParseOptions::new()
540        .dialect(SQLDialect::PostgreSQL)
541        .arguments(SQLArguments::QuestionMark)
542        .warn_unquoted_identifiers(false);
543
544    let mut issues = Issues::new(sql);
545    let _result = parse_statement(sql, &mut issues, &options);
546    // assert!(result.is_none(), "result: {:#?}", &result);
547    assert!(issues.is_ok(), "{}", issues);
548}
549
550#[test]
551pub fn parse_use_index() {
552    let sql = "SELECT `a` FROM `b` FORCE INDEX FOR GROUP BY (`b`, `c`)";
553    let options = ParseOptions::new()
554        .dialect(SQLDialect::MariaDB)
555        .arguments(SQLArguments::QuestionMark)
556        .warn_unquoted_identifiers(false);
557
558    let mut issues = Issues::new(sql);
559    let _result = parse_statement(sql, &mut issues, &options);
560    assert!(issues.is_ok(), "{}", issues);
561}