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