sql_parse/
drop.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
13use alloc::vec::Vec;
14
15use crate::{
16    keywords::Keyword,
17    lexer::Token,
18    parser::{ParseError, Parser},
19    qualified_name::parse_qualified_name,
20    Identifier, QualifiedName, Span, Spanned, Statement,
21};
22
23/// Represent a drop table statement
24/// ```
25/// # use sql_parse::{SQLDialect, SQLArguments, ParseOptions, parse_statements, DropTable, Statement, Issues};
26/// # let options = ParseOptions::new().dialect(SQLDialect::MariaDB);
27/// #
28/// let sql = "DROP TABLE `Employees`, `Customers`;";
29/// let mut issues = Issues::new(sql);
30///
31/// let mut stmts = parse_statements(sql, &mut issues, &options);
32///
33/// # assert!(issues.is_ok());
34/// let delete: DropTable = match stmts.pop() {
35///     Some(Statement::DropTable(d)) => d,
36///     _ => panic!("We should get a drop table statement")
37/// };
38///
39/// assert!(delete.tables.get(0).unwrap().identifier.as_str() == "Employees");
40/// ```
41#[derive(Debug, Clone)]
42pub struct DropTable<'a> {
43    /// Span of "DROP"
44    pub drop_span: Span,
45    /// Span of "TEMPORARY" if specified
46    pub temporary: Option<Span>,
47    /// Span of "TABLE"
48    pub table_span: Span,
49    /// Span of "IF EXISTS" if specified
50    pub if_exists: Option<Span>,
51    /// List of tables to drops
52    pub tables: Vec<QualifiedName<'a>>,
53    /// Span of "CASCADE" if specified
54    pub cascade: Option<Span>,
55}
56
57impl<'a> Spanned for DropTable<'a> {
58    fn span(&self) -> Span {
59        self.drop_span
60            .join_span(&self.temporary)
61            .join_span(&self.table_span)
62            .join_span(&self.if_exists)
63            .join_span(&self.tables)
64    }
65}
66
67/// Represent a drop view statement
68/// ```
69/// # use sql_parse::{SQLDialect, SQLArguments, ParseOptions, parse_statements, DropView, Statement, Issues};
70/// # let options = ParseOptions::new().dialect(SQLDialect::MariaDB);
71/// #
72/// let sql = "DROP VIEW `Employees`, `Customers`;";
73/// let mut issues = Issues::new(sql);
74/// let mut stmts = parse_statements(sql, &mut issues, &options);
75///
76/// # assert!(issues.is_ok());
77/// let delete: DropView = match stmts.pop() {
78///     Some(Statement::DropView(d)) => d,
79///     _ => panic!("We should get a drop table statement")
80/// };
81///
82/// assert!(delete.views.get(0).unwrap().identifier.as_str() == "Employees");
83/// ```
84#[derive(Debug, Clone)]
85pub struct DropView<'a> {
86    /// Span of "DROP"
87    pub drop_span: Span,
88    /// Span of "TEMPORARY" if specified
89    pub temporary: Option<Span>,
90    /// Span of "VIEW"
91    pub view_span: Span,
92    /// Span of "IF EXISTS"
93    pub if_exists: Option<Span>,
94    /// List of views to drop
95    pub views: Vec<QualifiedName<'a>>,
96}
97
98impl<'a> Spanned for DropView<'a> {
99    fn span(&self) -> Span {
100        self.drop_span
101            .join_span(&self.temporary)
102            .join_span(&self.view_span)
103            .join_span(&self.if_exists)
104            .join_span(&self.views)
105    }
106}
107
108/// Represent a drop database statement
109/// ```
110/// # use sql_parse::{SQLDialect, SQLArguments, ParseOptions, parse_statements, DropDatabase, Statement, Issues};
111/// # let options = ParseOptions::new().dialect(SQLDialect::MariaDB);
112/// #
113/// let sql = "DROP DATABASE mydb;";
114/// let mut issues = Issues::new(sql);
115/// let mut stmts = parse_statements(sql, &mut issues, &options);
116///
117/// # assert!(issues.is_ok());
118/// #
119/// let s: DropDatabase = match stmts.pop() {
120///     Some(Statement::DropDatabase(s)) => s,
121///     _ => panic!("We should get a drop database statement")
122/// };
123///
124/// assert!(s.database.as_str() == "mydb");
125/// ```
126#[derive(Debug, Clone)]
127pub struct DropDatabase<'a> {
128    /// Span of "DROP"
129    pub drop_span: Span,
130    /// Span of "DATABASE"
131    pub database_span: Span,
132    /// Span of "IF EXISTS" if specified
133    pub if_exists: Option<Span>,
134    /// Name of database to drop
135    pub database: Identifier<'a>,
136}
137
138impl<'a> Spanned for DropDatabase<'a> {
139    fn span(&self) -> Span {
140        self.drop_span
141            .join_span(&self.database_span)
142            .join_span(&self.if_exists)
143            .join_span(&self.database)
144    }
145}
146
147/// Represent a drop event statement
148/// ```
149/// # use sql_parse::{SQLDialect, SQLArguments, ParseOptions, parse_statements, DropEvent, Statement, Issues};
150/// # let options = ParseOptions::new().dialect(SQLDialect::MariaDB);
151/// #
152/// let sql = "DROP EVENT myevent;";
153/// let mut issues = Issues::new(sql);
154/// let mut stmts = parse_statements(sql, &mut issues, &options);
155///
156/// # assert!(issues.is_ok());
157/// let s: DropEvent = match stmts.pop() {
158///     Some(Statement::DropEvent(s)) => s,
159///     _ => panic!("We should get a drop event statement")
160/// };
161///
162/// assert!(s.event.identifier.as_str() == "myevent");
163/// ```
164#[derive(Debug, Clone)]
165pub struct DropEvent<'a> {
166    /// Span of "DROP"
167    pub drop_span: Span,
168    /// Span of "EVENT"
169    pub event_span: Span,
170    /// Span of "IF EXISTS" if specified
171    pub if_exists: Option<Span>,
172    /// Event to drop
173    pub event: QualifiedName<'a>,
174}
175
176impl<'a> Spanned for DropEvent<'a> {
177    fn span(&self) -> Span {
178        self.drop_span
179            .join_span(&self.event_span)
180            .join_span(&self.if_exists)
181            .join_span(&self.event)
182    }
183}
184
185/// Represent a drop function statement
186/// ```
187/// # use sql_parse::{SQLDialect, SQLArguments, ParseOptions, parse_statements, DropFunction, Statement, Issues};
188/// # let options = ParseOptions::new().dialect(SQLDialect::MariaDB);
189/// #
190/// let sql = "DROP FUNCTION myfunc;";
191/// let mut issues = Issues::new(sql);
192/// let mut stmts = parse_statements(sql, &mut issues, &options);
193///
194/// # assert!(issues.is_ok());
195/// let s: DropFunction = match stmts.pop() {
196///     Some(Statement::DropFunction(s)) => s,
197///     _ => panic!("We should get a drop function statement")
198/// };
199///
200/// assert!(s.function.identifier.as_str() == "myfunc");
201/// ```
202#[derive(Debug, Clone)]
203pub struct DropFunction<'a> {
204    /// Span of "DROP"
205    pub drop_span: Span,
206    /// Span of "FUNCTION"
207    pub function_span: Span,
208    /// Span of "IF EXISTS" if specified
209    pub if_exists: Option<Span>,
210    /// Function to drop
211    pub function: QualifiedName<'a>,
212}
213
214impl<'a> Spanned for DropFunction<'a> {
215    fn span(&self) -> Span {
216        self.drop_span
217            .join_span(&self.function_span)
218            .join_span(&self.if_exists)
219            .join_span(&self.function)
220    }
221}
222
223/// Represent a drop procedure statement
224/// ```
225/// # use sql_parse::{SQLDialect, SQLArguments, ParseOptions, parse_statements, DropProcedure, Statement, Issues};
226/// # let options = ParseOptions::new().dialect(SQLDialect::MariaDB);
227/// #
228/// let sql = "DROP PROCEDURE myproc;";
229/// let mut issues = Issues::new(sql);
230/// let mut stmts = parse_statements(sql, &mut issues, &options);
231///
232/// # assert!(issues.is_ok());
233/// let s: DropProcedure = match stmts.pop() {
234///     Some(Statement::DropProcedure(s)) => s,
235///     _ => panic!("We should get a drop procedure statement")
236/// };
237///
238/// assert!(s.procedure.identifier.as_str() == "myproc");
239/// ```
240#[derive(Debug, Clone)]
241pub struct DropProcedure<'a> {
242    /// Span of "DROP"
243    pub drop_span: Span,
244    /// Span of "PROCEDURE"
245    pub procedure_span: Span,
246    /// Span of "IF EXISTS" if specified
247    pub if_exists: Option<Span>,
248    /// Procedure to drop
249    pub procedure: QualifiedName<'a>,
250}
251
252impl<'a> Spanned for DropProcedure<'a> {
253    fn span(&self) -> Span {
254        self.drop_span
255            .join_span(&self.procedure_span)
256            .join_span(&self.if_exists)
257            .join_span(&self.procedure)
258    }
259}
260
261/// Represent a drop server statement
262/// ```
263/// # use sql_parse::{SQLDialect, SQLArguments, ParseOptions, parse_statements, DropServer, Statement, Issues};
264/// # let options = ParseOptions::new().dialect(SQLDialect::MariaDB);
265/// #
266/// let sql = "DROP SERVER myserver;";
267/// let mut issues = Issues::new(sql);
268/// let mut stmts = parse_statements(sql, &mut issues, &options);
269///
270/// # assert!(issues.is_ok());
271/// #
272/// let s: DropServer = match stmts.pop() {
273///     Some(Statement::DropServer(s)) => s,
274///     _ => panic!("We should get a drop server statement")
275/// };
276///
277/// assert!(s.server.as_str() == "myserver");
278/// ```
279#[derive(Debug, Clone)]
280pub struct DropServer<'a> {
281    /// Span of "DROP"
282    pub drop_span: Span,
283    /// Span of "SERVER"
284    pub server_span: Span,
285    /// Span of "IF EXISTS" if specified
286    pub if_exists: Option<Span>,
287    /// Server to drop
288    pub server: Identifier<'a>,
289}
290
291impl<'a> Spanned for DropServer<'a> {
292    fn span(&self) -> Span {
293        self.drop_span
294            .join_span(&self.server_span)
295            .join_span(&self.if_exists)
296            .join_span(&self.server)
297    }
298}
299
300/// Represent a drop trigger statement
301/// ```
302/// # use sql_parse::{SQLDialect, SQLArguments, ParseOptions, parse_statements, DropTrigger, Statement, Issues};
303/// # let options = ParseOptions::new().dialect(SQLDialect::MariaDB);
304/// #
305/// let sql = "DROP TRIGGER IF EXISTS `foo`.`mytrigger`;";
306/// let mut issues = Issues::new(sql);
307/// let mut stmts = parse_statements(sql, &mut issues, &options);
308///
309/// # assert!(issues.is_ok());
310/// let s: DropTrigger = match stmts.pop() {
311///     Some(Statement::DropTrigger(s)) => s,
312///     _ => panic!("We should get a drop trigger statement")
313/// };
314///
315/// assert!(s.identifier.identifier.as_str() == "mytrigger");
316/// ```
317#[derive(Debug, Clone)]
318pub struct DropTrigger<'a> {
319    /// Span of "DROP"
320    pub drop_span: Span,
321    /// Span of "TRIGGER"
322    pub trigger_span: Span,
323    /// Span of "IF EXISTS" if specified
324    pub if_exists: Option<Span>,
325    /// Trigger to drop
326    pub identifier: QualifiedName<'a>,
327}
328
329impl<'a> Spanned for DropTrigger<'a> {
330    fn span(&self) -> Span {
331        self.drop_span
332            .join_span(&self.trigger_span)
333            .join_span(&self.if_exists)
334            .join_span(&self.identifier)
335    }
336}
337
338pub(crate) fn parse_drop<'a>(parser: &mut Parser<'a, '_>) -> Result<Statement<'a>, ParseError> {
339    let drop_span = parser.consume_keyword(Keyword::DROP)?;
340    let temporary = parser.skip_keyword(Keyword::TEMPORARY);
341    match &parser.token {
342        Token::Ident(_, Keyword::TABLE) => {
343            let table_span = parser.consume_keyword(Keyword::TABLE)?;
344            let if_exists = if let Some(span) = parser.skip_keyword(Keyword::IF) {
345                Some(parser.consume_keyword(Keyword::EXISTS)?.join_span(&span))
346            } else {
347                None
348            };
349            let mut tables = Vec::new();
350            loop {
351                tables.push(parse_qualified_name(parser)?);
352                if parser.skip_token(Token::Comma).is_none() {
353                    break;
354                }
355            }
356            let cascade = if parser.options.dialect.is_postgresql() {
357                parser.skip_keyword(Keyword::CASCADE)
358            } else {
359                None
360            };
361            Ok(Statement::DropTable(DropTable {
362                drop_span,
363                temporary,
364                table_span,
365                if_exists,
366                tables,
367                cascade,
368            }))
369        }
370        Token::Ident(_, kw @ Keyword::DATABASE | kw @ Keyword::SCHEMA) => {
371            // TODO complain about temporary
372            let kw = *kw;
373            let database_span = parser.consume_keyword(kw)?;
374            let if_exists = if let Some(span) = parser.skip_keyword(Keyword::IF) {
375                Some(parser.consume_keyword(Keyword::EXISTS)?.join_span(&span))
376            } else {
377                None
378            };
379            let database = parser.consume_plain_identifier()?;
380            Ok(Statement::DropDatabase(DropDatabase {
381                drop_span,
382                database_span,
383                if_exists,
384                database,
385            }))
386        }
387        Token::Ident(_, Keyword::EVENT) => {
388            // TODO complain about temporary
389            let event_span = parser.consume_keyword(Keyword::EVENT)?;
390            let if_exists = if let Some(span) = parser.skip_keyword(Keyword::IF) {
391                Some(parser.consume_keyword(Keyword::EXISTS)?.join_span(&span))
392            } else {
393                None
394            };
395            let event = parse_qualified_name(parser)?;
396            Ok(Statement::DropEvent(DropEvent {
397                drop_span,
398                event_span,
399                if_exists,
400                event,
401            }))
402        }
403        Token::Ident(_, Keyword::FUNCTION) => {
404            // TODO complain about temporary
405            let function_span = parser.consume_keyword(Keyword::FUNCTION)?;
406            let if_exists = if let Some(span) = parser.skip_keyword(Keyword::IF) {
407                Some(parser.consume_keyword(Keyword::EXISTS)?.join_span(&span))
408            } else {
409                None
410            };
411            let function = parse_qualified_name(parser)?;
412            Ok(Statement::DropFunction(DropFunction {
413                drop_span,
414                function_span,
415                if_exists,
416                function,
417            }))
418        }
419        Token::Ident(_, Keyword::INDEX) => {
420            // DROP INDEX [IF EXISTS] index_name ON tbl_name
421            let index_span = parser.consume_keyword(Keyword::INDEX)?;
422            let if_exists = if let Some(span) = parser.skip_keyword(Keyword::IF) {
423                Some(parser.consume_keyword(Keyword::EXISTS)?.join_span(&span))
424            } else {
425                None
426            };
427            let index_name = parser.consume_plain_identifier()?;
428            let on = if let Some(span) = parser.skip_keyword(Keyword::ON) {
429                let table_name = parse_qualified_name(parser)?;
430                Some((span, table_name))
431            } else {
432                None
433            };
434
435            let v = DropIndex {
436                drop_span,
437                index_span,
438                if_exists,
439                index_name,
440                on,
441            };
442
443            if v.on.is_none() && parser.options.dialect.is_maria() {
444                parser.err("On required for index drops in MariaDb", &v);
445            }
446            if v.on.is_some() && parser.options.dialect.is_postgresql() {
447                parser.err("On not supported for index drops in PostgreSQL", &v);
448            }
449            Ok(Statement::DropIndex(v))
450        }
451        Token::Ident(_, Keyword::PROCEDURE) => {
452            // TODO complain about temporary
453            let procedure_span = parser.consume_keyword(Keyword::PROCEDURE)?;
454            let if_exists = if let Some(span) = parser.skip_keyword(Keyword::IF) {
455                Some(parser.consume_keyword(Keyword::EXISTS)?.join_span(&span))
456            } else {
457                None
458            };
459            let procedure = parse_qualified_name(parser)?;
460            Ok(Statement::DropProcedure(DropProcedure {
461                drop_span,
462                procedure_span,
463                if_exists,
464                procedure,
465            }))
466        }
467        Token::Ident(_, Keyword::SEQUENCE) => {
468            // DROP [TEMPORARY] SEQUENCE [IF EXISTS] [/*COMMENT TO SAVE*/] sequence_name [, sequence_name] ...
469            parser.todo(file!(), line!())
470        }
471        Token::Ident(_, Keyword::SERVER) => {
472            // TODO complain about temporary
473            let server_span = parser.consume_keyword(Keyword::SERVER)?;
474            let if_exists = if let Some(span) = parser.skip_keyword(Keyword::IF) {
475                Some(parser.consume_keyword(Keyword::EXISTS)?.join_span(&span))
476            } else {
477                None
478            };
479            let server = parser.consume_plain_identifier()?;
480            Ok(Statement::DropServer(DropServer {
481                drop_span,
482                server_span,
483                if_exists,
484                server,
485            }))
486        }
487        Token::Ident(_, Keyword::TRIGGER) => {
488            let trigger_span = parser.consume_keyword(Keyword::TRIGGER)?;
489            let if_exists = if let Some(span) = parser.skip_keyword(Keyword::IF) {
490                Some(parser.consume_keyword(Keyword::EXISTS)?.join_span(&span))
491            } else {
492                None
493            };
494            let identifier = parse_qualified_name(parser)?;
495            Ok(Statement::DropTrigger(DropTrigger {
496                drop_span,
497                trigger_span,
498                if_exists,
499                identifier,
500            }))
501        }
502        Token::Ident(_, Keyword::VIEW) => {
503            let view_span = parser.consume_keyword(Keyword::VIEW)?;
504            let if_exists = if let Some(span) = parser.skip_keyword(Keyword::IF) {
505                Some(parser.consume_keyword(Keyword::EXISTS)?.join_span(&span))
506            } else {
507                None
508            };
509            let mut views = Vec::new();
510            loop {
511                views.push(parse_qualified_name(parser)?);
512                if parser.skip_token(Token::Comma).is_none() {
513                    break;
514                }
515            }
516            // TODO  [RESTRICT | CASCADE]
517            Ok(Statement::DropView(DropView {
518                drop_span,
519                temporary,
520                view_span,
521                if_exists,
522                views,
523            }))
524        }
525        Token::Ident(_, Keyword::USER) => {
526            // DROP USER [IF EXISTS] user_name [, user_name] ..
527            parser.todo(file!(), line!())
528        }
529        _ => parser.expected_failure("droppable"),
530    }
531}
532
533/// Represent a drop index statement.
534///
535/// MariaDB example
536/// ```
537/// # use sql_parse::{SQLDialect, SQLArguments, ParseOptions, parse_statements, DropIndex, Statement, Issues};
538/// # let options = ParseOptions::new().dialect(SQLDialect::MariaDB);
539/// #
540/// let sql = "DROP INDEX IF EXISTS `myindex` ON `bar`;";
541/// let mut issues = Issues::new(sql);
542/// let mut stmts = parse_statements(sql, &mut issues, &options);
543///
544/// # assert!(issues.is_ok());
545/// let s: DropIndex = match stmts.pop() {
546///     Some(Statement::DropIndex(s)) => s,
547///     _ => panic!("We should get a drop trigger statement")
548/// };
549///
550/// assert!(s.index_name.as_str() == "myindex");
551/// ```
552///
553/// PostgreSQL example
554/// ```
555/// # use sql_parse::{SQLDialect, SQLArguments, ParseOptions, parse_statements, DropIndex, Statement, Issues};
556/// # let options = ParseOptions::new().dialect(SQLDialect::PostgreSQL);
557/// #
558/// let sql = "DROP INDEX IF EXISTS \"myindex\";";
559/// let mut issues = Issues::new(sql);
560/// let mut stmts = parse_statements(sql, &mut issues, &options);
561///
562/// # assert!(issues.is_ok(), "{}", issues);
563/// #
564/// let s: DropIndex = match stmts.pop() {
565///     Some(Statement::DropIndex(s)) => s,
566///     _ => panic!("We should get a drop trigger statement")
567/// };
568///
569/// assert!(s.index_name.as_str() == "myindex");
570/// ```
571#[derive(Debug, Clone)]
572pub struct DropIndex<'a> {
573    /// Span of "DROP"
574    pub drop_span: Span,
575    /// Span of "INDEX"
576    pub index_span: Span,
577    /// Span of "IF EXISTS" if specified
578    pub if_exists: Option<Span>,
579    pub index_name: Identifier<'a>,
580    pub on: Option<(Span, QualifiedName<'a>)>,
581}
582
583impl<'a> Spanned for DropIndex<'a> {
584    fn span(&self) -> Span {
585        self.drop_span
586            .join_span(&self.index_span)
587            .join_span(&self.if_exists)
588            .join_span(&self.index_name)
589            .join_span(&self.on)
590    }
591}