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