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