Skip to main content

sqlite_diff_rs/builders/sql/
parser.rs

1//! SQL parser for changeset/patchset operations.
2
3use core::hash::Hash;
4
5use crate::{
6    DiffSetBuilder, PatchsetFormat, SchemaWithPK, Value, builders::operation::Operation,
7    schema::NamedColumns,
8};
9use alloc::borrow::Cow;
10use alloc::string::String;
11use alloc::vec;
12use alloc::vec::Vec;
13
14use super::lexer::{Lexer, LexerError, Token, TokenKind};
15
16/// SQL parser errors.
17#[derive(Debug, Clone, PartialEq, thiserror::Error)]
18pub enum ParseError<'a> {
19    /// Lexer error.
20    #[error("Lexer error: {0}")]
21    Lexer(#[from] LexerError),
22    /// Unexpected token.
23    #[error("Unexpected token {found:?} at position {pos}, expected {expected}")]
24    UnexpectedToken {
25        /// What was expected.
26        expected: &'static str,
27        /// What was found.
28        found: TokenKind<'a>,
29        /// Position in input.
30        pos: usize,
31    },
32    /// Unexpected end of input.
33    #[error("Unexpected end of input, expected {expected}")]
34    UnexpectedEof {
35        /// What was expected.
36        expected: &'static str,
37    },
38    /// Empty column list.
39    #[error("Empty column list in CREATE TABLE")]
40    EmptyColumnList,
41    /// Duplicate column name.
42    #[error("Duplicate column name: {0}")]
43    DuplicateColumn(String),
44    /// Unknown column in PRIMARY KEY constraint.
45    #[error("Unknown column '{column}' in PRIMARY KEY constraint")]
46    UnknownPKColumn {
47        /// The unknown column name.
48        column: String,
49    },
50    /// Unknown table name in statement.
51    #[error("Unknown table name: {0}")]
52    UnknownTable(&'a str),
53    /// Unknown column name in statement.
54    #[error("Unknown column name: {0}")]
55    UnknownColumn(&'a str),
56    /// Missing WHERE clause in UPDATE or DELETE.
57    #[error("Missing WHERE clause in {statement}")]
58    MissingWhere {
59        /// The statement type.
60        statement: &'static str,
61    },
62    /// Where constraint on non-PK column.
63    #[error("WHERE clause on non-primary key column '{column}'")]
64    WhereNonPKColumn {
65        /// The column name.
66        column: &'a str,
67    },
68}
69
70/// SQL parser.
71pub(crate) struct Parser<'input, 'builder, T: SchemaWithPK, S> {
72    lexer: Lexer<'input>,
73    builder: &'builder mut DiffSetBuilder<PatchsetFormat, T, S, Vec<u8>>,
74}
75
76impl<'input, 'builder, T: NamedColumns, S: Clone + Hash + Eq + AsRef<str> + for<'a> From<&'a str>>
77    Parser<'input, 'builder, T, S>
78{
79    /// Create a new parser for the given input.
80    #[must_use]
81    pub(crate) fn new(
82        input: &'input str,
83        builder: &'builder mut DiffSetBuilder<PatchsetFormat, T, S, Vec<u8>>,
84    ) -> Self {
85        Self {
86            lexer: Lexer::new(input),
87            builder,
88        }
89    }
90
91    /// Parse all statements from the input.
92    ///
93    /// # Errors
94    ///
95    /// Returns an error if parsing fails.
96    pub(crate) fn digest_all(&mut self) -> Result<(), ParseError<'input>> {
97        loop {
98            // Skip any semicolons (leading, trailing, between statements)
99            while self.lexer.peek()?.kind == TokenKind::Semicolon {
100                self.lexer.next()?;
101            }
102
103            if self.lexer.peek()?.kind == TokenKind::Eof {
104                break;
105            }
106
107            self.digest_statement()?;
108        }
109
110        Ok(())
111    }
112
113    /// Parse a single statement.
114    fn digest_statement(&mut self) -> Result<(), ParseError<'input>> {
115        let token = self.lexer.peek()?;
116        match &token.kind {
117            TokenKind::Insert => self.digest_insert(),
118            TokenKind::Update => self.digest_update(),
119            TokenKind::Delete => self.digest_delete(),
120            other => Err(ParseError::UnexpectedToken {
121                expected: "INSERT, UPDATE, or DELETE",
122                found: other.clone(),
123                pos: token.pos,
124            }),
125        }
126    }
127
128    /// Parse an INSERT statement.
129    fn digest_insert(&mut self) -> Result<(), ParseError<'input>> {
130        self.expect(&TokenKind::Insert)?;
131        self.expect(&TokenKind::Into)?;
132
133        let table = self.expect_table()?;
134
135        // There cannot ever possibly be more than i32::MAX columns
136        // and since they are unsigned, we use u16 for column identifiers.
137        // By default, there can be up to 2000 columns in SQLite, so this is more than enough.
138        // If a user employes SQLITE_MAX_COLUMN to set a higher limit, they still cannot exceed
139        // i16::MAX columns, so this is safe.
140        let mut column_identifiers: Vec<u16> = Vec::new();
141        // Optional column list
142        if self.lexer.peek()?.kind == TokenKind::LParen {
143            self.lexer.next()?;
144
145            loop {
146                column_identifiers.push(self.expect_column(&table)?.0);
147                if self.lexer.peek()?.kind != TokenKind::Comma {
148                    break;
149                }
150                self.lexer.next()?;
151            }
152
153            self.expect(&TokenKind::RParen)?;
154        }
155
156        self.expect(&TokenKind::Values)?;
157        self.expect(&TokenKind::LParen)?;
158
159        let mut values = vec![Value::Null; table.number_of_columns()];
160        let mut pks = vec![Value::Null; table.number_of_primary_keys()];
161
162        if column_identifiers.is_empty() {
163            // No explicit column list: values map to all columns in order
164            for (col_idx, value_ref) in values.iter_mut().enumerate() {
165                if col_idx > 0 {
166                    self.expect(&TokenKind::Comma)?;
167                }
168                *value_ref = self.parse_value()?;
169                if let Some(pk_idx) = table.primary_key_index(col_idx) {
170                    pks[pk_idx] = (*value_ref).clone();
171                }
172            }
173        } else {
174            for column_index in column_identifiers {
175                values[usize::from(column_index)] = self.parse_value()?;
176                if let Some(primary_key_index) = table.primary_key_index(usize::from(column_index))
177                {
178                    pks[primary_key_index] = values[usize::from(column_index)].clone();
179                }
180                if self.lexer.peek()?.kind != TokenKind::Comma {
181                    break;
182                }
183                self.lexer.next()?;
184            }
185        }
186
187        self.expect(&TokenKind::RParen)?;
188
189        self.builder.add_operation(
190            &table,
191            pks,
192            Operation::Insert {
193                values,
194                indirect: false,
195            },
196        );
197
198        Ok(())
199    }
200
201    /// Parse an UPDATE statement.
202    fn digest_update(&mut self) -> Result<(), ParseError<'input>> {
203        self.expect(&TokenKind::Update)?;
204
205        let table = self.expect_table()?;
206        self.expect(&TokenKind::Set)?;
207
208        let mut new_values = vec![((), None); table.number_of_columns()];
209
210        // Parse SET assignments
211        loop {
212            let (col_idx, _) = self.expect_column(&table)?;
213            self.expect(&TokenKind::Equals)?;
214            let val = self.parse_value()?;
215            new_values[usize::from(col_idx)] = ((), Some(val));
216
217            if self.lexer.peek()?.kind != TokenKind::Comma {
218                break;
219            }
220            self.lexer.next()?;
221        }
222
223        // WHERE clause is required
224        if self.lexer.peek()?.kind != TokenKind::Where {
225            return Err(ParseError::MissingWhere {
226                statement: "UPDATE",
227            });
228        }
229
230        let mut pk = vec![Value::Null; table.number_of_primary_keys()];
231
232        self.digest_where(&table, |col_idx, col_name, val| {
233            if let Some(primary_key_index) = table.primary_key_index(usize::from(col_idx)) {
234                pk[primary_key_index] = val.clone();
235                Ok(())
236            } else {
237                Err(ParseError::WhereNonPKColumn { column: col_name })
238            }
239        })?;
240
241        self.builder.add_operation(
242            &table,
243            pk,
244            Operation::Update {
245                values: new_values,
246                indirect: false,
247            },
248        );
249
250        Ok(())
251    }
252
253    /// Parse a DELETE statement.
254    fn digest_delete(&mut self) -> Result<(), ParseError<'input>> {
255        self.expect(&TokenKind::Delete)?;
256        self.expect(&TokenKind::From)?;
257
258        let table = self.expect_table()?;
259        let mut pks = vec![Value::Null; table.number_of_primary_keys()];
260
261        // WHERE clause is required
262        if self.lexer.peek()?.kind != TokenKind::Where {
263            return Err(ParseError::MissingWhere {
264                statement: "DELETE",
265            });
266        }
267        self.digest_where(&table, |col_idx, col_name, val| {
268            if let Some(primary_key_index) = table.primary_key_index(usize::from(col_idx)) {
269                pks[primary_key_index] = val.clone();
270                Ok(())
271            } else {
272                Err(ParseError::WhereNonPKColumn { column: col_name })
273            }
274        })?;
275
276        self.builder.add_operation(
277            &table,
278            pks,
279            Operation::Delete {
280                data: (),
281                indirect: false,
282            },
283        );
284
285        Ok(())
286    }
287
288    /// Parse a WHERE clause.
289    fn digest_where<D>(&mut self, table: &T, mut digestor: D) -> Result<(), ParseError<'input>>
290    where
291        D: FnMut(u16, &'input str, Value<S, Vec<u8>>) -> Result<(), ParseError<'input>>,
292    {
293        self.expect(&TokenKind::Where)?;
294
295        loop {
296            let (col_idx, col_name) = self.expect_column(table)?;
297            self.expect(&TokenKind::Equals)?;
298            let val = self.parse_value()?;
299            digestor(col_idx, col_name, val)?;
300
301            if self.lexer.peek()?.kind != TokenKind::And {
302                break;
303            }
304            self.lexer.next()?;
305        }
306
307        Ok(())
308    }
309
310    /// Parse a value literal.
311    fn parse_value(&mut self) -> Result<Value<S, Vec<u8>>, ParseError<'input>> {
312        let token = self.lexer.next()?;
313        match token.kind {
314            TokenKind::Null => Ok(Value::Null),
315            TokenKind::IntegerLiteral(v) => Ok(Value::Integer(v)),
316            TokenKind::RealLiteral(v) => Ok(Value::Real(v)),
317            TokenKind::StringLiteral(s) => {
318                let text: S = match s {
319                    Cow::Borrowed(b) => S::from(b),
320                    Cow::Owned(o) => S::from(o.as_str()),
321                };
322                Ok(Value::Text(text))
323            }
324            TokenKind::BlobLiteral(b) => Ok(Value::Blob(b)),
325            TokenKind::Minus => {
326                // Negative number
327                let next = self.lexer.next()?;
328                match next.kind {
329                    TokenKind::IntegerLiteral(v) => Ok(Value::Integer(-v)),
330                    TokenKind::RealLiteral(v) => {
331                        // Check if the negated float is exactly representable as i64.
332                        // This handles the i64::MIN case: 9223372036854775808 overflows
333                        // i64 in the lexer (positive), but -9223372036854775808 is valid.
334                        let neg = -v;
335                        #[allow(
336                            clippy::cast_precision_loss,
337                            clippy::float_cmp,
338                            clippy::cast_possible_truncation
339                        )]
340                        if neg >= i64::MIN as f64
341                            && neg <= i64::MAX as f64
342                            && neg == (neg as i64 as f64)
343                        {
344                            Ok(Value::Integer(neg as i64))
345                        } else {
346                            Ok(Value::Real(neg))
347                        }
348                    }
349                    other => Err(ParseError::UnexpectedToken {
350                        expected: "number after minus",
351                        found: other,
352                        pos: next.pos,
353                    }),
354                }
355            }
356            other => Err(ParseError::UnexpectedToken {
357                expected: "value (NULL, number, string, or blob)",
358                found: other,
359                pos: token.pos,
360            }),
361        }
362    }
363
364    /// Expect a specific token kind.
365    fn expect(
366        &mut self,
367        expected: &TokenKind<'input>,
368    ) -> Result<Token<'input>, ParseError<'input>> {
369        let token = self.lexer.next()?;
370        if core::mem::discriminant(&token.kind) == core::mem::discriminant(expected) {
371            Ok(token)
372        } else {
373            Err(ParseError::UnexpectedToken {
374                expected: expected.static_name(),
375                found: token.kind,
376                pos: token.pos,
377            })
378        }
379    }
380
381    /// Expects a column identifier and returns the corresponding column index in the table schema.
382    fn expect_column(&mut self, table: &T) -> Result<(u16, &'input str), ParseError<'input>> {
383        let column_name = self.expect_identifier()?;
384        #[allow(clippy::cast_possible_truncation)]
385        table
386            .column_index(column_name)
387            .map(|idx| (idx as u16, column_name))
388            .ok_or(ParseError::UnknownColumn(column_name))
389    }
390
391    /// Expects a table existing in the builder's schema and returns a clone.
392    fn expect_table(&mut self) -> Result<T, ParseError<'input>> {
393        let table_name = self.expect_identifier()?;
394        self.builder
395            .table(table_name)
396            .cloned()
397            .ok_or(ParseError::UnknownTable(table_name))
398    }
399
400    /// Expect an identifier and return its name.
401    fn expect_identifier(&mut self) -> Result<&'input str, ParseError<'input>> {
402        let token = self.lexer.next()?;
403        match token.kind {
404            TokenKind::Identifier(name) => Ok(name),
405            // Also accept keywords as identifiers (common in SQL)
406            TokenKind::Insert => Ok("INSERT"),
407            TokenKind::Into => Ok("INTO"),
408            TokenKind::Values => Ok("VALUES"),
409            TokenKind::Update => Ok("UPDATE"),
410            TokenKind::Set => Ok("SET"),
411            TokenKind::Delete => Ok("DELETE"),
412            TokenKind::From => Ok("FROM"),
413            TokenKind::Where => Ok("WHERE"),
414            TokenKind::And => Ok("AND"),
415            TokenKind::Primary => Ok("PRIMARY"),
416            TokenKind::Key => Ok("KEY"),
417            TokenKind::Null => Ok("NULL"),
418            TokenKind::Integer => Ok("INTEGER"),
419            TokenKind::Int => Ok("INT"),
420            TokenKind::Real => Ok("REAL"),
421            TokenKind::Text => Ok("TEXT"),
422            TokenKind::Blob => Ok("BLOB"),
423            TokenKind::Not => Ok("NOT"),
424            other => Err(ParseError::UnexpectedToken {
425                expected: "identifier",
426                found: other,
427                pos: token.pos,
428            }),
429        }
430    }
431}
432
433#[cfg(test)]
434mod tests {
435    use alloc::string::String;
436    use alloc::vec::Vec;
437
438    use crate::schema::SimpleTable;
439    use crate::{DiffSetBuilder, PatchsetFormat};
440
441    fn make_builder(
442        tables: &[SimpleTable],
443    ) -> DiffSetBuilder<PatchsetFormat, SimpleTable, String, Vec<u8>> {
444        let mut builder = DiffSetBuilder::default();
445        for t in tables {
446            builder.add_table(t);
447        }
448        builder
449    }
450
451    #[test]
452    fn test_digest_insert() {
453        let users = SimpleTable::new("users", &["id", "name"], &[0]);
454        let mut builder = make_builder(&[users]);
455        builder
456            .digest_sql("INSERT INTO users (id, name) VALUES (1, 'Alice')")
457            .unwrap();
458        assert_eq!(builder.len(), 1);
459        assert!(!builder.build().is_empty());
460    }
461
462    #[test]
463    fn test_digest_insert_positional() {
464        let users = SimpleTable::new("users", &["id", "name"], &[0]);
465        let mut builder = make_builder(&[users]);
466        builder
467            .digest_sql("INSERT INTO users VALUES (1, 'Alice')")
468            .unwrap();
469        assert_eq!(builder.len(), 1);
470    }
471
472    #[test]
473    fn test_digest_update() {
474        let users = SimpleTable::new("users", &["id", "name"], &[0]);
475        let mut builder = make_builder(&[users]);
476        builder
477            .digest_sql("UPDATE users SET name = 'Bob' WHERE id = 1")
478            .unwrap();
479        assert_eq!(builder.len(), 1);
480        assert!(!builder.build().is_empty());
481    }
482
483    #[test]
484    fn test_digest_delete() {
485        let users = SimpleTable::new("users", &["id", "name"], &[0]);
486        let mut builder = make_builder(&[users]);
487        builder
488            .digest_sql("DELETE FROM users WHERE id = 1")
489            .unwrap();
490        assert_eq!(builder.len(), 1);
491        assert!(!builder.build().is_empty());
492    }
493
494    #[test]
495    fn test_digest_delete_rejects_non_pk_in_where() {
496        let users = SimpleTable::new("users", &["id", "name", "status"], &[0]);
497        let mut builder = make_builder(&[users]);
498        let result = builder.digest_sql("DELETE FROM users WHERE id = 1 AND status = 'active'");
499        assert!(result.is_err());
500    }
501
502    #[test]
503    fn test_digest_multiple_dml() {
504        let users = SimpleTable::new("users", &["id", "name"], &[0]);
505        let mut builder = make_builder(&[users]);
506        builder
507            .digest_sql(
508                "INSERT INTO users (id, name) VALUES (1, 'Alice');\
509                 INSERT INTO users (id, name) VALUES (2, 'Bob');\
510                 DELETE FROM users WHERE id = 1;",
511            )
512            .unwrap();
513        // INSERT(1) + INSERT(2) + DELETE(1) leaves only INSERT(2)
514        assert_eq!(builder.len(), 1);
515        assert!(!builder.build().is_empty());
516    }
517
518    #[test]
519    fn test_digest_create_table_rejected() {
520        let mut builder: DiffSetBuilder<PatchsetFormat, SimpleTable, String, Vec<u8>> =
521            DiffSetBuilder::default();
522        let result = builder.digest_sql("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)");
523        assert!(result.is_err());
524    }
525
526    #[test]
527    fn test_digest_blob_value() {
528        let t = SimpleTable::new("t", &["data"], &[0]);
529        let mut builder = make_builder(&[t]);
530        builder
531            .digest_sql("INSERT INTO t (data) VALUES (X'DEADBEEF')")
532            .unwrap();
533        assert_eq!(builder.len(), 1);
534    }
535
536    #[test]
537    fn test_digest_null_value() {
538        let t = SimpleTable::new("t", &["id", "v"], &[0]);
539        let mut builder = make_builder(&[t]);
540        builder
541            .digest_sql("INSERT INTO t (id, v) VALUES (1, NULL)")
542            .unwrap();
543        assert_eq!(builder.len(), 1);
544    }
545
546    #[test]
547    fn test_digest_negative_numbers() {
548        let t = SimpleTable::new("t", &["a", "b"], &[0]);
549        let mut builder = make_builder(&[t]);
550        builder
551            .digest_sql("INSERT INTO t (a, b) VALUES (-42, -3.14)")
552            .unwrap();
553        assert_eq!(builder.len(), 1);
554    }
555
556    #[test]
557    fn test_digest_keyword_column_names() {
558        // Each reserved keyword is a column name. This forces expect_identifier
559        // to take every keyword arm. The names registered on the schema match
560        // the uppercase constants the parser returns for those arms.
561        let cols = [
562            "INSERT", "INTO", "VALUES", "UPDATE", "SET", "DELETE", "FROM", "WHERE", "AND",
563            "PRIMARY", "KEY", "NULL", "INTEGER", "INT", "REAL", "TEXT", "BLOB", "NOT",
564        ];
565        let t = SimpleTable::new("kwords", &cols, &[0]);
566        let mut builder = make_builder(&[t]);
567        builder
568            .digest_sql(
569                "INSERT INTO kwords (insert, into, values, update, set, delete, from, where, and, primary, key, null, integer, int, real, text, blob, not) \
570                 VALUES (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18)",
571            )
572            .unwrap();
573        assert_eq!(builder.len(), 1);
574    }
575
576    // ---- ParseError variant tests ----
577
578    use crate::builders::sql::ParseError;
579
580    #[test]
581    fn test_digest_insert_missing_into() {
582        let t = SimpleTable::new("t", &["id"], &[0]);
583        let mut builder = make_builder(&[t]);
584        let err = builder.digest_sql("INSERT FROM t").unwrap_err();
585        assert!(matches!(err, ParseError::UnexpectedToken { .. }));
586    }
587
588    #[test]
589    fn test_digest_insert_unknown_table() {
590        let t = SimpleTable::new("t", &["id"], &[0]);
591        let mut builder = make_builder(&[t]);
592        let err = builder
593            .digest_sql("INSERT INTO unknown_table VALUES (1)")
594            .unwrap_err();
595        assert!(matches!(err, ParseError::UnknownTable("unknown_table")));
596    }
597
598    #[test]
599    fn test_digest_update_missing_where() {
600        let t = SimpleTable::new("t", &["id", "v"], &[0]);
601        let mut builder = make_builder(&[t]);
602        let err = builder.digest_sql("UPDATE t SET v = 1").unwrap_err();
603        assert!(matches!(
604            err,
605            ParseError::MissingWhere {
606                statement: "UPDATE"
607            }
608        ));
609    }
610
611    #[test]
612    fn test_digest_delete_missing_where() {
613        let t = SimpleTable::new("t", &["id"], &[0]);
614        let mut builder = make_builder(&[t]);
615        let err = builder.digest_sql("DELETE FROM t").unwrap_err();
616        assert!(matches!(
617            err,
618            ParseError::MissingWhere {
619                statement: "DELETE"
620            }
621        ));
622    }
623
624    #[test]
625    fn test_digest_update_where_non_pk_column() {
626        let t = SimpleTable::new("t", &["id", "v"], &[0]);
627        let mut builder = make_builder(&[t]);
628        let err = builder
629            .digest_sql("UPDATE t SET v = 2 WHERE v = 1")
630            .unwrap_err();
631        assert!(matches!(err, ParseError::WhereNonPKColumn { column: "v" }));
632    }
633
634    #[test]
635    fn test_digest_unexpected_top_level_token() {
636        let t = SimpleTable::new("t", &["id"], &[0]);
637        let mut builder = make_builder(&[t]);
638        let err = builder.digest_sql("SELECT 1").unwrap_err();
639        assert!(matches!(err, ParseError::UnexpectedToken { .. }));
640    }
641
642    #[test]
643    fn test_digest_expect_identifier_rejects_value_token() {
644        let t = SimpleTable::new("t", &["id"], &[0]);
645        let mut builder = make_builder(&[t]);
646        // Integer literal where a table name is expected.
647        let err = builder.digest_sql("INSERT INTO 42 VALUES (1)").unwrap_err();
648        assert!(matches!(err, ParseError::UnexpectedToken { .. }));
649    }
650}