Skip to main content

wasm_dbms_sql/
parser.rs

1//! Recursive-descent parser for the supported SQL dialect.
2
3mod condition;
4mod dml;
5mod select;
6#[cfg(test)]
7mod tests;
8
9use wasm_dbms_api::prelude::SqlError;
10
11use crate::ast::Statement;
12use crate::lexer::{Keyword, Position, Spanned, Token, tokenize};
13
14/// Maximum number of terms in the conditions of one statement.
15///
16/// Every comparison, `IN`, `LIKE`, and `IS NULL` test, every `AND`, `OR`, and
17/// `NOT`, and every parenthesized group counts as one term. The bound keeps
18/// the recursion depth of the parser, and of everything that later walks the
19/// condition tree, small enough for the limited stack of a WASM runtime.
20pub(crate) const MAX_CONDITION_TERMS: usize = 256;
21
22const STATEMENT_START: &str =
23    "a statement (SELECT, INSERT, UPDATE, DELETE, BEGIN, COMMIT or ROLLBACK)";
24
25/// A parsed statement and the number of `?` placeholders it contains.
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct ParsedStatement {
28    /// The statement.
29    pub statement: Statement,
30    /// Number of `?` placeholders; the statement needs that many parameters.
31    pub parameter_count: usize,
32}
33
34/// Parses one SQL statement.
35///
36/// The statement may end with a single `;`. Keywords are case-insensitive and
37/// identifiers are case-sensitive. Names are not checked against a schema:
38/// that happens when the statement is executed.
39///
40/// # Errors
41///
42/// - [`SqlError::Parse`] when `sql` is not a single statement of the
43///   supported grammar. The error carries the 1-based line and column of the
44///   offending token.
45/// - [`SqlError::MissingWhereClause`] when an `UPDATE` or `DELETE` has no
46///   `WHERE` clause.
47///
48/// # Examples
49///
50/// ```
51/// use wasm_dbms_sql::ast::Statement;
52///
53/// let parsed = wasm_dbms_sql::parse("DELETE FROM users WHERE id = ?")?;
54/// assert!(matches!(parsed.statement, Statement::Delete(_)));
55/// assert_eq!(parsed.parameter_count, 1);
56/// # Ok::<(), wasm_dbms_api::prelude::SqlError>(())
57/// ```
58pub fn parse(sql: &str) -> Result<ParsedStatement, SqlError> {
59    let mut parser = Parser::new(sql)?;
60    let statement = parser.statement()?;
61    parser.finish()?;
62    Ok(ParsedStatement {
63        statement,
64        parameter_count: parser.parameters,
65    })
66}
67
68/// Token cursor shared by the statement parsers.
69struct Parser {
70    /// The tokens of the statement; the last one is always [`Token::Eof`].
71    tokens: Vec<Spanned>,
72    cursor: usize,
73    /// Number of `?` placeholders consumed so far.
74    parameters: usize,
75    /// Number of condition terms consumed so far.
76    condition_terms: usize,
77}
78
79impl Parser {
80    fn new(sql: &str) -> Result<Self, SqlError> {
81        Ok(Self {
82            tokens: tokenize(sql)?,
83            cursor: 0,
84            parameters: 0,
85            condition_terms: 0,
86        })
87    }
88
89    /// Returns the token `offset` places after the cursor, or `Eof` past the end.
90    fn peek_at(&self, offset: usize) -> &Token {
91        let index = (self.cursor + offset).min(self.tokens.len() - 1);
92        &self.tokens[index].token
93    }
94
95    fn peek(&self) -> &Token {
96        self.peek_at(0)
97    }
98
99    /// Returns the position of the current token.
100    fn position(&self) -> Position {
101        self.tokens[self.cursor].position
102    }
103
104    /// Moves past the current token; the cursor never moves past `Eof`.
105    fn advance(&mut self) {
106        if self.cursor < self.tokens.len() - 1 {
107            self.cursor += 1;
108        }
109    }
110
111    fn at_keyword(&self, keyword: Keyword) -> bool {
112        *self.peek() == Token::Keyword(keyword)
113    }
114
115    /// Consumes the current token if it equals `token`.
116    fn eat(&mut self, token: &Token) -> bool {
117        let found = self.peek() == token;
118        if found {
119            self.advance();
120        }
121        found
122    }
123
124    fn eat_keyword(&mut self, keyword: Keyword) -> bool {
125        self.eat(&Token::Keyword(keyword))
126    }
127
128    /// Builds a parse error located at the current token.
129    fn error(&self, msg: impl Into<String>) -> SqlError {
130        self.position().error(msg)
131    }
132
133    /// Builds an `expected ..., found ...` error located at the current token.
134    fn unexpected(&self, expected: &str) -> SqlError {
135        self.error(format!(
136            "expected {expected}, found {found}",
137            found = self.peek().describe()
138        ))
139    }
140
141    /// Consumes `token`, or fails with `expected {expected}, found ...`.
142    fn expect(&mut self, token: &Token, expected: &str) -> Result<(), SqlError> {
143        if self.eat(token) {
144            Ok(())
145        } else {
146            Err(self.unexpected(expected))
147        }
148    }
149
150    fn expect_keyword(&mut self, keyword: Keyword) -> Result<(), SqlError> {
151        let expected = format!("`{keyword}`", keyword = keyword.as_str());
152        self.expect(&Token::Keyword(keyword), &expected)
153    }
154
155    /// Consumes an identifier; `expected` names its role, e.g. `a table name`.
156    fn identifier(&mut self, expected: &str) -> Result<String, SqlError> {
157        match self.peek() {
158            Token::Identifier(name) => {
159                let name = name.clone();
160                self.advance();
161                Ok(name)
162            }
163            Token::Keyword(_) => Err(self.error(format!(
164                "expected {expected}, found {found} (reserved words must be double-quoted to be \
165                 used as names)",
166                found = self.peek().describe()
167            ))),
168            _ => Err(self.unexpected(expected)),
169        }
170    }
171
172    /// Returns the 0-based index of the `?` placeholder being consumed.
173    fn next_parameter(&mut self) -> usize {
174        let index = self.parameters;
175        self.parameters += 1;
176        index
177    }
178
179    fn statement(&mut self) -> Result<Statement, SqlError> {
180        match self.peek() {
181            Token::Keyword(Keyword::Select) => self
182                .select()
183                .map(|select| Statement::Select(Box::new(select))),
184            Token::Keyword(Keyword::Insert) => self.insert().map(Statement::Insert),
185            Token::Keyword(Keyword::Update) => self.update().map(Statement::Update),
186            Token::Keyword(Keyword::Delete) => self.delete().map(Statement::Delete),
187            Token::Keyword(Keyword::Begin) => Ok(self.transaction(Statement::Begin)),
188            Token::Keyword(Keyword::Commit) => Ok(self.transaction(Statement::Commit)),
189            Token::Keyword(Keyword::Rollback) => Ok(self.transaction(Statement::Rollback)),
190            _ => Err(self.unexpected(STATEMENT_START)),
191        }
192    }
193
194    /// Consumes `BEGIN`, `COMMIT`, or `ROLLBACK` and an optional `TRANSACTION`.
195    fn transaction(&mut self, statement: Statement) -> Statement {
196        self.advance();
197        self.eat_keyword(Keyword::Transaction);
198        statement
199    }
200
201    /// Consumes the optional trailing `;` and checks nothing follows.
202    fn finish(&mut self) -> Result<(), SqlError> {
203        let terminated = self.eat(&Token::Semicolon);
204        match self.peek() {
205            Token::Eof => Ok(()),
206            _ if terminated => Err(self.error("multiple statements are not supported")),
207            _ => Err(self.unexpected("end of statement")),
208        }
209    }
210}