1use crate::error::{Result, SqliteError};
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum Generated {
35 No,
36 Virtual,
37 Stored,
38}
39
40impl Generated {
41 pub fn is_generated(self) -> bool {
42 !matches!(self, Generated::No)
43 }
44
45 pub fn is_virtual(self) -> bool {
47 matches!(self, Generated::Virtual)
48 }
49}
50
51#[derive(Debug, Clone, PartialEq, Eq)]
53pub struct ColumnDef {
54 pub name: String,
55 pub declared_type: Option<String>,
59 pub not_null: bool,
60 pub default: Option<String>,
64 pub generated: Generated,
65}
66
67#[derive(Debug, Clone, PartialEq, Eq)]
69pub struct KeyColumn {
70 pub name: String,
71 pub descending: bool,
72}
73
74#[derive(Debug, Clone, PartialEq, Eq)]
83pub enum Constraint {
84 Unique { columns: Vec<String> },
86 Check { expression: String },
88 ForeignKey {
90 columns: Vec<String>,
91 references: String,
92 },
93 Collation { column: String, name: String },
95}
96
97#[derive(Debug, Clone, PartialEq, Eq)]
98pub struct TableDef {
99 pub name: String,
100 pub columns: Vec<ColumnDef>,
101 pub primary_key: Vec<KeyColumn>,
103 pub without_rowid: bool,
104 pub strict: bool,
105 pub unsupported_constraints: Vec<Constraint>,
108 inline_pk_desc: bool,
111}
112
113impl TableDef {
114 pub fn column(&self, name: &str) -> Option<&ColumnDef> {
115 self.columns
116 .iter()
117 .find(|c| c.name.eq_ignore_ascii_case(name))
118 }
119
120 pub fn column_index(&self, name: &str) -> Option<usize> {
121 self.columns
122 .iter()
123 .position(|c| c.name.eq_ignore_ascii_case(name))
124 }
125
126 pub fn rowid_alias(&self) -> Option<&ColumnDef> {
150 if self.without_rowid || self.primary_key.len() != 1 || self.inline_pk_desc {
151 return None;
152 }
153 let key = &self.primary_key[0];
154 let column = self.column(&key.name)?;
155 match &column.declared_type {
156 Some(declared) if declared.trim().eq_ignore_ascii_case("integer") => Some(column),
159 _ => None,
160 }
161 }
162}
163
164#[derive(Debug, Clone, PartialEq, Eq)]
169enum Token {
170 Word(String),
174 Quoted(String),
176 Literal(String),
178 Punct(char),
179}
180
181#[derive(Debug, Clone)]
185struct Tok {
186 token: Token,
187 start: usize,
188 end: usize,
189}
190
191impl Token {
192 fn word(&self) -> Option<&str> {
194 match self {
195 Token::Word(w) => Some(w),
196 _ => None,
197 }
198 }
199
200 fn is_word(&self, keyword: &str) -> bool {
201 matches!(self, Token::Word(w) if w.eq_ignore_ascii_case(keyword))
202 }
203
204 fn is_punct(&self, c: char) -> bool {
205 matches!(self, Token::Punct(p) if *p == c)
206 }
207
208 fn identifier(&self) -> Option<String> {
210 match self {
211 Token::Word(w) => Some(w.clone()),
212 Token::Quoted(q) => Some(q.clone()),
213 _ => None,
214 }
215 }
216
217 fn text(&self) -> String {
220 match self {
221 Token::Word(w) => w.clone(),
222 Token::Quoted(q) => format!("\"{}\"", q.replace('"', "\"\"")),
223 Token::Literal(l) => l.clone(),
224 Token::Punct(p) => p.to_string(),
225 }
226 }
227}
228
229fn tokenize(sql: &str) -> Result<Vec<Tok>> {
230 let chars: Vec<char> = sql.chars().collect();
231 let mut offsets: Vec<usize> = sql.char_indices().map(|(i, _)| i).collect();
234 offsets.push(sql.len());
235
236 let mut tokens: Vec<Tok> = Vec::new();
237 let mut at = 0usize;
238
239 while at < chars.len() {
240 let c = chars[at];
241
242 if c.is_whitespace() {
243 at += 1;
244 continue;
245 }
246 let began = at;
247
248 if c == '-' && chars.get(at + 1) == Some(&'-') {
250 while at < chars.len() && chars[at] != '\n' {
251 at += 1;
252 }
253 continue;
254 }
255
256 if c == '/' && chars.get(at + 1) == Some(&'*') {
258 at += 2;
259 while at < chars.len() && !(chars[at] == '*' && chars.get(at + 1) == Some(&'/')) {
260 at += 1;
261 }
262 at = (at + 2).min(chars.len());
263 continue;
264 }
265
266 if let Some((open, close)) = match c {
268 '"' => Some(('"', '"')),
269 '`' => Some(('`', '`')),
270 '[' => Some(('[', ']')),
271 _ => None,
272 } {
273 at += 1;
274 let mut value = String::new();
275 loop {
276 let Some(&ch) = chars.get(at) else {
277 return Err(SqliteError::unsupported(format!(
278 "a create statement has an unterminated {open} quoted name"
279 )));
280 };
281 at += 1;
282 if ch == close {
283 if close != ']' && chars.get(at) == Some(&close) {
285 value.push(close);
286 at += 1;
287 continue;
288 }
289 break;
290 }
291 value.push(ch);
292 }
293 tokens.push(Tok {
294 token: Token::Quoted(value),
295 start: offsets[began],
296 end: offsets[at],
297 });
298 continue;
299 }
300
301 if c == '\'' {
302 let mut text = String::from("'");
303 at += 1;
304 loop {
305 let Some(&ch) = chars.get(at) else {
306 return Err(SqliteError::unsupported(
307 "a create statement has an unterminated string",
308 ));
309 };
310 at += 1;
311 text.push(ch);
312 if ch == '\'' {
313 if chars.get(at) == Some(&'\'') {
314 text.push('\'');
315 at += 1;
316 continue;
317 }
318 break;
319 }
320 }
321 tokens.push(Tok {
322 token: Token::Literal(text),
323 start: offsets[began],
324 end: offsets[at],
325 });
326 continue;
327 }
328
329 if c.is_ascii_digit() || (c == '.' && chars.get(at + 1).is_some_and(|d| d.is_ascii_digit()))
330 {
331 let start = at;
332 while at < chars.len()
333 && (chars[at].is_ascii_alphanumeric() || chars[at] == '.' || {
334 (chars[at] == '+' || chars[at] == '-')
336 && matches!(chars[at - 1], 'e' | 'E')
337 && chars[start..at].iter().all(|c| *c != 'x' && *c != 'X')
338 })
339 {
340 at += 1;
341 }
342 tokens.push(Tok {
343 token: Token::Literal(chars[start..at].iter().collect()),
344 start: offsets[began],
345 end: offsets[at],
346 });
347 continue;
348 }
349
350 if c.is_alphabetic() || c == '_' || !c.is_ascii() {
351 let start = at;
352 while at < chars.len()
353 && (chars[at].is_alphanumeric()
354 || chars[at] == '_'
355 || chars[at] == '$'
356 || !chars[at].is_ascii())
357 {
358 at += 1;
359 }
360 let word: String = chars[start..at].iter().collect();
361 if (word.eq_ignore_ascii_case("x")) && chars.get(at) == Some(&'\'') {
363 let mut text = word;
364 text.push('\'');
365 at += 1;
366 while let Some(&ch) = chars.get(at) {
367 at += 1;
368 text.push(ch);
369 if ch == '\'' {
370 break;
371 }
372 }
373 tokens.push(Tok {
374 token: Token::Literal(text),
375 start: offsets[began],
376 end: offsets[at],
377 });
378 } else {
379 tokens.push(Tok {
380 token: Token::Word(word),
381 start: offsets[began],
382 end: offsets[at],
383 });
384 }
385 continue;
386 }
387
388 tokens.push(Tok {
389 token: Token::Punct(c),
390 start: offsets[began],
391 end: offsets[at + 1],
392 });
393 at += 1;
394 }
395
396 Ok(tokens)
397}
398
399const CONSTRAINT_WORDS: &[&str] = &[
405 "constraint",
406 "primary",
407 "not",
408 "null",
409 "unique",
410 "check",
411 "default",
412 "collate",
413 "references",
414 "generated",
415 "as",
416];
417
418struct Parser<'a> {
419 sql: &'a str,
420 tokens: Vec<Tok>,
421 at: usize,
422}
423
424impl<'a> Parser<'a> {
425 fn peek(&self) -> Option<&Token> {
426 self.tokens.get(self.at).map(|t| &t.token)
427 }
428
429 fn at_word(&self, offset: usize, keyword: &str) -> bool {
430 self.tokens
431 .get(self.at + offset)
432 .is_some_and(|t| t.token.is_word(keyword))
433 }
434
435 fn start_of(&self, index: usize) -> usize {
437 match self.tokens.get(index) {
438 Some(t) => t.start,
439 None => self.sql.len(),
440 }
441 }
442
443 fn end_before(&self, index: usize) -> usize {
445 match index.checked_sub(1).and_then(|i| self.tokens.get(i)) {
446 Some(t) => t.end,
447 None => 0,
448 }
449 }
450
451 fn text_since(&self, from: usize) -> String {
454 let start = self.start_of(from);
455 let end = self.end_before(self.at);
456 if end <= start {
457 String::new()
458 } else {
459 self.sql[start..end].trim().to_string()
460 }
461 }
462
463 fn next(&mut self) -> Option<Token> {
464 let token = self.tokens.get(self.at).map(|t| t.token.clone());
465 if token.is_some() {
466 self.at += 1;
467 }
468 token
469 }
470
471 fn eat_word(&mut self, keyword: &str) -> bool {
472 if self.peek().is_some_and(|t| t.is_word(keyword)) {
473 self.at += 1;
474 true
475 } else {
476 false
477 }
478 }
479
480 fn eat_punct(&mut self, c: char) -> bool {
481 if self.peek().is_some_and(|t| t.is_punct(c)) {
482 self.at += 1;
483 true
484 } else {
485 false
486 }
487 }
488
489 fn expect_word(&mut self, keyword: &str) -> Result<()> {
490 if self.eat_word(keyword) {
491 Ok(())
492 } else {
493 Err(self.gap(&format!("expected {keyword}")))
494 }
495 }
496
497 fn expect_punct(&mut self, c: char) -> Result<()> {
498 if self.eat_punct(c) {
499 Ok(())
500 } else {
501 Err(self.gap(&format!("expected {c}")))
502 }
503 }
504
505 fn identifier(&mut self) -> Result<String> {
506 match self.next().and_then(|t| t.identifier()) {
507 Some(name) => Ok(name),
508 None => Err(self.gap("expected a name")),
509 }
510 }
511
512 fn gap(&self, what: &str) -> SqliteError {
516 let found = match self.tokens.get(self.at) {
517 Some(t) => format!("{:?}", t.token.text()),
518 None => "the end of the statement".to_string(),
519 };
520 SqliteError::unsupported(format!(
521 "cannot read this create statement: {what}, found {found} at token {}",
522 self.at
523 ))
524 }
525
526 fn at_constraint_word(&self) -> bool {
527 self.peek()
528 .and_then(|t| t.word())
529 .is_some_and(|w| CONSTRAINT_WORDS.iter().any(|k| w.eq_ignore_ascii_case(k)))
530 }
531
532 fn balanced(&mut self) -> Result<String> {
535 let from = self.at;
536 self.expect_punct('(')?;
537 let mut depth = 1usize;
538 while depth > 0 {
539 let Some(token) = self.next() else {
540 return Err(self.gap("expected a closing paren"));
541 };
542 if token.is_punct('(') {
543 depth += 1;
544 } else if token.is_punct(')') {
545 depth -= 1;
546 }
547 }
548 Ok(self.text_since(from))
549 }
550
551 fn column_list(&mut self) -> Result<Vec<String>> {
554 self.expect_punct('(')?;
555 let mut names = Vec::new();
556 loop {
557 names.push(self.identifier()?);
558 if self.eat_word("collate") {
559 self.identifier()?;
560 }
561 let _ = self.eat_word("asc") || self.eat_word("desc");
562 if self.eat_punct(',') {
563 continue;
564 }
565 self.expect_punct(')')?;
566 return Ok(names);
567 }
568 }
569
570 fn skip_to_end_of_item(&mut self) -> Result<()> {
573 loop {
574 match self.peek() {
575 None => return Ok(()),
576 Some(t) if t.is_punct(',') || t.is_punct(')') => return Ok(()),
577 Some(t) if t.is_punct('(') => {
578 self.balanced()?;
579 }
580 _ => {
581 self.at += 1;
582 }
583 }
584 }
585 }
586
587 fn eat_generated_kind(&mut self) -> Generated {
590 if self.eat_word("stored") {
591 Generated::Stored
592 } else {
593 self.eat_word("virtual");
594 Generated::Virtual
595 }
596 }
597
598 fn eat_conflict_clause(&mut self) {
600 if self.at_word(0, "on") && self.at_word(1, "conflict") {
601 self.at += 2;
602 if self.peek().and_then(|t| t.word()).is_some() {
603 self.at += 1;
604 }
605 }
606 }
607}
608
609pub fn parse_create_table(sql: &str) -> Result<TableDef> {
611 let tokens = tokenize(sql)?;
612 let mut parser = Parser { sql, tokens, at: 0 };
613
614 parser.expect_word("create")?;
615 let _ = parser.eat_word("temp") || parser.eat_word("temporary");
618 parser.expect_word("table")?;
619 if parser.eat_word("if") {
620 parser.expect_word("not")?;
621 parser.expect_word("exists")?;
622 }
623
624 let mut name = parser.identifier()?;
625 if parser.eat_punct('.') {
626 name = parser.identifier()?;
628 }
629
630 if parser.peek().is_some_and(|t| t.is_word("as")) {
631 return Err(SqliteError::unsupported(format!(
632 "{name} was created by `create table ... as select`, so the file does not record \
633 its column types; this reader cannot import it"
634 )));
635 }
636
637 parser.expect_punct('(')?;
638
639 let mut columns: Vec<ColumnDef> = Vec::new();
640 let mut primary_key: Vec<KeyColumn> = Vec::new();
641 let mut constraints: Vec<Constraint> = Vec::new();
642 let mut inline_pk_desc = false;
643
644 loop {
645 if parser.peek().is_none() {
646 return Err(parser.gap("the column list is not closed"));
647 }
648 if parser.eat_punct(')') {
649 break;
650 }
651
652 let is_table_constraint = {
654 let offset = if parser.at_word(0, "constraint") {
657 2
658 } else {
659 0
660 };
661 parser.at_word(offset, "primary")
662 || parser.at_word(offset, "unique")
663 || parser.at_word(offset, "check")
664 || parser.at_word(offset, "foreign")
665 };
666
667 if is_table_constraint {
668 if parser.eat_word("constraint") {
669 parser.identifier()?;
670 }
671 if parser.eat_word("primary") {
672 parser.expect_word("key")?;
673 if !primary_key.is_empty() {
674 return Err(SqliteError::unsupported(format!(
675 "{name} declares more than one primary key"
676 )));
677 }
678 parser.expect_punct('(')?;
679 loop {
680 let column = parser.identifier()?;
681 if parser.eat_word("collate") {
682 parser.identifier()?;
683 }
684 let descending = if parser.eat_word("desc") {
685 true
686 } else {
687 parser.eat_word("asc");
688 false
689 };
690 primary_key.push(KeyColumn {
691 name: column,
692 descending,
693 });
694 if parser.eat_punct(',') {
695 continue;
696 }
697 parser.expect_punct(')')?;
698 break;
699 }
700 parser.eat_conflict_clause();
701 } else if parser.eat_word("unique") {
702 let columns = parser.column_list()?;
703 constraints.push(Constraint::Unique { columns });
704 parser.eat_conflict_clause();
705 parser.skip_to_end_of_item()?;
706 } else if parser.eat_word("check") {
707 let expression = parser.balanced()?;
708 constraints.push(Constraint::Check { expression });
709 parser.skip_to_end_of_item()?;
710 } else if parser.eat_word("foreign") {
711 parser.expect_word("key")?;
712 let columns = parser.column_list()?;
713 let references = if parser.eat_word("references") {
714 parser.identifier()?
715 } else {
716 String::new()
717 };
718 constraints.push(Constraint::ForeignKey {
719 columns,
720 references,
721 });
722 parser.skip_to_end_of_item()?;
723 } else {
724 parser.skip_to_end_of_item()?;
725 }
726 } else {
727 let column_name = parser.identifier()?;
728 let type_from = parser.at;
729 while !parser.at_constraint_word() {
730 match parser.peek() {
731 None => break,
732 Some(t) if t.is_punct(',') || t.is_punct(')') => break,
733 Some(t) if t.is_punct('(') => {
734 parser.balanced()?;
735 }
736 Some(_) => {
737 parser.at += 1;
738 }
739 }
740 }
741 let declared_type = parser.text_since(type_from);
742
743 let mut column = ColumnDef {
744 name: column_name,
745 declared_type: if declared_type.is_empty() {
746 None
747 } else {
748 Some(declared_type)
749 },
750 not_null: false,
751 default: None,
752 generated: Generated::No,
753 };
754
755 loop {
757 if parser.eat_word("constraint") {
758 parser.identifier()?;
759 continue;
760 }
761 if parser.eat_word("primary") {
762 parser.expect_word("key")?;
763 if !primary_key.is_empty() {
764 return Err(SqliteError::unsupported(format!(
765 "{name} declares more than one primary key"
766 )));
767 }
768 let descending = if parser.eat_word("desc") {
769 true
770 } else {
771 parser.eat_word("asc");
772 false
773 };
774 inline_pk_desc = descending;
775 primary_key.push(KeyColumn {
776 name: column.name.clone(),
777 descending,
778 });
779 parser.eat_conflict_clause();
780 parser.eat_word("autoincrement");
781 continue;
782 }
783 if parser.eat_word("not") {
784 parser.expect_word("null")?;
785 column.not_null = true;
786 parser.eat_conflict_clause();
787 continue;
788 }
789 if parser.eat_word("null") {
790 parser.eat_conflict_clause();
791 continue;
792 }
793 if parser.eat_word("unique") {
794 constraints.push(Constraint::Unique {
795 columns: vec![column.name.clone()],
796 });
797 parser.eat_conflict_clause();
798 continue;
799 }
800 if parser.eat_word("check") {
801 let expression = parser.balanced()?;
802 constraints.push(Constraint::Check { expression });
803 continue;
804 }
805 if parser.eat_word("default") {
806 let from = parser.at;
807 if parser.peek().is_some_and(|t| t.is_punct('(')) {
808 parser.balanced()?;
809 } else {
810 let _ = parser.eat_punct('-') || parser.eat_punct('+');
813 if parser.next().is_none() {
814 return Err(parser.gap("expected a default value"));
815 }
816 }
817 column.default = Some(parser.text_since(from));
818 continue;
819 }
820 if parser.eat_word("collate") {
821 let name = parser.identifier()?;
822 if !name.eq_ignore_ascii_case("binary") {
824 constraints.push(Constraint::Collation {
825 column: column.name.clone(),
826 name,
827 });
828 }
829 continue;
830 }
831 if parser.eat_word("references") {
832 let references = parser.identifier().unwrap_or_default();
833 constraints.push(Constraint::ForeignKey {
834 columns: vec![column.name.clone()],
835 references,
836 });
837 parser.skip_to_end_of_item()?;
838 continue;
839 }
840 if parser.eat_word("generated") {
844 parser.expect_word("always")?;
845 parser.expect_word("as")?;
846 parser.balanced()?;
847 column.generated = parser.eat_generated_kind();
848 continue;
849 }
850 if parser.peek().is_some_and(|t| t.is_word("as")) {
851 parser.at += 1;
852 parser.balanced()?;
853 column.generated = parser.eat_generated_kind();
854 continue;
855 }
856 break;
857 }
858
859 columns.push(column);
860 }
861
862 if parser.eat_punct(',') {
863 continue;
864 }
865 parser.expect_punct(')')?;
866 break;
867 }
868
869 let mut without_rowid = false;
871 let mut strict = false;
872 loop {
873 if parser.eat_word("without") {
874 parser.expect_word("rowid")?;
875 without_rowid = true;
876 } else if parser.eat_word("strict") {
877 strict = true;
878 } else {
879 break;
880 }
881 if !parser.eat_punct(',') {
882 break;
883 }
884 }
885 let _ = parser.eat_punct(';');
886
887 if columns.is_empty() {
888 return Err(SqliteError::unsupported(format!(
889 "{name} was parsed as having no columns"
890 )));
891 }
892 for key in &primary_key {
893 if !columns
894 .iter()
895 .any(|c| c.name.eq_ignore_ascii_case(&key.name))
896 {
897 return Err(SqliteError::unsupported(format!(
898 "{name} has a primary key over {}, which is not one of its columns",
899 key.name
900 )));
901 }
902 }
903
904 Ok(TableDef {
905 name,
906 columns,
907 primary_key,
908 without_rowid,
909 strict,
910 unsupported_constraints: constraints,
911 inline_pk_desc,
912 })
913}