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