Skip to main content

qusql_parse/
lock.rs

1use alloc::vec::Vec;
2
3use crate::{
4    Identifier, QualifiedName, Span, Spanned,
5    keywords::Keyword,
6    lexer::Token,
7    parser::{ParseError, Parser},
8    qualified_name::parse_qualified_name_unreserved,
9};
10
11#[derive(Clone, Debug)]
12pub enum LockType {
13    Read(Span),
14    ReadLocal(Span),
15    Write(Span),
16    LowPriorityWrite(Span),
17}
18
19impl Spanned for LockType {
20    fn span(&self) -> Span {
21        match self {
22            LockType::Read(s) => s.clone(),
23            LockType::ReadLocal(s) => s.clone(),
24            LockType::Write(s) => s.clone(),
25            LockType::LowPriorityWrite(s) => s.clone(),
26        }
27    }
28}
29
30#[derive(Clone, Debug)]
31pub struct LockMember<'a> {
32    pub table_name: QualifiedName<'a>,
33    pub alias: Option<Identifier<'a>>,
34    pub lock_type: LockType,
35}
36
37impl Spanned for LockMember<'_> {
38    fn span(&self) -> Span {
39        self.table_name
40            .span()
41            .join_span(&self.alias)
42            .join_span(&self.lock_type)
43    }
44}
45
46/// Represent a MySQL `LOCK` statement
47/// ```
48/// # use qusql_parse::{SQLDialect, SQLArguments, ParseOptions, parse_statements,
49///   Statement, Issues};
50/// # let options = ParseOptions::new().dialect(SQLDialect::MariaDB);
51/// let sql = "LOCK TABLES t1 AS a READ, t2 WRITE;";
52/// let mut issues = Issues::new(sql);
53/// let mut stmts = parse_statements(sql, &mut issues, &options);
54/// # assert!(issues.is_ok(), "{}", issues);
55/// let lock_stmt = match stmts.pop() {
56///    Some(Statement::Lock(l)) => l,
57///   _ => panic!("We should get a lock statement"),
58/// };
59/// ```
60#[derive(Clone, Debug)]
61pub struct Lock<'a> {
62    pub lock_span: Span,
63    pub tables_span: Span,
64    pub members: Vec<LockMember<'a>>,
65}
66
67impl Spanned for Lock<'_> {
68    fn span(&self) -> Span {
69        self.lock_span
70            .join_span(&self.tables_span)
71            .join_span(&self.members)
72    }
73}
74
75pub(crate) fn parse_lock<'a>(parser: &mut Parser<'a, '_>) -> Result<Lock<'a>, ParseError> {
76    let lock_span = parser.consume_keyword(Keyword::LOCK)?;
77
78    let tables_span = match parser.token {
79        Token::Ident(_, Keyword::TABLE) => parser.consume_keyword(Keyword::TABLE)?,
80        Token::Ident(_, Keyword::TABLES) => parser.consume_keyword(Keyword::TABLES)?,
81        _ => return parser.expected_failure("'TABLE' | 'TABLES'"),
82    };
83
84    let mut members = Vec::new();
85    loop {
86        let table_name = parse_qualified_name_unreserved(parser)?;
87
88        let alias = if parser.skip_keyword(Keyword::AS).is_some() {
89            Some(parser.consume_plain_identifier_unreserved()?)
90        } else if matches!(
91            parser.token,
92            Token::Ident(_, kw) if !matches!(kw, Keyword::READ | Keyword::WRITE | Keyword::LOW_PRIORITY)
93        ) {
94            // Optional AS: consume identifier if it's not a lock type keyword
95            Some(parser.consume_plain_identifier_unreserved()?)
96        } else {
97            None
98        };
99
100        let lock_type = match &parser.token {
101            Token::Ident(_, Keyword::READ) => {
102                let read_span = parser.consume_keyword(Keyword::READ)?;
103                if let Some(local_span) = parser.skip_keyword(Keyword::LOCAL) {
104                    LockType::ReadLocal(read_span.join_span(&local_span))
105                } else {
106                    LockType::Read(read_span)
107                }
108            }
109            Token::Ident(_, Keyword::LOW_PRIORITY) => {
110                let span = parser.consume_keywords(&[Keyword::LOW_PRIORITY, Keyword::WRITE])?;
111                LockType::LowPriorityWrite(span)
112            }
113            Token::Ident(_, Keyword::WRITE) => {
114                let write_span = parser.consume_keyword(Keyword::WRITE)?;
115                LockType::Write(write_span)
116            }
117            _ => return parser.expected_failure("'READ' | 'WRITE'"),
118        };
119
120        members.push(LockMember {
121            table_name,
122            alias,
123            lock_type,
124        });
125
126        if parser.skip_token(Token::Comma).is_none() {
127            break;
128        }
129    }
130    Ok(Lock {
131        lock_span,
132        tables_span,
133        members,
134    })
135}
136
137/// Represent a MySQL `UNLOCK` statement
138/// ```
139/// # use qusql_parse::{SQLDialect, SQLArguments, ParseOptions, parse_statements,
140///  Statement, Issues};
141/// # let options = ParseOptions::new().dialect(SQLDialect::MariaDB);
142/// let sql = "UNLOCK TABLES;";
143/// let mut issues = Issues::new(sql);
144/// let mut stmts = parse_statements(sql, &mut issues, &options);
145/// # assert!(issues.is_ok(), "{}", issues);
146/// let unlock_stmt = match stmts.pop() {
147///    Some(Statement::Unlock(u)) => u,
148///  _ => panic!("We should get an unlock statement"),
149/// };
150/// ```
151#[derive(Clone, Debug)]
152pub struct Unlock {
153    pub unlock_span: Span,
154    pub tables_span: Span,
155}
156
157impl Spanned for Unlock {
158    fn span(&self) -> Span {
159        self.unlock_span.join_span(&self.tables_span)
160    }
161}
162
163pub(crate) fn parse_unlock<'a>(parser: &mut Parser<'a, '_>) -> Result<Unlock, ParseError> {
164    let unlock_span = parser.consume_keyword(Keyword::UNLOCK)?;
165
166    let tables_span = match parser.token {
167        Token::Ident(_, Keyword::TABLE) => parser.consume_keyword(Keyword::TABLE)?,
168        Token::Ident(_, Keyword::TABLES) => parser.consume_keyword(Keyword::TABLES)?,
169        _ => return parser.expected_failure("'TABLE' | 'TABLES'"),
170    };
171
172    Ok(Unlock {
173        unlock_span,
174        tables_span,
175    })
176}