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, DropSchema, DropSequence, DropServer,
154    DropTable, 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, LikeExpression,
162    LikeOperator, ListHackExpression, MatchAgainstExpression, MatchMode, MemberOfExpression,
163    NullExpression, Quantifier, QuantifierExpression, RowExpression, SubqueryExpression, TimeUnit,
164    TimestampAddExpression, TimestampDiffExpression, TrimDirection, TrimExpression,
165    TypeCastExpression, UnaryExpression, UnaryOperator, UserVariableExpression, Variable,
166    VariableExpression, When,
167};
168pub use flush::{Flush, FlushOption};
169pub use function_expression::{
170    AggregateFunctionCallExpression, CharFunctionExpression, Function, FunctionCallExpression,
171    WindowClause, WindowFrame, WindowFrameBound, WindowFrameMode, WindowFunctionCallExpression,
172    WindowSpec,
173};
174pub use grant::{
175    AllRoutineKind, Grant, GrantKind, GrantObject, GrantPrivilege, MembershipOption,
176    MembershipOptionKind, MembershipOptionValue, PrivilegeItem, RoleSpec, RoutineArgType,
177    RoutineKind, RoutineName,
178};
179pub use identifier::Identifier;
180pub use insert_replace::{
181    InsertReplace, InsertReplaceFlag, InsertReplaceOnDuplicateKeyUpdate, InsertReplaceSet,
182    InsertReplaceSetPair, InsertReplaceType, OnConflict, OnConflictAction, OnConflictTarget,
183};
184pub use issue::{Fragment, Issue, IssueHandle, Issues, Level};
185pub use kill::{Kill, KillType};
186pub use lock::{Lock, LockMember, LockType, Unlock};
187pub use operator::{
188    AlterOperator, AlterOperatorAction, AlterOperatorClass, AlterOperatorClassAction,
189    AlterOperatorFamily, AlterOperatorFamilyAction, CreateOperator, CreateOperatorClass,
190    CreateOperatorFamily, LeftOperatorType, OperatorClassItem, OperatorClassOperatorOption,
191    OperatorFamilyDropItem, OperatorFamilyItem, OperatorOption, OperatorRef,
192};
193pub use qualified_name::QualifiedName;
194pub use rename::{RenameTable, TableToTable};
195pub use select::{
196    IndexHint, IndexHintFor, IndexHintType, IndexHintUse, JoinSpecification, JoinType,
197    JsonTableColumn, JsonTableOnErrorEmpty, LockStrength, LockWait, Locking, OrderFlag, Select,
198    SelectExpr, SelectFlag, TableFunctionName, TableReference,
199};
200pub use show::{
201    ShowCharacterSet, ShowCollation, ShowColumns, ShowCreateDatabase, ShowCreateTable,
202    ShowCreateView, ShowDatabases, ShowEngines, ShowProcessList, ShowStatus, ShowTables,
203    ShowVariables,
204};
205pub use span::{OptSpanned, Span, Spanned};
206pub use sstring::SString;
207pub use statement::{
208    AlterSchema, AlterSchemaAction, Analyze, Assign, Begin, Block, Call, CaseStatement,
209    CloseCursor, CommentOn, CommentOnObjectType, Commit, CompoundOperator, CompoundQuantifier,
210    CompoundQuery, CompoundQueryBranch, CursorHold, CursorScroll, CursorSensitivity, DeclareCursor,
211    DeclareCursorMariaDb, DeclareHandler, DeclareVariable, Do, DoBody, End, ExceptionHandler,
212    Explain, ExplainFormat, ExplainOption, FetchCursor, HandlerAction, HandlerCondition, If,
213    IfCondition, Invalid, Iterate, Leave, Loop, OpenCursor, Perform, PlpgsqlExecute, Prepare,
214    Raise, RaiseLevel, RaiseOptionName, RefreshMaterializedView, Repeat, Return, Set, SetVariable,
215    Signal, SignalConditionInformationName, StartTransaction, Statement, Stdin, WhenStatement,
216    While,
217};
218pub use truncate::{IdentityOption, TruncateTable, TruncateTableSpec};
219pub use update::{Update, UpdateFlag};
220pub use values::{Fetch, FetchDirection, Values};
221pub use visit::{Visitor, walk_expression, walk_select, walk_statement, walk_table_reference};
222pub use with_query::{MaterializedHint, WithBlock, WithQuery};
223
224/// What sql diarect to parse as
225#[derive(Clone, Debug)]
226pub enum SQLDialect {
227    /// Parse MariaDB/Mysql SQL
228    MariaDB,
229    PostgreSQL,
230    /// PostgreSQL with PostGIS extension functions enabled
231    PostGIS,
232    Sqlite,
233}
234
235impl SQLDialect {
236    pub fn is_postgresql(&self) -> bool {
237        matches!(self, SQLDialect::PostgreSQL | SQLDialect::PostGIS)
238    }
239
240    pub fn is_postgis(&self) -> bool {
241        matches!(self, SQLDialect::PostGIS)
242    }
243
244    pub fn is_maria(&self) -> bool {
245        matches!(self, SQLDialect::MariaDB)
246    }
247
248    pub fn is_sqlite(&self) -> bool {
249        matches!(self, SQLDialect::Sqlite)
250    }
251}
252
253/// What kinds or arguments
254#[derive(Clone, Debug)]
255pub enum SQLArguments {
256    /// The statements do not contain arguments
257    None,
258    /// Arguments are %s or %d
259    Percent,
260    /// Arguments are ?
261    QuestionMark,
262    /// Arguments ar #i
263    Dollar,
264}
265
266/// Options used when parsing sql
267#[derive(Clone, Debug)]
268pub struct ParseOptions {
269    dialect: SQLDialect,
270    arguments: SQLArguments,
271    warn_unquoted_identifiers: bool,
272    warn_none_capital_keywords: bool,
273    list_hack: bool,
274    /// When true, parse in function/procedure body mode:
275    /// allows `BEGIN ... END` blocks and other compound statements
276    /// that are only valid inside a stored function or procedure body.
277    function_body: bool,
278    /// Byte offset added to every span produced by the lexer. Set this when
279    /// parsing a sub-string that is embedded inside a larger source file so
280    /// that all spans are relative to the outer file rather than the sub-string.
281    span_offset: usize,
282}
283
284impl Default for ParseOptions {
285    fn default() -> Self {
286        Self {
287            dialect: SQLDialect::MariaDB,
288            arguments: SQLArguments::None,
289            warn_none_capital_keywords: false,
290            warn_unquoted_identifiers: false,
291            list_hack: false,
292            function_body: false,
293            span_offset: 0,
294        }
295    }
296}
297
298impl ParseOptions {
299    pub fn new() -> Self {
300        Default::default()
301    }
302
303    /// Change whan SQL dialect to use
304    pub fn dialect(self, dialect: SQLDialect) -> Self {
305        Self { dialect, ..self }
306    }
307
308    pub fn get_dialect(&self) -> SQLDialect {
309        self.dialect.clone()
310    }
311
312    /// Change what kinds of arguments are supplied
313    pub fn arguments(self, arguments: SQLArguments) -> Self {
314        Self { arguments, ..self }
315    }
316
317    /// Should we warn about unquoted identifiers
318    pub fn warn_unquoted_identifiers(self, warn_unquoted_identifiers: bool) -> Self {
319        Self {
320            warn_unquoted_identifiers,
321            ..self
322        }
323    }
324
325    /// Should we warn about unquoted identifiers
326    pub fn warn_none_capital_keywords(self, warn_none_capital_keywords: bool) -> Self {
327        Self {
328            warn_none_capital_keywords,
329            ..self
330        }
331    }
332
333    /// Parse _LIST_ as special expression
334    pub fn list_hack(self, list_hack: bool) -> Self {
335        Self { list_hack, ..self }
336    }
337
338    /// Parse in function/procedure body mode (allows BEGIN...END blocks)
339    pub fn function_body(self, function_body: bool) -> Self {
340        Self {
341            function_body,
342            ..self
343        }
344    }
345
346    pub fn get_function_body(&self) -> bool {
347        self.function_body
348    }
349
350    /// Set the byte offset of the sub-string being parsed within the outer source file.
351    /// All spans produced by the lexer will be adjusted by this offset so they remain
352    /// relative to the outer file.
353    pub fn span_offset(self, span_offset: usize) -> Self {
354        Self {
355            span_offset,
356            ..self
357        }
358    }
359
360    pub fn get_span_offset(&self) -> usize {
361        self.span_offset
362    }
363}
364
365/// Construct an "Internal compiler error" issue, containing the current file and line
366#[macro_export]
367macro_rules! issue_ice {
368    ( $issues: expr, $spanned:expr ) => {{
369        $issues.err(
370            alloc::format!("Internal compiler error in {}:{}", file!(), line!()),
371            $spanned,
372        );
373    }};
374}
375
376/// Construct an "Not yet implemented" issue, containing the current file and line
377#[macro_export]
378macro_rules! issue_todo {
379    ( $issues: expr, $spanned:expr ) => {{
380        $issues.err(
381            alloc::format!("Not yet implemented {}:{}", file!(), line!()),
382            $spanned,
383        );
384    }};
385}
386
387/// Parse multiple statements,
388/// return an Vec of Statements even if there are parse errors.
389/// The statements are free of errors if no Error issues are
390/// added to issues
391pub fn parse_statements<'a>(
392    src: &'a str,
393    issues: &mut Issues<'a>,
394    options: &ParseOptions,
395) -> Vec<Statement<'a>> {
396    let mut parser = Parser::new(src, issues, options);
397    statement::parse_statements(&mut parser)
398}
399
400/// Parse a single statement,
401/// A statement may be returned even if there where parse errors.
402/// The statement is free of errors if no Error issues are
403/// added to issues
404pub fn parse_statement<'a>(
405    src: &'a str,
406    issues: &mut Issues<'a>,
407    options: &ParseOptions,
408) -> Option<Statement<'a>> {
409    let mut parser = Parser::new(src, issues, options);
410    match statement::parse_statement(&mut parser) {
411        Ok(Some(v)) => {
412            // Allow a single trailing statement delimiter (e.g. `;` after `BEGIN...END`)
413            if parser.token == Token::Delimiter {
414                parser.consume();
415            }
416            if parser.token != Token::Eof {
417                parser.expected_error("Unexpected token after statement")
418            }
419            Some(v)
420        }
421        Ok(None) => {
422            parser.expected_error("Statement");
423            None
424        }
425        Err(_) => None,
426    }
427}
428
429#[test]
430pub fn test_parse_alter_sql() {
431    let sql = "ALTER TABLE `test` ADD COLUMN `test1` VARCHAR (128) NULL DEFAULT NULL";
432    let options = ParseOptions::new()
433        .dialect(SQLDialect::MariaDB)
434        .arguments(SQLArguments::QuestionMark)
435        .warn_unquoted_identifiers(false);
436
437    let mut issues = Issues::new(sql);
438    parse_statement(sql, &mut issues, &options);
439    assert!(issues.is_ok(), "{}", issues);
440}
441
442#[test]
443pub fn test_parse_delete_sql_with_schema() {
444    let sql = "DROP TABLE IF EXISTS `test_schema`.`test`";
445    let options = ParseOptions::new()
446        .dialect(SQLDialect::MariaDB)
447        .arguments(SQLArguments::QuestionMark)
448        .warn_unquoted_identifiers(false);
449
450    let mut issues = Issues::new(sql);
451    parse_statement(sql, &mut issues, &options);
452    assert!(issues.is_ok(), "{}", issues);
453}
454
455#[test]
456pub fn parse_create_index_sql_with_schema() {
457    let sql = "CREATE INDEX `idx_test` ON  test_schema.test(`col_test`)";
458    let options = ParseOptions::new()
459        .dialect(SQLDialect::MariaDB)
460        .arguments(SQLArguments::QuestionMark)
461        .warn_unquoted_identifiers(false);
462
463    let mut issues = Issues::new(sql);
464    parse_statement(sql, &mut issues, &options);
465    assert!(issues.is_ok(), "{}", issues);
466}
467
468#[test]
469pub fn parse_create_index_sql_with_opclass() {
470    let sql = "CREATE INDEX idx_test ON test(path text_pattern_ops)";
471    let options = ParseOptions::new()
472        .dialect(SQLDialect::PostgreSQL)
473        .arguments(SQLArguments::Dollar)
474        .warn_unquoted_identifiers(false);
475
476    let mut issues = Issues::new(sql);
477    parse_statement(sql, &mut issues, &options);
478    assert!(issues.is_ok(), "{}", issues);
479}
480
481#[test]
482pub fn parse_drop_index_sql_with_schema() {
483    let sql = "DROP INDEX `idx_test` ON  test_schema.test";
484    let options = ParseOptions::new()
485        .dialect(SQLDialect::MariaDB)
486        .arguments(SQLArguments::QuestionMark)
487        .warn_unquoted_identifiers(false);
488
489    let mut issues = Issues::new(sql);
490    let _result = parse_statement(sql, &mut issues, &options);
491    // assert!(result.is_none(), "result: {:#?}", &result);
492    assert!(issues.is_ok(), "{}", issues);
493}
494
495#[test]
496pub fn parse_create_view_sql_with_schema() {
497    let sql =
498        "CREATE OR REPLACE VIEW `test_schema`.`view_test` AS SELECT * FROM `test_schema`.`test`";
499    let options = ParseOptions::new()
500        .dialect(SQLDialect::MariaDB)
501        .arguments(SQLArguments::QuestionMark)
502        .warn_unquoted_identifiers(false);
503
504    let mut issues = Issues::new(sql);
505    let _result = parse_statement(sql, &mut issues, &options);
506    // assert!(result.is_none(), "result: {:#?}", &result);
507    assert!(issues.is_ok(), "{}", issues);
508}
509
510#[test]
511pub fn parse_drop_view_sql_with_schema() {
512    let sql = "DROP VIEW `test_schema`.`view_test`";
513    let options = ParseOptions::new()
514        .dialect(SQLDialect::MariaDB)
515        .arguments(SQLArguments::QuestionMark)
516        .warn_unquoted_identifiers(false);
517
518    let mut issues = Issues::new(sql);
519    let _result = parse_statement(sql, &mut issues, &options);
520    // assert!(result.is_none(), "result: {:#?}", &result);
521    assert!(issues.is_ok(), "{}", issues);
522}
523
524#[test]
525pub fn parse_truncate_table_sql_with_schema() {
526    let sql = "TRUNCATE TABLE `test_schema`.`table_test`";
527    let options = ParseOptions::new()
528        .dialect(SQLDialect::MariaDB)
529        .arguments(SQLArguments::QuestionMark)
530        .warn_unquoted_identifiers(false);
531
532    let mut issues = Issues::new(sql);
533    let _result = parse_statement(sql, &mut issues, &options);
534    // assert!(result.is_none(), "result: {:#?}", &result);
535    assert!(issues.is_ok(), "{}", issues);
536}
537
538#[test]
539pub fn parse_rename_table_sql_with_schema() {
540    let sql = "RENAME TABLE `test_schema`.`table_test` To `test_schema`.`table_new_test`";
541    let options = ParseOptions::new()
542        .dialect(SQLDialect::MariaDB)
543        .arguments(SQLArguments::QuestionMark)
544        .warn_unquoted_identifiers(false);
545
546    let mut issues = Issues::new(sql);
547    let _result = parse_statement(sql, &mut issues, &options);
548    // assert!(result.is_none(), "result: {:#?}", &result);
549    assert!(issues.is_ok(), "{}", issues);
550}
551
552#[test]
553pub fn parse_with_statement() {
554    let sql = "
555        WITH monkeys AS (DELETE FROM thing RETURNING id),
556        baz AS (SELECT id FROM cats WHERE comp IN (monkeys))
557        DELETE FROM dogs WHERE cat IN (cats)";
558    let options = ParseOptions::new()
559        .dialect(SQLDialect::PostgreSQL)
560        .arguments(SQLArguments::QuestionMark)
561        .warn_unquoted_identifiers(false);
562
563    let mut issues = Issues::new(sql);
564    let _result = parse_statement(sql, &mut issues, &options);
565    // assert!(result.is_none(), "result: {:#?}", &result);
566    assert!(issues.is_ok(), "{}", issues);
567}
568
569#[test]
570pub fn parse_use_index() {
571    let sql = "SELECT `a` FROM `b` FORCE INDEX FOR GROUP BY (`b`, `c`)";
572    let options = ParseOptions::new()
573        .dialect(SQLDialect::MariaDB)
574        .arguments(SQLArguments::QuestionMark)
575        .warn_unquoted_identifiers(false);
576
577    let mut issues = Issues::new(sql);
578    let _result = parse_statement(sql, &mut issues, &options);
579    assert!(issues.is_ok(), "{}", issues);
580}