Skip to main content

qusql_parse/
truncate.rs

1use crate::{
2    QualifiedName, Span, Spanned,
3    drop::{CascadeOrRestrict, parse_cascade_or_restrict},
4    keywords::Keyword,
5    lexer::Token,
6    parser::{ParseError, Parser},
7    qualified_name::parse_qualified_name_unreserved,
8};
9
10use alloc::vec::Vec;
11
12/// Represent a truncate table statement
13/// ```
14/// # use qusql_parse::{SQLDialect, SQLArguments, ParseOptions, parse_statements, TruncateTable, Statement, Issues};
15/// # let options = ParseOptions::new().dialect(SQLDialect::MariaDB);
16/// #
17/// let sql = "TRUNCATE TABLE `t1`;";
18/// let mut issues = Issues::new(sql);
19/// let mut stmts = parse_statements(sql, &mut issues, &options);
20///
21/// # assert!(issues.is_ok(), "Issues: {}", issues);
22/// #
23/// let truncate_table: TruncateTable = match stmts.pop() {
24///     Some(Statement::TruncateTable(c)) => *c,
25///     _ => panic!("We should get a truncate table statement")
26/// };
27///
28/// assert!(truncate_table.tables.len() == 1);
29/// assert!(truncate_table.tables[0].table_name.identifier.as_str() == "t1");
30///
31/// ```
32/// A table specification in a TRUNCATE statement
33#[derive(Debug, Clone)]
34pub struct TruncateTableSpec<'a> {
35    /// Span of "ONLY" if specified (PostgreSQL)
36    pub only_span: Option<Span>,
37    /// Name of the table to truncate
38    pub table_name: QualifiedName<'a>,
39    /// Span of "*" if specified (PostgreSQL - include descendants)
40    pub descendants_span: Option<Span>,
41}
42
43impl<'a> Spanned for TruncateTableSpec<'a> {
44    fn span(&self) -> Span {
45        self.table_name
46            .span()
47            .join_span(&self.only_span)
48            .join_span(&self.descendants_span)
49    }
50}
51
52/// Identity restart option for TRUNCATE (PostgreSQL)
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub enum IdentityOption {
55    /// RESTART IDENTITY
56    Restart(Span),
57    /// CONTINUE IDENTITY
58    Continue(Span),
59}
60
61impl Spanned for IdentityOption {
62    fn span(&self) -> Span {
63        match self {
64            IdentityOption::Restart(span) => span.clone(),
65            IdentityOption::Continue(span) => span.clone(),
66        }
67    }
68}
69
70#[derive(Debug, Clone)]
71pub struct TruncateTable<'a> {
72    /// Span of "TRUNCATE"
73    pub truncate_span: Span,
74    /// Span of "TABLE" if specified
75    pub table_span: Option<Span>,
76    /// List of tables to truncate
77    pub tables: Vec<TruncateTableSpec<'a>>,
78    /// Identity restart option (PostgreSQL)
79    pub identity_option: Option<IdentityOption>,
80    /// Cascade or restrict option (PostgreSQL)
81    pub cascade_or_restrict: Option<CascadeOrRestrict>,
82}
83
84impl<'a> Spanned for TruncateTable<'a> {
85    fn span(&self) -> Span {
86        self.truncate_span
87            .join_span(&self.table_span)
88            .join_span(&self.tables)
89            .join_span(&self.identity_option)
90            .join_span(&self.cascade_or_restrict)
91    }
92}
93
94pub(crate) fn parse_truncate_table<'a>(
95    parser: &mut Parser<'a, '_>,
96) -> Result<TruncateTable<'a>, ParseError> {
97    let truncate_span = parser.consume_keyword(Keyword::TRUNCATE)?;
98    let table_span = parser.skip_keyword(Keyword::TABLE);
99
100    let mut tables = Vec::new();
101
102    // Parse comma-separated list of tables
103    loop {
104        let only_span = parser.skip_keyword(Keyword::ONLY);
105        parser.postgres_only(&only_span);
106
107        let table_name = parse_qualified_name_unreserved(parser)?;
108
109        let descendants_span = parser.skip_token(Token::Mul);
110        parser.postgres_only(&descendants_span);
111
112        tables.push(TruncateTableSpec {
113            only_span,
114            table_name,
115            descendants_span,
116        });
117
118        let comma_span = parser.skip_token(Token::Comma);
119        if comma_span.is_none() {
120            break;
121        }
122        // Multiple tables in TRUNCATE is PostgreSQL-only
123        parser.postgres_only(&comma_span);
124    }
125
126    // Parse RESTART IDENTITY or CONTINUE IDENTITY (PostgreSQL)
127    let identity_option = if let Some(restart_span) = parser.skip_keyword(Keyword::RESTART) {
128        let identity_span = parser.consume_keyword(Keyword::IDENTITY)?;
129        let full_span = restart_span.join_span(&identity_span);
130        parser.postgres_only(&full_span);
131        Some(IdentityOption::Restart(full_span))
132    } else if let Some(continue_span) = parser.skip_keyword(Keyword::CONTINUE) {
133        let identity_span = parser.consume_keyword(Keyword::IDENTITY)?;
134        let full_span = continue_span.join_span(&identity_span);
135        parser.postgres_only(&full_span);
136        Some(IdentityOption::Continue(full_span))
137    } else {
138        None
139    };
140
141    let cascade_or_restrict = parse_cascade_or_restrict(parser);
142
143    Ok(TruncateTable {
144        truncate_span,
145        table_span,
146        tables,
147        identity_option,
148        cascade_or_restrict,
149    })
150}