1use std::collections::HashMap;
62use std::fmt;
63
64use super::model::{Constraint, DataType, Field, ForeignKey, Literal, Schema};
65
66pub const MAX_INPUT: usize = 16 * 1024 * 1024;
68
69pub const MAX_STATEMENTS: usize = 100_000;
71
72pub const MAX_COLUMNS: usize = 4_096;
75
76pub const MAX_NESTING: usize = 64;
78
79pub const MAX_TOKENS: usize = 1_000_000;
82
83pub const MAX_NOTES: usize = 1_000;
85
86#[derive(Clone, Debug, PartialEq, Eq)]
88pub struct SqlError {
89 line: usize,
90 message: String,
91}
92
93impl SqlError {
94 fn new(line: usize, message: impl Into<String>) -> Self {
95 SqlError {
96 line,
97 message: message.into(),
98 }
99 }
100
101 pub fn line(&self) -> usize {
103 self.line
104 }
105
106 pub fn message(&self) -> &str {
108 &self.message
109 }
110}
111
112impl fmt::Display for SqlError {
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 write!(f, "line {}: {}", self.line, self.message)
115 }
116}
117
118impl std::error::Error for SqlError {}
119
120#[derive(Clone, Debug, PartialEq, Eq)]
122pub struct Note {
123 pub line: usize,
125 pub message: String,
127}
128
129impl fmt::Display for Note {
130 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131 write!(f, "line {}: {}", self.line, self.message)
132 }
133}
134
135#[derive(Clone, Debug, PartialEq, Eq)]
137#[non_exhaustive]
138pub struct Parsed {
139 pub schema: Schema,
141 pub notes: Vec<Note>,
143}
144
145pub fn parse(text: &str) -> Result<Parsed, SqlError> {
147 if text.len() > MAX_INPUT {
148 return Err(SqlError::new(
149 1,
150 format!(
151 "the DDL is {} bytes, past the limit of {MAX_INPUT}",
152 text.len()
153 ),
154 ));
155 }
156 check(text)?;
157 let mut reader = Reader {
158 schema: Schema::default(),
159 index: HashMap::new(),
160 notes: Vec::new(),
161 dropped: 0,
162 };
163 let mut statements = Statements::new(text);
164 let mut count = 0;
165 while let Some(statement) = statements.next()? {
166 count += 1;
167 if count > MAX_STATEMENTS {
168 return Err(SqlError::new(
169 statement[0].line,
170 format!("more than {MAX_STATEMENTS} statements"),
171 ));
172 }
173 reader.statement(&statement)?;
174 }
175 if reader.dropped > 0 {
176 let line = statements.last_line;
177 reader.notes.push(Note {
178 line,
179 message: format!("… and {} more notes", reader.dropped),
180 });
181 }
182 Ok(Parsed {
183 schema: reader.schema,
184 notes: reader.notes,
185 })
186}
187
188#[derive(Clone, Copy, Debug, PartialEq, Eq)]
191enum Kind {
192 Word,
194 Quoted,
196 Str,
198 Number,
200 Symbol,
202}
203
204#[derive(Clone, Debug)]
205struct Token {
206 kind: Kind,
207 text: String,
209 value: String,
211 line: usize,
212}
213
214impl Token {
215 fn is(&self, word: &str) -> bool {
217 self.kind == Kind::Word && self.text.eq_ignore_ascii_case(word)
218 }
219
220 fn is_symbol(&self, symbol: &str) -> bool {
221 self.kind == Kind::Symbol && self.text == symbol
222 }
223
224 fn is_name(&self) -> bool {
225 matches!(self.kind, Kind::Word | Kind::Quoted)
226 }
227}
228
229struct Lexer {
231 chars: Vec<char>,
232 i: usize,
233 line: usize,
234}
235
236impl Lexer {
237 fn new(text: &str) -> Self {
238 Lexer {
239 chars: text.chars().collect(),
240 i: 0,
241 line: 1,
242 }
243 }
244
245 fn next(&mut self) -> Result<Option<Token>, SqlError> {
247 let chars = &self.chars;
248 let (mut i, mut line) = (self.i, self.line);
249 let take = |from: usize, to: usize| -> String { chars[from..to].iter().collect() };
250 let token = loop {
251 let Some(&c) = chars.get(i) else {
252 break None;
253 };
254 let next = chars.get(i + 1).copied();
255 if c == '\n' {
256 line += 1;
257 i += 1;
258 } else if c.is_whitespace() {
259 i += 1;
260 } else if c == '-' && next == Some('-') {
261 while i < chars.len() && chars[i] != '\n' {
262 i += 1;
263 }
264 } else if c == '/' && next == Some('*') {
265 let start = line;
266 i += 2;
267 loop {
268 match chars.get(i) {
269 None => return Err(SqlError::new(start, "a /* comment is not closed")),
270 Some('*') if chars.get(i + 1) == Some(&'/') => {
271 i += 2;
272 break;
273 }
274 Some('\n') => line += 1,
275 _ => {}
276 }
277 i += 1;
278 }
279 } else if c == '[' && next.is_some_and(|n| n == ']' || n.is_ascii_digit()) {
280 let len = if next == Some(']') { 2 } else { 1 };
282 let symbol = take(i, i + len);
283 i += len;
284 break Some(Token {
285 kind: Kind::Symbol,
286 value: symbol.clone(),
287 text: symbol,
288 line,
289 });
290 } else if matches!(c, '\'' | '"' | '`' | '[') {
291 let close = if c == '[' { ']' } else { c };
292 let start = (i, line);
293 let mut value = String::new();
294 i += 1;
295 loop {
296 match chars.get(i) {
297 None => {
298 let what = if c == '\'' { "string" } else { "quoted name" };
299 return Err(SqlError::new(start.1, format!("a {what} is not closed")));
300 }
301 Some(&ch) if ch == close => {
302 if close != ']' && chars.get(i + 1) == Some(&close) {
304 value.push(ch);
305 i += 2;
306 continue;
307 }
308 i += 1;
309 break;
310 }
311 Some(&ch) => {
312 if ch == '\n' {
313 line += 1;
314 }
315 value.push(ch);
316 i += 1;
317 }
318 }
319 }
320 let kind = if c == '\'' { Kind::Str } else { Kind::Quoted };
321 break Some(Token {
322 kind,
323 text: take(start.0, i),
324 value,
325 line: start.1,
326 });
327 } else if let Some(len) = dollar_tag(chars, i) {
328 let start = (i, line);
332 let tag = &chars[i..i + len];
333 i += len;
334 loop {
335 if i >= chars.len() {
336 return Err(SqlError::new(start.1, "a string is not closed"));
337 }
338 if chars[i..].starts_with(tag) {
339 i += len;
340 break;
341 }
342 if chars[i] == '\n' {
343 line += 1;
344 }
345 i += 1;
346 }
347 break Some(Token {
348 kind: Kind::Str,
349 text: take(start.0, i),
350 value: take(start.0 + len, i - len),
351 line: start.1,
352 });
353 } else if c.is_alphabetic() || c == '_' {
354 let start = i;
355 while i < chars.len()
356 && (chars[i].is_alphanumeric() || matches!(chars[i], '_' | '$'))
357 {
358 i += 1;
359 }
360 let word = take(start, i);
361 break Some(Token {
362 kind: Kind::Word,
363 value: word.clone(),
364 text: word,
365 line,
366 });
367 } else if c.is_ascii_digit() || (c == '.' && next.is_some_and(|n| n.is_ascii_digit())) {
368 let start = i;
369 while i < chars.len() {
370 let ch = chars[i];
371 let exponent_sign =
372 matches!(ch, '+' | '-') && i > start && matches!(chars[i - 1], 'e' | 'E');
373 if ch.is_ascii_digit() || matches!(ch, '.' | 'e' | 'E') || exponent_sign {
374 i += 1;
375 } else {
376 break;
377 }
378 }
379 let number = take(start, i);
380 break Some(Token {
381 kind: Kind::Number,
382 value: number.clone(),
383 text: number,
384 line,
385 });
386 } else {
387 let len = if c == ':' && next == Some(':') { 2 } else { 1 };
389 let symbol = take(i, i + len);
390 i += len;
391 break Some(Token {
392 kind: Kind::Symbol,
393 value: symbol.clone(),
394 text: symbol,
395 line,
396 });
397 }
398 };
399 self.i = i;
400 self.line = line;
401 Ok(token)
402 }
403}
404
405fn dollar_tag(chars: &[char], i: usize) -> Option<usize> {
408 if chars.get(i) != Some(&'$') {
409 return None;
410 }
411 let mut j = i + 1;
412 if chars.get(j).is_some_and(|c| c.is_alphabetic() || *c == '_') {
413 while chars
414 .get(j)
415 .is_some_and(|c| c.is_alphanumeric() || *c == '_')
416 {
417 j += 1;
418 }
419 }
420 (chars.get(j) == Some(&'$')).then_some(j + 1 - i)
421}
422
423fn check(text: &str) -> Result<(), SqlError> {
427 let mut lexer = Lexer::new(text);
428 let mut open: Vec<usize> = Vec::new();
429 let mut unmatched: Option<SqlError> = None;
430 while let Some(token) = lexer.next()? {
431 if unmatched.is_some() {
432 continue;
433 }
434 if token.is_symbol("(") {
435 open.push(token.line);
436 if open.len() > MAX_NESTING {
437 unmatched = Some(SqlError::new(
438 token.line,
439 format!("parentheses nest more than {MAX_NESTING} deep"),
440 ));
441 }
442 } else if token.is_symbol(")") && open.pop().is_none() {
443 unmatched = Some(SqlError::new(token.line, "a `)` closes nothing"));
444 }
445 }
446 if let Some(error) = unmatched {
447 return Err(error);
448 }
449 match open.first() {
450 Some(line) => Err(SqlError::new(*line, "a `(` is not closed")),
451 None => Ok(()),
452 }
453}
454
455#[derive(Clone, Copy, PartialEq, Eq)]
458enum Start {
459 Empty,
460 Create,
461 Or,
462 Modifiers,
463 Table,
464 Other,
465}
466
467const TABLE_MODIFIERS: &[&str] = &[
469 "GLOBAL",
470 "LOCAL",
471 "TEMP",
472 "TEMPORARY",
473 "UNLOGGED",
474 "VIRTUAL",
475];
476
477impl Start {
478 fn then(self, token: &Token) -> Start {
479 let modifier = TABLE_MODIFIERS.iter().any(|w| token.is(w));
480 match self {
481 Start::Empty if token.is("CREATE") => Start::Create,
482 Start::Create if token.is("OR") => Start::Or,
483 Start::Or if token.is("REPLACE") => Start::Modifiers,
484 Start::Create | Start::Modifiers if modifier => Start::Modifiers,
485 Start::Create | Start::Modifiers if token.is("TABLE") => Start::Table,
486 Start::Table => Start::Table,
487 _ => Start::Other,
488 }
489 }
490}
491
492const OTHER_TOKENS: usize = 8;
495
496struct Statements {
503 lexer: Lexer,
504 last_line: usize,
506 after_close: bool,
508 pending: Option<Token>,
510}
511
512impl Statements {
513 fn new(text: &str) -> Self {
514 Statements {
515 lexer: Lexer::new(text),
516 last_line: 0,
517 after_close: false,
518 pending: None,
519 }
520 }
521
522 fn next(&mut self) -> Result<Option<Vec<Token>>, SqlError> {
523 let mut tokens: Vec<Token> = Vec::new();
524 let mut depth = 0usize;
525 let mut start = Start::Empty;
526 loop {
527 let token = match self.pending.take() {
528 Some(token) => token,
529 None => match self.lexer.next()? {
530 Some(token) => token,
531 None => break,
532 },
533 };
534 let starts_line = token.line > self.last_line;
535 let after_close = std::mem::replace(&mut self.after_close, token.is_symbol(")"));
536 self.last_line = token.line;
537 if token.is_symbol("(") {
538 depth += 1;
539 } else if token.is_symbol(")") {
540 depth = depth.saturating_sub(1);
541 } else if depth == 0 && (token.is_symbol(";") || (token.is("GO") && starts_line)) {
542 if tokens.is_empty() {
543 continue;
544 }
545 return Ok(Some(tokens));
546 } else if depth == 0
547 && token.is("CREATE")
548 && (starts_line || after_close)
549 && !tokens.is_empty()
550 {
551 self.pending = Some(token);
552 return Ok(Some(tokens));
553 }
554 if start != Start::Other {
555 start = start.then(&token);
556 }
557 if start == Start::Other && tokens.len() >= OTHER_TOKENS {
558 continue;
559 }
560 if tokens.len() == MAX_TOKENS {
561 return Err(SqlError::new(
562 tokens[0].line,
563 format!("a statement of more than {MAX_TOKENS} tokens"),
564 ));
565 }
566 tokens.push(token);
567 }
568 Ok((!tokens.is_empty()).then_some(tokens))
569 }
570}
571
572fn split_commas(tokens: &[Token]) -> Vec<&[Token]> {
574 let mut out = Vec::new();
575 let mut depth = 0usize;
576 let mut start = 0;
577 for (index, token) in tokens.iter().enumerate() {
578 if token.is_symbol("(") {
579 depth += 1;
580 } else if token.is_symbol(")") {
581 depth = depth.saturating_sub(1);
582 } else if token.is_symbol(",") && depth == 0 {
583 out.push(&tokens[start..index]);
584 start = index + 1;
585 }
586 }
587 out.push(&tokens[start..]);
588 out
589}
590
591fn closing(tokens: &[Token], open: usize) -> usize {
593 let mut depth = 0usize;
594 for (index, token) in tokens.iter().enumerate().skip(open) {
595 if token.is_symbol("(") {
596 depth += 1;
597 } else if token.is_symbol(")") {
598 depth = depth.saturating_sub(1);
599 if depth == 0 {
600 return index + 1;
601 }
602 }
603 }
604 tokens.len()
605}
606
607fn write(tokens: &[Token]) -> String {
609 let mut out = String::new();
610 for (index, token) in tokens.iter().enumerate() {
611 if index > 0 && space_before(tokens, index) {
612 out.push(' ');
613 }
614 out.push_str(&token.text);
615 }
616 out
617}
618
619fn space_before(tokens: &[Token], index: usize) -> bool {
621 let (prev, token) = (&tokens[index - 1], &tokens[index]);
622 let symbol = |t: &Token, any: &[&str]| t.kind == Kind::Symbol && any.contains(&t.text.as_str());
623 if symbol(token, &[")", ",", "[", "]", "[]", "::", "."]) || symbol(prev, &["(", "[", "::", "."])
624 {
625 return false;
626 }
627 if token.is_symbol("(") && prev.is_name() {
629 return false;
630 }
631 let starts = index == 1 || symbol(&tokens[index - 2], &["(", ","]);
633 !(symbol(prev, &["-", "+"]) && token.kind == Kind::Number && starts)
634}
635
636fn name_list(tokens: &[Token], open: usize) -> (Vec<String>, usize) {
639 let end = closing(tokens, open);
640 let inner = &tokens[(open + 1).min(end)..end.saturating_sub(1).max(open + 1)];
641 let names = split_commas(inner)
643 .into_iter()
644 .filter_map(|part| part.iter().find(|t| t.is_name()).map(|t| t.value.clone()))
645 .take(MAX_COLUMNS + 1)
646 .collect();
647 (names, end)
648}
649
650fn bounded(table: &str, line: usize, columns: Vec<String>) -> Result<Vec<String>, SqlError> {
652 if columns.len() > MAX_COLUMNS {
653 return Err(SqlError::new(
654 line,
655 format!("{table}: a key of more than {MAX_COLUMNS} columns"),
656 ));
657 }
658 Ok(columns)
659}
660
661fn qualified(tokens: &[Token], mut at: usize) -> Option<(String, usize)> {
663 let mut parts = Vec::new();
664 loop {
665 let token = tokens.get(at).filter(|t| t.is_name())?;
666 parts.push(token.value.clone());
667 at += 1;
668 if tokens.get(at).is_some_and(|t| t.is_symbol(".")) {
669 at += 1;
670 } else {
671 return Some((parts.join("."), at));
672 }
673 }
674}
675
676struct Reader {
679 schema: Schema,
680 index: HashMap<String, usize>,
682 notes: Vec<Note>,
683 dropped: usize,
684}
685
686const COLUMN_KEYWORDS: &[&str] = &[
688 "CONSTRAINT",
689 "NOT",
690 "NULL",
691 "DEFAULT",
692 "PRIMARY",
693 "UNIQUE",
694 "REFERENCES",
695 "CHECK",
696 "COLLATE",
697 "GENERATED",
698 "AS",
699 "AUTO_INCREMENT",
700 "AUTOINCREMENT",
701 "IDENTITY",
702 "COMMENT",
703 "ON",
704 "CHARACTER",
705 "CHARSET",
706];
707
708impl Reader {
709 fn note(&mut self, line: usize, message: impl Into<String>) {
710 if self.notes.len() < MAX_NOTES {
711 self.notes.push(Note {
712 line,
713 message: message.into(),
714 });
715 } else {
716 self.dropped += 1;
717 }
718 }
719
720 fn statement(&mut self, tokens: &[Token]) -> Result<(), SqlError> {
721 let line = tokens[0].line;
722 let mut at = 0;
723 let word = |at: usize, w: &str| tokens.get(at).is_some_and(|t| t.is(w));
724 if !word(0, "CREATE") {
725 let what = tokens[0].text.to_uppercase();
726 self.note(line, format!("{what} statement skipped"));
727 return Ok(());
728 }
729 at += 1;
730 if word(at, "OR") && word(at + 1, "REPLACE") {
731 at += 2;
732 }
733 while TABLE_MODIFIERS.iter().any(|w| word(at, w)) {
734 at += 1;
735 }
736 if !word(at, "TABLE") {
737 let objects = [
739 "INDEX",
740 "VIEW",
741 "SEQUENCE",
742 "TYPE",
743 "FUNCTION",
744 "TRIGGER",
745 "SCHEMA",
746 "EXTENSION",
747 "DATABASE",
748 "PROCEDURE",
749 "DOMAIN",
750 ];
751 let mut what: Vec<String> = Vec::new();
752 for token in tokens.iter().take_while(|t| t.kind == Kind::Word).take(5) {
753 what.push(token.text.to_uppercase());
754 if objects.iter().any(|w| token.is(w)) {
755 break;
756 }
757 }
758 self.note(line, format!("{} statement skipped", what.join(" ")));
759 return Ok(());
760 }
761 at += 1;
762 if word(at, "IF") && word(at + 1, "NOT") && word(at + 2, "EXISTS") {
763 at += 3;
764 }
765 let Some((name, after)) = qualified(tokens, at) else {
766 let line = tokens.get(at).map_or(line, |t| t.line);
767 return Err(SqlError::new(line, "CREATE TABLE needs a table name"));
768 };
769 at = after;
770 if word(at, "AS") || word(at, "LIKE") {
771 let what = tokens[at].text.to_uppercase();
772 self.note(line, format!("{name}: CREATE TABLE … {what} skipped"));
773 return Ok(());
774 }
775 if !tokens.get(at).is_some_and(|t| t.is_symbol("(")) {
776 let line = tokens.get(at).map_or(line, |t| t.line);
777 return Err(SqlError::new(
778 line,
779 format!("CREATE TABLE {name} needs a column list in parentheses"),
780 ));
781 }
782 let end = closing(tokens, at);
783 let body = &tokens[at + 1..end - 1];
784 let table = self.table(&name, body, line)?;
785 if end < tokens.len() {
786 let options = &tokens[end..];
789 let comment = options
790 .iter()
791 .position(|t| t.is("COMMENT"))
792 .and_then(|at| options[at + 1..].iter().find(|t| !t.is_symbol("=")))
793 .filter(|t| t.kind == Kind::Str)
794 .map(|t| t.value.clone());
795 let table = match comment {
796 Some(comment) => table.with_description(comment),
797 None => table,
798 };
799 self.add(table, line);
800 } else {
801 self.add(table, line);
802 }
803 Ok(())
804 }
805
806 fn add(&mut self, table: Schema, line: usize) {
807 let name = table.name().unwrap_or_default().to_string();
808 if let Some(&index) = self.index.get(&name) {
809 self.note(
810 line,
811 format!("{name}: defined again; the later definition is kept"),
812 );
813 self.schema.tables_mut()[index] = table;
814 } else {
815 self.index.insert(name, self.schema.tables().len());
816 self.schema.push_table(table);
817 }
818 }
819
820 fn table(&mut self, name: &str, body: &[Token], line: usize) -> Result<Schema, SqlError> {
821 let parts = split_commas(body);
822 if parts.len() > MAX_COLUMNS {
823 return Err(SqlError::new(
824 line,
825 format!("{name}: more than {MAX_COLUMNS} columns"),
826 ));
827 }
828 let mut table = Schema::default().named(name);
829 let mut keys: Vec<(usize, TableKey)> = Vec::new();
830 for part in parts {
831 let Some(first) = part.first() else {
832 continue;
833 };
834 let mut at = 0;
835 if first.is("CONSTRAINT") {
836 at = 2;
837 }
838 let Some(head) = part.get(at) else {
839 return Err(SqlError::new(
840 first.line,
841 format!("{name}: CONSTRAINT needs a rule"),
842 ));
843 };
844 let word = |at: usize, w: &str| part.get(at).is_some_and(|t| t.is(w));
845 let open = |at: usize| part.get(at).is_some_and(|t| t.is_symbol("("));
846 if head.is("PRIMARY") && word(at + 1, "KEY") {
847 let mut open_at = at + 2;
849 while part.get(open_at).is_some_and(|t| t.kind == Kind::Word) {
850 open_at += 1;
851 }
852 if !open(open_at) {
853 return Err(SqlError::new(
854 head.line,
855 format!("{name}: PRIMARY KEY needs its columns"),
856 ));
857 }
858 let columns = bounded(name, head.line, name_list(part, open_at).0)?;
859 keys.push((head.line, TableKey::Primary(columns)));
860 } else if head.is("UNIQUE") {
861 let mut open_at = at + 1;
862 while part.get(open_at).is_some_and(|t| !t.is_symbol("(")) {
863 open_at += 1;
864 }
865 if !open(open_at) {
866 return Err(SqlError::new(
867 head.line,
868 format!("{name}: UNIQUE needs its columns"),
869 ));
870 }
871 let columns = bounded(name, head.line, name_list(part, open_at).0)?;
872 keys.push((head.line, TableKey::Unique(columns)));
873 } else if head.is("FOREIGN") && word(at + 1, "KEY") {
874 let mut open_at = at + 2;
876 while part
877 .get(open_at)
878 .is_some_and(|t| !t.is_symbol("(") && !t.is("REFERENCES"))
879 {
880 open_at += 1;
881 }
882 if !open(open_at) {
883 return Err(SqlError::new(
884 head.line,
885 format!("{name}: FOREIGN KEY needs its columns"),
886 ));
887 }
888 let (columns, after) = name_list(part, open_at);
889 let columns = bounded(name, head.line, columns)?;
890 let Some(key) = references(part, after) else {
891 return Err(SqlError::new(
892 head.line,
893 format!("{name}: FOREIGN KEY needs REFERENCES and a table"),
894 ));
895 };
896 let references = bounded(name, head.line, key.references)?;
897 let key = ForeignKey {
898 columns,
899 references,
900 ..key
901 };
902 keys.push((head.line, TableKey::Foreign(key)));
903 } else if head.is("CHECK") {
904 self.note(head.line, format!("{name}: CHECK constraint skipped"));
905 } else if [
906 "KEY", "INDEX", "FULLTEXT", "SPATIAL", "EXCLUDE", "LIKE", "PERIOD",
907 ]
908 .iter()
909 .any(|w| head.is(w))
910 && !(at == 0 && named_like_a_clause(part))
911 {
912 let what = head.text.to_uppercase();
913 self.note(head.line, format!("{name}: {what} clause skipped"));
914 } else if at == 0 && head.is_name() {
915 let field = self.column(name, part)?;
916 table.push(field);
917 } else {
918 return Err(SqlError::new(
919 head.line,
920 format!(
921 "{name}: expected a column or a constraint, found `{}`",
922 head.text
923 ),
924 ));
925 }
926 }
927 for (line, key) in keys {
928 self.apply(&mut table, key, line);
929 }
930 Ok(table)
931 }
932
933 fn apply(&mut self, table: &mut Schema, key: TableKey, line: usize) {
936 let columns = key.columns().to_vec();
937 let name = table.name().unwrap_or_default().to_string();
938 for column in &columns {
939 if table.field(column).is_none() {
940 self.note(line, format!("{name}: a key names no column {column:?}"));
941 return;
942 }
943 }
944 let single = columns.len() == 1;
945 let index = |table: &Schema, column: &str| table.index_of(column).unwrap_or(0);
946 match key {
947 TableKey::Primary(columns) => {
948 for column in &columns {
949 let i = index(table, column);
950 let field = &mut table.fields_mut()[i];
951 *field = field.clone().required(true).nullable(false);
952 if single && !field.is_primary_key() {
953 field
954 .constraints_mut()
955 .push(Constraint::PrimaryKey(Vec::new()));
956 }
957 }
958 if !single {
959 table
960 .constraints_mut()
961 .push(Constraint::PrimaryKey(columns));
962 }
963 }
964 TableKey::Unique(columns) if single => {
965 let i = index(table, &columns[0]);
966 let field = &mut table.fields_mut()[i];
967 if !field.is_unique() {
968 field.constraints_mut().push(Constraint::Unique(Vec::new()));
969 }
970 }
971 TableKey::Unique(columns) => table.constraints_mut().push(Constraint::Unique(columns)),
972 TableKey::Foreign(key) if single => {
973 let i = index(table, &key.columns[0]);
974 let field = &mut table.fields_mut()[i];
975 field
976 .constraints_mut()
977 .push(Constraint::References(ForeignKey {
978 columns: Vec::new(),
979 ..key
980 }));
981 }
982 TableKey::Foreign(key) => table.constraints_mut().push(Constraint::References(key)),
983 }
984 }
985
986 fn column(&mut self, table: &str, part: &[Token]) -> Result<Field, SqlError> {
987 let name = &part[0];
988 let mut at = 1;
990 while at < part.len() {
991 let token = &part[at];
992 if token.is_symbol("(") {
993 at = closing(part, at);
994 continue;
995 }
996 if COLUMN_KEYWORDS.iter().any(|w| token.is(w)) {
997 let character_type = token.is("CHARACTER") && at == 1;
1000 if !character_type {
1001 break;
1002 }
1003 }
1004 at += 1;
1005 }
1006 let type_tokens = &part[1..at];
1007 let native = write(type_tokens);
1008 let mut field = Field::new(name.value.clone(), sql_type(type_tokens));
1009 if !native.is_empty() {
1010 field = field.with_native_type(native.clone());
1011 }
1012 if let Some(values) = enum_values(type_tokens) {
1013 field = field.with_constraint(Constraint::Enum(values));
1014 }
1015 while at < part.len() {
1016 let token = &part[at];
1017 let word = |offset: usize, w: &str| part.get(at + offset).is_some_and(|t| t.is(w));
1018 if token.is("CONSTRAINT") {
1019 at += 2;
1020 } else if token.is("NOT") && word(1, "NULL") {
1021 field = field.nullable(false).required(true);
1022 at += 2;
1023 } else if token.is("NULL") {
1024 field = field.nullable(true);
1025 at += 1;
1026 } else if token.is("DEFAULT") {
1027 let end = expression_end(part, at + 1);
1028 let text = write(&part[at + 1..end]);
1029 field = field.with_constraint(Constraint::Default(Literal::new(text)));
1030 at = end;
1031 } else if token.is("PRIMARY") && word(1, "KEY") {
1032 field = field
1033 .nullable(false)
1034 .required(true)
1035 .with_constraint(Constraint::PrimaryKey(Vec::new()));
1036 at += 2;
1037 } else if token.is("UNIQUE") {
1038 field = field.with_constraint(Constraint::Unique(Vec::new()));
1039 at += if word(1, "KEY") { 2 } else { 1 };
1040 } else if token.is("REFERENCES") {
1041 match references(part, at) {
1042 Some(mut key) => {
1043 key.references = bounded(table, token.line, key.references)?;
1044 field = field.with_constraint(Constraint::References(key));
1045 }
1046 None => {
1047 return Err(SqlError::new(
1048 token.line,
1049 format!("{table}.{}: REFERENCES needs a table", name.value),
1050 ));
1051 }
1052 }
1053 at = skip_reference(part, at);
1054 } else if token.is("CHECK") {
1055 self.note(
1056 token.line,
1057 format!("{table}.{}: CHECK constraint skipped", name.value),
1058 );
1059 at += 1;
1060 if part.get(at).is_some_and(|t| t.is_symbol("(")) {
1061 at = closing(part, at);
1062 }
1063 } else if token.is("COMMENT") && part.get(at + 1).is_some_and(|t| t.kind == Kind::Str) {
1064 field = field.with_description(part[at + 1].value.clone());
1065 at += 2;
1066 } else if token.is_symbol("(") {
1067 at = closing(part, at);
1068 } else {
1069 at += 1;
1072 }
1073 }
1074 Ok(field)
1075 }
1076}
1077
1078fn named_like_a_clause(part: &[Token]) -> bool {
1083 match part.get(1) {
1084 None => true,
1085 Some(next) => next.kind == Kind::Word && sql_type(&part[1..2]) != DataType::Unknown,
1086 }
1087}
1088
1089enum TableKey {
1091 Primary(Vec<String>),
1092 Unique(Vec<String>),
1093 Foreign(ForeignKey),
1094}
1095
1096impl TableKey {
1097 fn columns(&self) -> &[String] {
1098 match self {
1099 TableKey::Primary(columns) | TableKey::Unique(columns) => columns,
1100 TableKey::Foreign(key) => &key.columns,
1101 }
1102 }
1103}
1104
1105fn references(tokens: &[Token], mut at: usize) -> Option<ForeignKey> {
1108 while tokens.get(at).is_some_and(|t| !t.is("REFERENCES")) {
1109 at += 1;
1110 }
1111 let (table, after) = qualified(tokens, at + 1)?;
1112 let references = if tokens.get(after).is_some_and(|t| t.is_symbol("(")) {
1113 name_list(tokens, after).0
1114 } else {
1115 Vec::new()
1116 };
1117 Some(ForeignKey {
1118 columns: Vec::new(),
1119 table,
1120 references,
1121 })
1122}
1123
1124fn skip_reference(tokens: &[Token], at: usize) -> usize {
1127 let Some((_, mut at)) = qualified(tokens, at + 1) else {
1128 return tokens.len();
1129 };
1130 if tokens.get(at).is_some_and(|t| t.is_symbol("(")) {
1131 at = closing(tokens, at);
1132 }
1133 let options = [
1134 "ON",
1135 "DELETE",
1136 "UPDATE",
1137 "CASCADE",
1138 "RESTRICT",
1139 "SET",
1140 "NULL",
1141 "DEFAULT",
1142 "NO",
1143 "ACTION",
1144 "MATCH",
1145 "FULL",
1146 "PARTIAL",
1147 "SIMPLE",
1148 "DEFERRABLE",
1149 "INITIALLY",
1150 "DEFERRED",
1151 "IMMEDIATE",
1152 "NOT",
1153 ];
1154 while let Some(token) = tokens.get(at) {
1155 if token.is("NOT") && tokens.get(at + 1).is_some_and(|t| t.is("NULL")) {
1157 break;
1158 }
1159 let after_set = tokens.get(at.wrapping_sub(1)).is_some_and(|t| t.is("SET"));
1161 if (token.is("NULL") || token.is("DEFAULT")) && !after_set {
1162 break;
1163 }
1164 if options.iter().any(|w| token.is(w)) {
1165 at += 1;
1166 } else {
1167 break;
1168 }
1169 }
1170 at
1171}
1172
1173fn expression_end(tokens: &[Token], mut at: usize) -> usize {
1176 let start = at;
1177 while let Some(token) = tokens.get(at) {
1178 if token.is_symbol("(") {
1179 at = closing(tokens, at);
1180 continue;
1181 }
1182 let keyword = [
1183 "NOT",
1184 "NULL",
1185 "PRIMARY",
1186 "UNIQUE",
1187 "REFERENCES",
1188 "CHECK",
1189 "CONSTRAINT",
1190 "COLLATE",
1191 "GENERATED",
1192 "COMMENT",
1193 "AUTO_INCREMENT",
1194 "AUTOINCREMENT",
1195 "ON",
1196 ]
1197 .iter()
1198 .any(|w| token.is(w));
1199 if keyword && at > start {
1201 break;
1202 }
1203 at += 1;
1204 }
1205 at
1206}
1207
1208fn enum_values(type_tokens: &[Token]) -> Option<Vec<Literal>> {
1210 let first = type_tokens.first()?;
1211 if !(first.is("ENUM") || first.is("SET")) {
1212 return None;
1213 }
1214 let values: Vec<Literal> = type_tokens
1215 .iter()
1216 .filter(|t| t.kind == Kind::Str)
1217 .map(|t| Literal::new(t.text.clone()))
1218 .collect();
1219 (!values.is_empty()).then_some(values)
1220}
1221
1222fn sql_type(type_tokens: &[Token]) -> DataType {
1224 let words: Vec<String> = type_tokens
1225 .iter()
1226 .filter(|t| t.kind == Kind::Word)
1227 .map(|t| t.text.to_uppercase())
1228 .collect();
1229 let Some(base) = words.first() else {
1230 return DataType::Unknown;
1231 };
1232 let array = words.iter().any(|w| w == "ARRAY")
1233 || type_tokens
1234 .iter()
1235 .any(|t| t.is_symbol("[]") || t.is_symbol("["));
1236 let has = |word: &str| words.iter().any(|w| w == word);
1237 let numbers: Vec<i64> = type_tokens
1238 .iter()
1239 .filter(|t| t.kind == Kind::Number)
1240 .filter_map(|t| t.text.parse().ok())
1241 .collect();
1242 let scalar = match base.as_str() {
1243 "INT" | "INTEGER" | "SMALLINT" | "BIGINT" | "TINYINT" | "MEDIUMINT" | "SERIAL"
1244 | "BIGSERIAL" | "SMALLSERIAL" | "INT2" | "INT4" | "INT8" | "SERIAL4" | "SERIAL8"
1245 | "UNSIGNED" => DataType::Integer,
1246 "REAL" | "FLOAT" | "FLOAT4" | "FLOAT8" | "DOUBLE" => DataType::Float,
1247 "DECIMAL" | "NUMERIC" | "DEC" | "NUMBER" | "MONEY" | "SMALLMONEY" => DataType::Decimal {
1248 precision: numbers.first().and_then(|&p| u32::try_from(p).ok()),
1249 scale: numbers.get(1).and_then(|&s| i32::try_from(s).ok()),
1250 },
1251 "CHAR" | "VARCHAR" | "CHARACTER" | "NCHAR" | "NVARCHAR" | "VARCHAR2" | "NVARCHAR2"
1252 | "TEXT" | "TINYTEXT" | "MEDIUMTEXT" | "LONGTEXT" | "NTEXT" | "CLOB" | "NCLOB"
1253 | "STRING" | "CITEXT" | "UUID" | "UNIQUEIDENTIFIER" | "ENUM" | "SET" | "INET" | "CIDR"
1254 | "MACADDR" | "XML" => DataType::String,
1255 "BOOLEAN" | "BOOL" => DataType::Boolean,
1256 "BLOB" | "TINYBLOB" | "MEDIUMBLOB" | "LONGBLOB" | "BYTEA" | "BINARY" | "VARBINARY"
1257 | "IMAGE" | "RAW" => DataType::Binary,
1258 "DATE" => DataType::Date,
1259 "TIMESTAMPTZ" => DataType::Timestamp {
1260 timezone: Some("UTC".into()),
1261 },
1262 "TIMESTAMP" | "DATETIME" | "DATETIME2" | "SMALLDATETIME" | "DATETIMEOFFSET" => {
1263 let zoned = (has("WITH") && !has("WITHOUT")) || base == "DATETIMEOFFSET";
1264 DataType::Timestamp {
1265 timezone: zoned.then(|| "UTC".to_string()),
1266 }
1267 }
1268 "JSON" | "JSONB" | "VARIANT" => DataType::Any,
1269 _ => DataType::Unknown,
1270 };
1271 if array {
1272 DataType::list(scalar)
1273 } else {
1274 scalar
1275 }
1276}
1277
1278#[cfg(test)]
1279mod tests {
1280 use super::*;
1281
1282 fn notes(parsed: &Parsed) -> Vec<String> {
1283 parsed.notes.iter().map(ToString::to_string).collect()
1284 }
1285
1286 #[test]
1287 fn columns_types_and_keys() {
1288 let parsed = parse(
1289 r#"
1290 CREATE TABLE IF NOT EXISTS "public"."order items" (
1291 "order" BIGINT NOT NULL REFERENCES orders (id) ON DELETE CASCADE,
1292 line INT NOT NULL,
1293 price NUMERIC(10, 2) DEFAULT 0.00 NOT NULL,
1294 status ENUM('new', 'paid') DEFAULT 'new' COMMENT 'Where it is',
1295 tags TEXT[],
1296 at TIMESTAMP WITH TIME ZONE DEFAULT now(),
1297 weight DOUBLE PRECISION NULL,
1298 code CHARACTER VARYING(8) UNIQUE,
1299 PRIMARY KEY ("order", line),
1300 UNIQUE (line, code),
1301 CONSTRAINT fk FOREIGN KEY (line, code) REFERENCES lines (n, c)
1302 ) ENGINE=InnoDB COMMENT 'Lines';
1303 "#,
1304 )
1305 .unwrap();
1306 assert!(parsed.notes.is_empty(), "{:?}", parsed.notes);
1307 let table = &parsed.schema.tables()[0];
1308 assert_eq!(table.name(), Some("public.order items"));
1309 assert_eq!(table.description(), Some("Lines"));
1310 let types: Vec<(String, String)> = table
1311 .fields()
1312 .iter()
1313 .map(|f| {
1314 (
1315 f.native_type().unwrap().to_string(),
1316 f.data_type().to_string(),
1317 )
1318 })
1319 .collect();
1320 let types: Vec<(&str, &str)> = types
1321 .iter()
1322 .map(|(a, b)| (a.as_str(), b.as_str()))
1323 .collect();
1324 assert_eq!(
1325 types,
1326 [
1327 ("BIGINT", "integer"),
1328 ("INT", "integer"),
1329 ("NUMERIC(10, 2)", "decimal(10,2)"),
1330 ("ENUM('new', 'paid')", "string"),
1331 ("TEXT[]", "list<string>"),
1332 ("TIMESTAMP WITH TIME ZONE", "timestamp[UTC]"),
1333 ("DOUBLE PRECISION", "float"),
1334 ("CHARACTER VARYING(8)", "string"),
1335 ]
1336 );
1337 let order = table.field("order").unwrap();
1338 assert!(order.is_required() && !order.is_nullable());
1339 assert_eq!(order.references().unwrap().to_string(), "orders.id");
1340 assert_eq!(table.primary_key(), ["order", "line"]);
1341 let price = table.field("price").unwrap();
1342 assert_eq!(price.default_value().unwrap().as_str(), "0.00");
1343 assert!(price.is_required());
1344 let status = table.field("status").unwrap();
1345 assert_eq!(status.default_value().unwrap().as_str(), "'new'");
1346 assert_eq!(status.description(), Some("Where it is"));
1347 assert_eq!(status.enum_values().unwrap().len(), 2);
1348 assert_eq!(
1349 table.field("at").unwrap().default_value().unwrap().as_str(),
1350 "now()"
1351 );
1352 assert!(table.field("code").unwrap().is_unique());
1353 let shown: Vec<String> = table
1354 .constraints()
1355 .iter()
1356 .map(ToString::to_string)
1357 .collect();
1358 assert_eq!(
1359 shown,
1360 [
1361 "primary key (order, line)",
1362 "unique (line, code)",
1363 "→ (line, code) → lines.(n, c)",
1364 ]
1365 );
1366 }
1367
1368 #[test]
1369 fn a_default_after_a_reference_is_the_columns() {
1370 let parsed = parse(
1371 "CREATE TABLE t (\
1372 a INT REFERENCES p (id) DEFAULT 0, \
1373 b INT REFERENCES p (id) ON DELETE SET DEFAULT DEFAULT 1 NOT NULL, \
1374 c INT REFERENCES p ON UPDATE SET NULL NULL);",
1375 )
1376 .unwrap();
1377 let t = &parsed.schema.tables()[0];
1378 let a = t.field("a").unwrap();
1379 assert_eq!(a.default_value().unwrap().as_str(), "0");
1380 assert_eq!(a.references().unwrap().to_string(), "p.id");
1381 let b = t.field("b").unwrap();
1382 assert_eq!(b.default_value().unwrap().as_str(), "1");
1383 assert!(b.is_required());
1384 let c = t.field("c").unwrap();
1385 assert!(c.default_value().is_none() && c.is_nullable());
1386 }
1387
1388 #[test]
1389 fn single_column_table_keys_go_on_the_column() {
1390 let parsed = parse(
1391 "CREATE TABLE t (a INT, b INT, PRIMARY KEY (a), UNIQUE (b), \
1392 FOREIGN KEY (b) REFERENCES u);",
1393 )
1394 .unwrap();
1395 let t = &parsed.schema.tables()[0];
1396 assert!(t.constraints().is_empty());
1397 assert!(t.field("a").unwrap().is_primary_key());
1398 assert!(t.field("a").unwrap().is_required());
1399 assert!(t.field("b").unwrap().is_unique());
1400 assert_eq!(t.foreign_keys()[0].to_string(), "b → u");
1401 }
1402
1403 #[test]
1404 fn other_statements_and_clauses_are_noted() {
1405 let parsed = parse(
1406 "/* a dump */\n\
1407 SET NAMES utf8;\n\
1408 CREATE TABLE a (x INT CHECK (x > 0), KEY ix (x));\n\
1409 CREATE UNIQUE INDEX i ON a (x);\n\
1410 CREATE TABLE b AS SELECT * FROM a;\n\
1411 CREATE TABLE a (y TEXT);\n\
1412 INSERT INTO a VALUES (1);",
1413 )
1414 .unwrap();
1415 assert_eq!(
1416 notes(&parsed),
1417 [
1418 "line 2: SET statement skipped",
1419 "line 3: a.x: CHECK constraint skipped",
1420 "line 3: a: KEY clause skipped",
1421 "line 4: CREATE UNIQUE INDEX statement skipped",
1422 "line 5: b: CREATE TABLE … AS skipped",
1423 "line 6: a: defined again; the later definition is kept",
1424 "line 7: INSERT statement skipped",
1425 ]
1426 );
1427 assert_eq!(parsed.schema.tables().len(), 1);
1428 assert_eq!(parsed.schema.tables()[0].fields()[0].name(), "y");
1429 }
1430
1431 #[test]
1432 fn errors_name_their_line() {
1433 let cases = [
1434 ("CREATE TABLE t (\n a INT", "line 1: a `(` is not closed"),
1435 ("\n\nSELECT 'oops", "line 3: a string is not closed"),
1436 ("/* never\nclosed", "line 1: a /* comment is not closed"),
1437 (
1438 "CREATE TABLE t\n;",
1439 "line 1: CREATE TABLE t needs a column list in parentheses",
1440 ),
1441 (
1442 "CREATE TABLE (a INT);",
1443 "line 1: CREATE TABLE needs a table name",
1444 ),
1445 ("x)", "line 1: a `)` closes nothing"),
1446 (
1447 "CREATE TABLE t (a INT REFERENCES);",
1448 "line 1: t.a: REFERENCES needs a table",
1449 ),
1450 (
1451 "CREATE TABLE t (\n, PRIMARY KEY);",
1452 "line 2: t: PRIMARY KEY needs its columns",
1453 ),
1454 ];
1455 for (ddl, expected) in cases {
1456 assert_eq!(parse(ddl).unwrap_err().to_string(), expected, "{ddl}");
1457 }
1458 }
1459
1460 #[test]
1461 fn limits_hold() {
1462 let deep = format!(
1463 "CREATE TABLE t (a INT DEFAULT {}1{});",
1464 "(".repeat(100),
1465 ")".repeat(100)
1466 );
1467 assert!(parse(&deep).unwrap_err().message().contains("nest"));
1468 let wide: Vec<String> = (0..=MAX_COLUMNS).map(|i| format!("c{i} INT")).collect();
1469 let wide = format!("CREATE TABLE t ({});", wide.join(", "));
1470 assert!(parse(&wide).unwrap_err().message().contains("columns"));
1471 let mut many = String::new();
1472 for i in 0..1_200 {
1473 many.push_str(&format!("INSERT INTO t VALUES ({i});\n"));
1474 }
1475 let parsed = parse(&many).unwrap();
1476 assert_eq!(parsed.notes.len(), MAX_NOTES + 1);
1477 assert_eq!(parsed.notes.last().unwrap().message, "… and 200 more notes");
1478 }
1479
1480 #[test]
1481 fn a_foreign_key_without_columns_is_an_error() {
1482 for ddl in [
1483 "CREATE TABLE t (a INT, FOREIGN KEY REFERENCES u)",
1484 "CREATE TABLE t (a INT, FOREIGN KEY)",
1485 "CREATE TABLE t (a INT, CONSTRAINT f FOREIGN KEY REFERENCES u (id))",
1486 ] {
1487 let error = parse(ddl).unwrap_err();
1488 assert_eq!(
1489 error.to_string(),
1490 "line 1: t: FOREIGN KEY needs its columns",
1491 "{ddl}"
1492 );
1493 }
1494 }
1495
1496 #[test]
1497 fn columns_named_like_index_clauses_are_columns() {
1498 let parsed = parse(
1499 "CREATE TABLE settings (key TEXT PRIMARY KEY, index INT NOT NULL, \
1500 period VARCHAR(10), KEY ix (index), INDEX (period), KEY (key));",
1501 )
1502 .unwrap();
1503 let t = &parsed.schema.tables()[0];
1504 let names: Vec<&str> = t.fields().iter().map(Field::name).collect();
1505 assert_eq!(names, ["key", "index", "period"]);
1506 assert_eq!(t.primary_key(), ["key"]);
1507 assert!(t.field("index").unwrap().is_required());
1508 assert_eq!(
1509 notes(&parsed),
1510 [
1511 "line 1: settings: KEY clause skipped",
1512 "line 1: settings: INDEX clause skipped",
1513 "line 1: settings: KEY clause skipped",
1514 ]
1515 );
1516 }
1517
1518 #[test]
1519 fn many_tables_parse_in_linear_time() {
1520 let mut ddl = String::new();
1523 for i in 0..MAX_STATEMENTS - 1 {
1524 ddl.push_str(&format!("CREATE TABLE t{i} (a INT);\n"));
1525 }
1526 ddl.push_str("CREATE TABLE t7 (b INT);");
1527 let started = std::time::Instant::now();
1528 let parsed = parse(&ddl).unwrap();
1529 assert!(started.elapsed() < std::time::Duration::from_secs(30));
1530 assert_eq!(parsed.schema.tables().len(), MAX_STATEMENTS - 1);
1531 assert_eq!(parsed.schema.tables()[7].fields()[0].name(), "b");
1532 assert_eq!(
1533 notes(&parsed),
1534 [format!(
1535 "line {MAX_STATEMENTS}: t7: defined again; the later definition is kept"
1536 )]
1537 );
1538 ddl.push_str("\nCREATE TABLE u (a INT);");
1539 let error = parse(&ddl).unwrap_err();
1540 assert_eq!(
1541 error.message(),
1542 format!("more than {MAX_STATEMENTS} statements")
1543 );
1544 }
1545
1546 #[test]
1547 fn key_column_lists_are_bounded() {
1548 let names = vec!["a"; MAX_COLUMNS + 1].join(", ");
1551 for ddl in [
1552 format!("CREATE TABLE t (a INT, PRIMARY KEY ({names}));"),
1553 format!("CREATE TABLE t (a INT, UNIQUE ({names}));"),
1554 format!("CREATE TABLE t (a INT, FOREIGN KEY ({names}) REFERENCES u);"),
1555 format!("CREATE TABLE t (a INT, FOREIGN KEY (a) REFERENCES u ({names}));"),
1556 format!("CREATE TABLE t (a INT REFERENCES u ({names}));"),
1557 ] {
1558 let error = parse(&ddl).unwrap_err();
1559 assert_eq!(
1560 error.to_string(),
1561 format!("line 1: t: a key of more than {MAX_COLUMNS} columns"),
1562 );
1563 }
1564 }
1565
1566 #[test]
1567 fn statements_are_read_one_at_a_time() {
1568 let long = format!("CREATE TABLE t ({});", "1 ".repeat(MAX_TOKENS));
1571 assert_eq!(
1572 parse(&long).unwrap_err().to_string(),
1573 format!("line 1: a statement of more than {MAX_TOKENS} tokens")
1574 );
1575 let insert = format!(
1576 "INSERT INTO t VALUES ({});\nCREATE TABLE u (a INT);",
1577 "1, ".repeat(MAX_TOKENS)
1578 );
1579 let parsed = parse(&insert).unwrap();
1580 assert_eq!(notes(&parsed), ["line 1: INSERT statement skipped"]);
1581 assert_eq!(parsed.schema.tables()[0].name(), Some("u"));
1582 assert_eq!(
1585 parse("CREATE TABLE t (a INT));\nSELECT 'oops")
1586 .unwrap_err()
1587 .to_string(),
1588 "line 2: a string is not closed"
1589 );
1590 assert_eq!(
1591 parse("CREATE TABLE t (a INT REFERENCES);\nx)")
1592 .unwrap_err()
1593 .to_string(),
1594 "line 2: a `)` closes nothing"
1595 );
1596 }
1597
1598 #[test]
1602 fn dollar_quoted_bodies_are_strings() {
1603 let parsed = parse(
1604 "CREATE TABLE users (id INT PRIMARY KEY);\n\
1605 CREATE FUNCTION audit() RETURNS trigger AS $$\n\
1606 BEGIN\n\
1607 CREATE TEMP TABLE scratch (n INT);\n\
1608 RETURN NEW;\n\
1609 END;\n\
1610 $$ LANGUAGE plpgsql;\n\
1611 CREATE FUNCTION q() RETURNS text AS $body$ SELECT $q$don't$q$; $body$;\n\
1612 CREATE TABLE posts (id INT, author INT DEFAULT $1);",
1613 )
1614 .unwrap();
1615 let tables: Vec<&str> = parsed
1616 .schema
1617 .tables()
1618 .iter()
1619 .filter_map(Schema::name)
1620 .collect();
1621 assert_eq!(tables, ["users", "posts"]);
1622 assert_eq!(
1623 notes(&parsed),
1624 [
1625 "line 2: CREATE FUNCTION statement skipped",
1626 "line 8: CREATE FUNCTION statement skipped",
1627 ]
1628 );
1629 let author = parsed.schema.tables()[1].field("author").unwrap();
1631 assert!(author.default_value().is_some());
1632 assert_eq!(
1633 parse("SELECT $tag$ never closed").unwrap_err().to_string(),
1634 "line 1: a string is not closed"
1635 );
1636 }
1637
1638 #[test]
1643 fn statements_without_semicolons_and_clustered_keys() {
1644 let parsed = parse(
1645 "SET ANSI_NULLS ON\nGO\n\
1646 CREATE TABLE [dbo].[a] ([id] INT NOT NULL,\n\
1647 CONSTRAINT [PK_a] PRIMARY KEY CLUSTERED ([id] ASC) WITH (PAD_INDEX = OFF)\n\
1648 ) ON [PRIMARY]\nGO\n\
1649 CREATE TABLE [dbo].[b] ([a_id] INT REFERENCES [dbo].[a] ([id]))\n\
1650 CREATE TABLE c (x INT)",
1651 )
1652 .unwrap();
1653 let tables: Vec<&str> = parsed
1654 .schema
1655 .tables()
1656 .iter()
1657 .filter_map(Schema::name)
1658 .collect();
1659 assert_eq!(tables, ["dbo.a", "dbo.b", "c"]);
1660 assert_eq!(parsed.schema.tables()[0].primary_key(), ["id"]);
1661 assert_eq!(notes(&parsed), ["line 1: SET statement skipped"]);
1662 }
1663
1664 #[test]
1665 fn odd_input_never_panics() {
1666 for ddl in [
1667 "",
1668 ";;;",
1669 "CREATE",
1670 "CREATE TABLE",
1671 "CREATE TABLE t ()",
1672 "CREATE TABLE t (CONSTRAINT)",
1673 "CREATE TABLE t (a)",
1674 "CREATE TABLE t (a INT, , b INT)",
1675 "CREATE TABLE t (FOREIGN KEY (a) REFERENCES)",
1676 "CREATE TABLE t (UNIQUE)",
1677 "CREATE TABLE t (a INT DEFAULT)",
1678 "CREATE TABLE t (a INT REFERENCES u (",
1679 "CREATE TABLE [t (a INT)",
1680 "CREATE TABLE \"t\" (\"a\"\"b\" INT)",
1681 "ÄÖ ü 1e+ .5 :: ::: \u{0}",
1682 ] {
1683 let _ = parse(ddl);
1684 }
1685 let parsed = parse("CREATE TABLE \"t\" (\"a\"\"b\" INT, c)").unwrap();
1686 let t = &parsed.schema.tables()[0];
1687 assert_eq!(t.fields()[0].name(), "a\"b");
1688 assert_eq!(t.fields()[1].data_type(), &DataType::Unknown);
1689 }
1690}