1#![cfg_attr(not(test), no_std)]
13#![forbid(unsafe_code)]
14
15extern crate alloc;
51
52use alloc::vec::Vec;
53pub use qusql_parse::{ByteToChar, Fragment, Issue, Issues, Level};
54use qusql_parse::{ParseOptions, parse_statement};
55use schema::Schemas;
56
57mod type_;
58mod type_binary_expression;
59mod type_call;
60mod type_delete;
61mod type_expression;
62mod type_function;
63mod type_insert_replace;
64mod type_lock;
65mod type_reference;
66mod type_select;
67mod type_set;
68mod type_statement;
69mod type_truncate;
70mod type_update;
71mod typer;
72
73pub mod schema;
74pub use type_::{BaseType, FullType, Type};
75pub use type_insert_replace::AutoIncrementId;
76pub use type_select::SelectTypeColumn;
77use typer::Typer;
78
79pub use qusql_parse::{SQLArguments, SQLDialect};
80
81#[derive(Debug, Default, Clone)]
83pub struct TypeOptions {
84 parse_options: ParseOptions,
85 warn_unnamed_column_in_select: bool,
86 warn_duplicate_column_in_select: bool,
87}
88
89impl TypeOptions {
90 pub fn new() -> Self {
92 Default::default()
93 }
94
95 pub fn dialect(self, dialect: SQLDialect) -> Self {
97 Self {
98 parse_options: self.parse_options.dialect(dialect),
99 ..self
100 }
101 }
102
103 pub fn arguments(self, arguments: SQLArguments) -> Self {
105 Self {
106 parse_options: self.parse_options.arguments(arguments),
107 ..self
108 }
109 }
110
111 pub fn warn_unquoted_identifiers(self, warn_unquoted_identifiers: bool) -> Self {
113 Self {
114 parse_options: self
115 .parse_options
116 .warn_unquoted_identifiers(warn_unquoted_identifiers),
117 ..self
118 }
119 }
120
121 pub fn warn_none_capital_keywords(self, warn_none_capital_keywords: bool) -> Self {
123 Self {
124 parse_options: self
125 .parse_options
126 .warn_none_capital_keywords(warn_none_capital_keywords),
127 ..self
128 }
129 }
130
131 pub fn warn_unnamed_column_in_select(self, warn_unnamed_column_in_select: bool) -> Self {
133 Self {
134 warn_unnamed_column_in_select,
135 ..self
136 }
137 }
138
139 pub fn warn_duplicate_column_in_select(self, warn_duplicate_column_in_select: bool) -> Self {
141 Self {
142 warn_duplicate_column_in_select,
143 ..self
144 }
145 }
146
147 pub fn list_hack(self, list_hack: bool) -> Self {
149 Self {
150 parse_options: self.parse_options.list_hack(list_hack),
151 ..self
152 }
153 }
154}
155
156#[derive(Debug, Clone, Hash, PartialEq, Eq)]
158pub enum ArgumentKey<'a> {
159 Index(usize),
161 Identifier(&'a str),
163}
164
165#[derive(Debug, Clone)]
167pub enum StatementType<'a> {
168 Select {
170 columns: Vec<SelectTypeColumn<'a>>,
172 arguments: Vec<(ArgumentKey<'a>, FullType<'a>)>,
174 },
175 Delete {
177 arguments: Vec<(ArgumentKey<'a>, FullType<'a>)>,
179 returning: Option<Vec<SelectTypeColumn<'a>>>,
181 },
182 Insert {
184 yield_autoincrement: AutoIncrementId,
186 arguments: Vec<(ArgumentKey<'a>, FullType<'a>)>,
188 returning: Option<Vec<SelectTypeColumn<'a>>>,
190 },
191 Update {
193 arguments: Vec<(ArgumentKey<'a>, FullType<'a>)>,
195 returning: Option<Vec<SelectTypeColumn<'a>>>,
197 },
198 Replace {
200 arguments: Vec<(ArgumentKey<'a>, FullType<'a>)>,
202 returning: Option<Vec<SelectTypeColumn<'a>>>,
204 },
205 Truncate,
207 Call {
209 arguments: Vec<(ArgumentKey<'a>, FullType<'a>)>,
211 },
212 Transaction,
214 Set,
216 Lock,
218 Invalid,
220}
221
222pub fn type_statement<'a>(
224 schemas: &'a Schemas<'a>,
225 statement: &'a str,
226 issues: &mut Issues<'a>,
227 options: &TypeOptions,
228) -> StatementType<'a> {
229 if let Some(stmt) = parse_statement(statement, issues, &options.parse_options) {
230 let mut typer = Typer {
231 schemas,
232 issues,
233 reference_types: Vec::new(),
234 outer_reference_types: Vec::new(),
235 arg_types: Default::default(),
236 options,
237 with_schemas: Default::default(),
238 };
239 let t = type_statement::type_statement(&mut typer, &stmt);
240 let arguments = typer.arg_types;
241 match t {
242 type_statement::InnerStatementType::Select(s) => StatementType::Select {
243 columns: s.columns,
244 arguments,
245 },
246 type_statement::InnerStatementType::Delete { returning } => StatementType::Delete {
247 arguments,
248 returning: returning.map(|r| r.columns),
249 },
250 type_statement::InnerStatementType::Insert {
251 auto_increment_id,
252 returning,
253 } => StatementType::Insert {
254 yield_autoincrement: auto_increment_id,
255 arguments,
256 returning: returning.map(|r| r.columns),
257 },
258 type_statement::InnerStatementType::Update { returning } => StatementType::Update {
259 arguments,
260 returning: returning.map(|r| r.columns),
261 },
262 type_statement::InnerStatementType::Replace { returning } => StatementType::Replace {
263 arguments,
264 returning: returning.map(|r| r.columns),
265 },
266 type_statement::InnerStatementType::Truncate => StatementType::Truncate,
267 type_statement::InnerStatementType::Call => StatementType::Call { arguments },
268 type_statement::InnerStatementType::Transaction => StatementType::Transaction,
269 type_statement::InnerStatementType::Set => StatementType::Set,
270 type_statement::InnerStatementType::Lock => StatementType::Lock,
271 type_statement::InnerStatementType::Invalid => StatementType::Invalid,
272 }
273 } else {
274 StatementType::Invalid
275 }
276}
277
278#[cfg(test)]
279mod tests {
280 use alloc::vec::Vec;
281 use codespan_reporting::{
282 diagnostic::{Diagnostic, Label},
283 files::SimpleFiles,
284 term::{
285 self,
286 termcolor::{ColorChoice, StandardStream},
287 },
288 };
289 use qusql_parse::{Identifier, Issue, Issues, Level, SQLArguments, SQLDialect};
290
291 use crate::{
292 ArgumentKey, AutoIncrementId, BaseType, FullType, SelectTypeColumn, StatementType, Type,
293 TypeOptions, schema::parse_schemas, type_statement,
294 };
295
296 struct N<'a>(Option<&'a str>);
297 impl<'a> alloc::fmt::Display for N<'a> {
298 fn fmt(&self, f: &mut alloc::fmt::Formatter<'_>) -> alloc::fmt::Result {
299 if let Some(v) = self.0 {
300 v.fmt(f)
301 } else {
302 f.write_str("None")
303 }
304 }
305 }
306
307 struct N2<'a>(Option<Identifier<'a>>);
308 impl<'a> alloc::fmt::Display for N2<'a> {
309 fn fmt(&self, f: &mut alloc::fmt::Formatter<'_>) -> alloc::fmt::Result {
310 if let Some(v) = &self.0 {
311 v.fmt(f)
312 } else {
313 f.write_str("None")
314 }
315 }
316 }
317
318 fn check_no_errors(name: &str, src: &str, issues: &[Issue], errors: &mut usize) {
319 let mut files = SimpleFiles::new();
320 let file_id = files.add(name, &src);
321 let writer = StandardStream::stderr(ColorChoice::Always);
322 let config = codespan_reporting::term::Config::default();
323 for issue in issues {
324 let mut labels = vec![Label::primary(file_id, issue.span.clone())];
325 for fragment in &issue.fragments {
326 labels.push(
327 Label::secondary(file_id, fragment.span.clone())
328 .with_message(fragment.message.to_string()),
329 );
330 }
331 let d = match issue.level {
332 Level::Error => {
333 *errors += 1;
334 Diagnostic::error()
335 }
336 Level::Warning => Diagnostic::warning(),
337 };
338 let d = d
339 .with_message(issue.message.to_string())
340 .with_labels(labels);
341 term::emit_to_write_style(&mut writer.lock(), &config, &files, &d).unwrap();
342 }
343 }
344
345 fn str_to_type(t: &str) -> FullType<'static> {
346 let (t, not_null) = if let Some(t) = t.strip_suffix('!') {
347 (t, true)
348 } else {
349 (t, false)
350 };
351 let (t, list_hack) = if let Some(v) = t.strip_suffix("[]") {
352 (v, true)
353 } else {
354 (t, false)
355 };
356 let t = match t {
357 "b" => BaseType::Bool.into(),
358 "u8" => Type::U8,
359 "u16" => Type::U16,
360 "u32" => Type::U32,
361 "u64" => Type::U64,
362 "i8" => Type::I8,
363 "i16" => Type::I16,
364 "i32" => Type::I32,
365 "i64" => Type::I64,
366 "f32" => Type::F32,
367 "f64" => Type::F64,
368 "i" => BaseType::Integer.into(),
369 "f" => BaseType::Float.into(),
370 "str" => BaseType::String.into(),
371 "bytes" => BaseType::Bytes.into(),
372 "dt" => BaseType::DateTime.into(),
373 "date" => BaseType::Date.into(),
374 "ts" => BaseType::TimeStamp.into(),
375 "time" => BaseType::Time.into(),
376 "json" => Type::JSON,
377 "any" => BaseType::Any.into(),
378 _ => panic!("Unknown type {t}"),
379 };
380 let mut t = FullType::new(t, not_null);
381 if list_hack {
382 t.list_hack = true;
383 }
384 t
385 }
386
387 fn check_arguments(
388 name: &str,
389 got: &[(ArgumentKey<'_>, FullType<'_>)],
390 expected: &str,
391 errors: &mut usize,
392 ) {
393 if expected.is_empty() {
394 for (cnt, value) in got.iter().enumerate() {
395 println!("{name}: Unexpected argument {cnt} type {value:?}");
396 *errors += 1;
397 }
398 return;
399 }
400 let mut got2 = Vec::new();
401 let inv = FullType::invalid();
402 for (k, v) in got {
403 match k {
404 ArgumentKey::Index(i) => {
405 while got2.len() <= *i {
406 got2.push(&inv);
407 }
408 got2[*i] = v;
409 }
410 ArgumentKey::Identifier(k) => {
411 println!("{name}: Got named argument {k}");
412 *errors += 1;
413 }
414 }
415 }
416 let mut cnt = 0;
417 for (i, t) in expected.split(',').enumerate() {
418 let t = t.trim();
419 let t = str_to_type(t);
420 if let Some(v) = got2.get(i) {
421 if *v != &t {
422 println!("{name}: Expected type {t} for argument {i} got {v}");
423 *errors += 1;
424 }
425 } else {
426 println!("{name}: Expected type {t} for argument {i} got None");
427 *errors += 1;
428 }
429 cnt += 1;
430 }
431 while cnt < got.len() {
432 println!("{}: Unexpected argument {} type {:?}", name, cnt, got[cnt]);
433 cnt += 1;
434 *errors += 1;
435 }
436 }
437
438 fn check_columns(
439 name: &str,
440 src: &str,
441 got: &[SelectTypeColumn<'_>],
442 expected: &str,
443 errors: &mut usize,
444 ) {
445 let mut cnt = 0;
446 for (i, t) in expected.split(',').enumerate() {
447 let t = t.trim();
448 let (cname, t) = t.split_once(":").unwrap();
449 let t = str_to_type(t);
450 let cname = if cname.is_empty() { None } else { Some(cname) };
451 if let Some(v) = got.get(i) {
452 if v.name.as_deref() != cname || v.type_ != t {
453 let mut files = SimpleFiles::new();
454 let file_id = files.add(name, &src);
455 let writer = StandardStream::stderr(ColorChoice::Always);
456 let config = codespan_reporting::term::Config::default();
457 let d = Diagnostic::error()
458 .with_message(format!(
459 "{}: Expected column {} with name {} of type {} got {} of type {}",
460 name,
461 i,
462 N(cname),
463 t,
464 N2(v.name.clone()),
465 v.type_
466 ))
467 .with_label(Label::primary(file_id, v.span.clone()));
468
469 term::emit_to_write_style(&mut writer.lock(), &config, &files, &d).unwrap();
470
471 *errors += 1;
472 }
473 } else {
474 let mut files = SimpleFiles::new();
475 let file_id = files.add(name, &src);
476 let writer = StandardStream::stderr(ColorChoice::Always);
477 let config = codespan_reporting::term::Config::default();
478 let d = Diagnostic::error()
479 .with_message(format!(
480 "{}: Expected column {} with name {} of type {} got None",
481 name,
482 i,
483 N(cname),
484 t
485 ))
486 .with_label(Label::primary(file_id, 0..src.len()));
487 term::emit_to_write_style(&mut writer.lock(), &config, &files, &d).unwrap();
488 *errors += 1;
489 }
490 cnt += 1;
491 }
492 while cnt < got.len() {
493 println!(
494 "{}: Unexpected column {} with name {} of type {}",
495 name,
496 cnt,
497 N2(got[cnt].name.clone()),
498 got[cnt].type_
499 );
500 cnt += 1;
501 *errors += 1;
502 }
503 }
504
505 #[test]
506 fn mariadb() {
507 let schema_src = "
508
509 DROP TABLE IF EXISTS `t1`;
510 CREATE TABLE `t1` (
511 `id` int(11) NOT NULL,
512 `cbool` tinyint(1) NOT NULL,
513 `cu8` tinyint UNSIGNED NOT NULL,
514 `cu16` smallint UNSIGNED NOT NULL,
515 `cu32` int UNSIGNED NOT NULL,
516 `cu64` bigint UNSIGNED NOT NULL,
517 `ci8` tinyint,
518 `ci16` smallint,
519 `ci32` int,
520 `ci64` bigint,
521 `cbin` binary(16),
522 `ctext` varchar(100) NOT NULL,
523 `cbytes` blob,
524 `cf32` float,
525 `cf64` double,
526 `cu8_plus_one` tinyint UNSIGNED GENERATED ALWAYS AS (
527 `cu8` + 1
528 ) STORED,
529 `status` varchar(10) GENERATED ALWAYS AS (case when `cu8` <> 0 and `cu16` = 0 then 'a' when
530 `cbool` then 'b' when `ci32` = 42 then 'd' when `cu64` = 43 then 'x' when
531 `ci64` = 12 then 'y' else 'z' end) VIRTUAL
532 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
533
534 ALTER TABLE `t1`
535 MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
536
537 DROP INDEX IF EXISTS `hat` ON `t1`;
538
539 CREATE INDEX `hat2` ON `t1` (`id`, `cf64`);
540
541 CREATE TABLE `t2` (
542 `id` int(11) NOT NULL AUTO_INCREMENT,
543 `t1_id` int(11) NOT NULL);
544
545 CREATE TABLE `t3` (
546 `id` int(11) NOT NULL AUTO_INCREMENT,
547 `text` TEXT);
548
549 CREATE TABLE `t4` (
550 `id` int(11) NOT NULL AUTO_INCREMENT,
551 `dt` datetime NOT NULL);
552
553 CREATE TABLE `t5` (
554 `id` int(11) NOT NULL AUTO_INCREMENT,
555 `a` int NOT NULL,
556 `b` int,
557 `c` int NOT NULL DEFAULT 42);
558
559 CREATE TABLE `t6` (
560 `id` int(11) NOT NULL AUTO_INCREMENT,
561 `d` date NOT NULL,
562 `dt` datetime NOT NULL,
563 `t` time NOT NULL);
564
565 CREATE PROCEDURE `p1`(IN `a` int, IN `b` varchar(100))
566 BEGIN
567 END;
568 ";
569
570 let options = TypeOptions::new().dialect(SQLDialect::MariaDB);
571 let mut issues = Issues::new(schema_src);
572 let schema = parse_schemas(schema_src, &mut issues, &options);
573 let mut errors = 0;
574 check_no_errors("schema", schema_src, issues.get(), &mut errors);
575
576 let options = TypeOptions::new()
577 .dialect(SQLDialect::MariaDB)
578 .arguments(SQLArguments::QuestionMark);
579
580 {
581 let name = "q1";
582 let src =
583 "SELECT `id`, `cbool`, `cu8`, `cu8_plus_one`, `cu16`, `cu32`, `cu64`, `ci8`, `ci16`, `ci32`, `ci64`,
584 `ctext`, `cbytes`, `cf32`, `cf64` FROM `t1` WHERE ci8 IS NOT NULL
585 AND `cbool`=? AND `cu8`=? AND `cu16`=? AND `cu32`=? AND `cu64`=?
586 AND `ci8`=? AND `ci16`=? AND `ci32`=? AND `ci64`=?
587 AND `ctext`=? AND `cbytes`=? AND `cf32`=? AND `cf64`=?";
588
589 let mut issues: Issues<'_> = Issues::new(src);
590 let q = type_statement(&schema, src, &mut issues, &options);
591 check_no_errors(name, src, issues.get(), &mut errors);
592 if let StatementType::Select { arguments, columns } = q {
593 check_arguments(
594 name,
595 &arguments,
596 "b,i,i,i,i,i,i,i,i,str,bytes,f,f",
597 &mut errors,
598 );
599 check_columns(
600 name,
601 src,
602 &columns,
603 "id:i32!,cbool:b!,cu8:u8!,cu8_plus_one:u8!,cu16:u16!,cu32:u32!,cu64:u64!,
604 ci8:i8!,ci16:i16!,ci32:i32!,ci64:i64!,ctext:str!,cbytes:bytes!,cf32:f32!,cf64:f64!",
605 &mut errors,
606 );
607 } else {
608 println!("{name} should be select");
609 errors += 1;
610 }
611 }
612
613 {
614 let name = "q1.1";
615 let src =
616 "SELECT `id`, `cbool`, `cu8`, `cu16`, `cu32`, `cu64`, `ci8`, `ci16`, `ci32`, `ci64`,
617 `ctext`, `cbytes`, `cf32`, `cf64`, `cbin` FROM `t1` WHERE ci8 IS NOT NULL";
618
619 let mut issues: Issues<'_> = Issues::new(src);
620 let q = type_statement(&schema, src, &mut issues, &options);
621 check_no_errors(name, src, issues.get(), &mut errors);
622 if let StatementType::Select { arguments, columns } = q {
623 check_arguments(name, &arguments, "", &mut errors);
624 check_columns(
625 name,
626 src,
627 &columns,
628 "id:i32!,cbool:b!,cu8:u8!,cu16:u16!,cu32:u32!,cu64:u64!,
629 ci8:i8!,ci16:i16,ci32:i32,ci64:i64,ctext:str!,cbytes:bytes,cf32:f32,cf64:f64,cbin:bytes",
630 &mut errors,
631 );
632 } else {
633 println!("{name} should be select");
634 errors += 1;
635 }
636 }
637
638 {
639 let name = "q2";
640 let src =
641 "INSERT INTO `t1` (`cbool`, `cu8`, `cu16`, `cu32`, `cu64`, `ci8`, `ci16`, `ci32`, `ci64`,
642 `ctext`, `cbytes`, `cf32`, `cf64`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
643
644 let mut issues: Issues<'_> = Issues::new(src);
645 let q = type_statement(&schema, src, &mut issues, &options);
646 check_no_errors(name, src, issues.get(), &mut errors);
647 if let StatementType::Insert {
648 arguments,
649 yield_autoincrement,
650 returning,
651 } = q
652 {
653 check_arguments(
654 name,
655 &arguments,
656 "b!,u8!,u16!,u32!,u64!,i8,i16,i32,i64,str!,bytes,f32,f64",
657 &mut errors,
658 );
659 if yield_autoincrement != AutoIncrementId::Yes {
660 println!("{name} should yield autoincrement");
661 errors += 1;
662 }
663 if returning.is_some() {
664 println!("{name} should not return columns");
665 errors += 1;
666 }
667 } else {
668 println!("{name} should be insert");
669 errors += 1;
670 }
671 }
672
673 {
674 let name = "q3";
675 let src =
676 "DELETE `t1` FROM `t1`, `t2` WHERE `t1`.`id` = `t2`.`t1_id` AND `t2`.`id` = ?";
677 let mut issues: Issues<'_> = Issues::new(src);
678 let q = type_statement(&schema, src, &mut issues, &options);
679 check_no_errors(name, src, issues.get(), &mut errors);
680 if let StatementType::Delete { arguments, .. } = q {
681 check_arguments(name, &arguments, "i", &mut errors);
682 } else {
683 println!("{name} should be delete");
684 errors += 1;
685 }
686 }
687
688 {
689 let name = "q4";
690 let src = "INSERT INTO `t2` (`t1_id`) VALUES (?) ON DUPLICATE KEY UPDATE `t1_id`=?";
691 let mut issues: Issues<'_> = Issues::new(src);
692 let q = type_statement(&schema, src, &mut issues, &options);
693 check_no_errors(name, src, issues.get(), &mut errors);
694 if let StatementType::Insert {
695 arguments,
696 yield_autoincrement,
697 returning,
698 } = q
699 {
700 check_arguments(name, &arguments, "i32!,i32!", &mut errors);
701 if yield_autoincrement != AutoIncrementId::Optional {
702 println!("{name} should yield optional auto increment");
703 errors += 1;
704 }
705 if returning.is_some() {
706 println!("{name} should not return columns");
707 errors += 1;
708 }
709 } else {
710 println!("{name} should be insert");
711 errors += 1;
712 }
713 }
714
715 {
716 let name = "q5";
717 let src = "INSERT IGNORE INTO `t2` SET `t1_id`=?";
718 let mut issues: Issues<'_> = Issues::new(src);
719 let q = type_statement(&schema, src, &mut issues, &options);
720 check_no_errors(name, src, issues.get(), &mut errors);
721 if let StatementType::Insert {
722 arguments,
723 yield_autoincrement,
724 returning,
725 } = q
726 {
727 check_arguments(name, &arguments, "i32!", &mut errors);
728 if yield_autoincrement != AutoIncrementId::Optional {
729 println!("{name} should yield optional auto increment");
730 errors += 1;
731 }
732 if returning.is_some() {
733 println!("{name} should not return columns");
734 errors += 1;
735 }
736 } else {
737 println!("{name} should be insert");
738 errors += 1;
739 }
740 }
741
742 {
743 let name = "q6";
744 let src = "SELECT IF(`ci32` IS NULL, `cbool`, ?) AS `cc` FROM `t1`";
745 let mut issues: Issues<'_> = Issues::new(src);
746 let q = type_statement(&schema, src, &mut issues, &options);
747 check_no_errors(name, src, issues.get(), &mut errors);
748 if let StatementType::Select { arguments, columns } = q {
749 check_arguments(name, &arguments, "b", &mut errors);
750 check_columns(name, src, &columns, "cc:b", &mut errors);
751 } else {
752 println!("{name} should be select");
753 errors += 1;
754 }
755 }
756
757 {
758 let name = "q7";
759 let src = "SELECT FROM_UNIXTIME(CAST(UNIX_TIMESTAMP() AS DOUBLE)) AS `cc` FROM `t1` WHERE `id`=?";
760 let mut issues: Issues<'_> = Issues::new(src);
761 let q = type_statement(&schema, src, &mut issues, &options);
762 check_no_errors(name, src, issues.get(), &mut errors);
763 if let StatementType::Select { arguments, columns } = q {
764 check_arguments(name, &arguments, "i", &mut errors);
765 check_columns(name, src, &columns, "cc:dt!", &mut errors);
766 } else {
767 println!("{name} should be select");
768 errors += 1;
769 }
770 }
771
772 {
773 let name = "q8";
774 let src = "REPLACE INTO `t2` SET `id` = ?, `t1_id`=?";
775 let mut issues: Issues<'_> = Issues::new(src);
776 let q = type_statement(&schema, src, &mut issues, &options);
777 check_no_errors(name, src, issues.get(), &mut errors);
778 if let StatementType::Replace {
779 arguments,
780 returning,
781 } = q
782 {
783 check_arguments(name, &arguments, "i32!,i32!", &mut errors);
784 if returning.is_some() {
785 println!("{name} should not return columns");
786 errors += 1;
787 }
788 } else {
789 println!("{name} should be replace");
790 errors += 1;
791 }
792 }
793
794 {
795 let name = "q9";
796 let src = "INSERT INTO `t2` (`t1_id`) VALUES (32) ON DUPLICATE KEY UPDATE `t1_id` = `t1_id` + VALUES(`t1_id`)";
797 let mut issues: Issues<'_> = Issues::new(src);
798 let q = type_statement(&schema, src, &mut issues, &options);
799 check_no_errors(name, src, issues.get(), &mut errors);
800 if let StatementType::Insert { arguments, .. } = q {
801 check_arguments(name, &arguments, "", &mut errors);
802 } else {
803 println!("{name} should be insert");
804 errors += 1;
805 }
806 }
807
808 {
809 let name = "q10";
810 let src =
811 "SELECT SUBSTRING_INDEX(`text`, '/', 5) AS `k` FROM `t3` WHERE `text` LIKE '%T%'";
812 let mut issues: Issues<'_> = Issues::new(src);
813 let q = type_statement(&schema, src, &mut issues, &options);
814 check_no_errors(name, src, issues.get(), &mut errors);
815 if let StatementType::Select { arguments, columns } = q {
816 check_arguments(name, &arguments, "", &mut errors);
817 check_columns(name, src, &columns, "k:str!", &mut errors);
818 } else {
819 println!("{name} should be select");
820 errors += 1;
821 }
822 }
823
824 {
825 let name = "q11";
826 let src = "SELECT * FROM `t1`, `t2` LEFT JOIN `t3` ON `t3`.`id` = `t1`.`id`";
827 let mut issues: Issues<'_> = Issues::new(src);
828 type_statement(&schema, src, &mut issues, &options);
829 if !issues.get().iter().any(|i| i.level == Level::Error) {
830 println!("{name} should be an error");
831 errors += 1;
832 }
833 }
834
835 {
836 let name = "q12";
837 let src = "SELECT JSON_REPLACE('{ \"A\": 1, \"B\": [2, 3]}', '$.B[1]', 4, '$.C[3]', 3) AS `k` FROM `t3`";
838 let mut issues: Issues<'_> = Issues::new(src);
839 let q = type_statement(&schema, src, &mut issues, &options);
840 check_no_errors(name, src, issues.get(), &mut errors);
841 if let StatementType::Select { arguments, columns } = q {
842 check_arguments(name, &arguments, "", &mut errors);
843 check_columns(name, src, &columns, "k:json", &mut errors);
844 } else {
845 println!("{name} should be select");
846 errors += 1;
847 }
848 }
849
850 {
851 let options = options.clone().list_hack(true);
852 let name = "q13";
853 let src = "SELECT `id` FROM `t1` WHERE `id` IN (_LIST_)";
854 let mut issues: Issues<'_> = Issues::new(src);
855 let q = type_statement(&schema, src, &mut issues, &options);
856 check_no_errors(name, src, issues.get(), &mut errors);
857 if let StatementType::Select { arguments, columns } = q {
858 check_arguments(name, &arguments, "i[]", &mut errors);
859 check_columns(name, src, &columns, "id:i32!", &mut errors);
860 } else {
861 println!("{name} should be select");
862 errors += 1;
863 }
864 }
865
866 {
867 let name = "q14";
868 let src = "SELECT CAST(NULL AS CHAR) AS `id`";
869 let mut issues: Issues<'_> = Issues::new(src);
870 let q = type_statement(&schema, src, &mut issues, &options);
871 check_no_errors(name, src, issues.get(), &mut errors);
872 if let StatementType::Select { arguments, columns } = q {
873 check_arguments(name, &arguments, "", &mut errors);
874 check_columns(name, src, &columns, "id:str", &mut errors);
875 } else {
876 println!("{name} should be select");
877 errors += 1;
878 }
879 }
880
881 {
882 let name = "q15";
883 let src =
884 "INSERT INTO `t1` (`cbool`, `cu8`, `cu16`, `cu32`, `cu64`, `ci8`, `ci16`, `ci32`, `ci64`,
885 `ctext`, `cbytes`, `cf32`, `cf64`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
886 RETURNING `id`, `cbool`, `cu8`, `ctext`, `cf64`";
887 let mut issues: Issues<'_> = Issues::new(src);
888 let q = type_statement(&schema, src, &mut issues, &options);
889 check_no_errors(name, src, issues.get(), &mut errors);
890 if let StatementType::Insert {
891 arguments,
892 yield_autoincrement,
893 returning,
894 } = q
895 {
896 check_arguments(
897 name,
898 &arguments,
899 "b!,u8!,u16!,u32!,u64!,i8,i16,i32,i64,str!,bytes,f32,f64",
900 &mut errors,
901 );
902 if yield_autoincrement != AutoIncrementId::Yes {
903 println!("{name} should yield autoincrement");
904 errors += 1;
905 }
906 if let Some(returning) = returning {
907 check_columns(
908 name,
909 src,
910 &returning,
911 "id:i32!,cbool:b!,cu8:u8!,ctext:str!,cf64:f64",
912 &mut errors,
913 );
914 } else {
915 println!("{name} should return columns");
916 errors += 1;
917 }
918 } else {
919 println!("{name} should be insert");
920 errors += 1;
921 }
922 }
923
924 {
925 let name = "q16";
926 let src = "REPLACE INTO `t2` SET `id` = ?, `t1_id`=? RETURNING `id`";
927 let mut issues: Issues<'_> = Issues::new(src);
928 let q = type_statement(&schema, src, &mut issues, &options);
929 check_no_errors(name, src, issues.get(), &mut errors);
930 if let StatementType::Replace {
931 arguments,
932 returning,
933 } = q
934 {
935 check_arguments(name, &arguments, "i32!,i32!", &mut errors);
936 if let Some(returning) = returning {
937 check_columns(name, src, &returning, "id:i32!", &mut errors);
938 } else {
939 println!("{name} should return columns");
940 errors += 1;
941 }
942 } else {
943 println!("{name} should be replace");
944 errors += 1;
945 }
946 }
947
948 {
949 let name = "q17";
950 let src = "SELECT dt, UNIX_TIMESTAMP(dt) AS t FROM t4";
951 let mut issues: Issues<'_> = Issues::new(src);
952 let q = type_statement(&schema, src, &mut issues, &options);
953 check_no_errors(name, src, issues.get(), &mut errors);
954 if let StatementType::Select { arguments, columns } = q {
955 check_arguments(name, &arguments, "", &mut errors);
956 check_columns(name, src, &columns, "dt:dt!,t:i64!", &mut errors);
957 } else {
958 println!("{name} should be select");
959 errors += 1;
960 }
961 }
962
963 {
964 let name = "q17";
965 let src = "SELECT CONCAT(?, \"hat\") AS c";
966 let mut issues: Issues<'_> = Issues::new(src);
967 let q = type_statement(&schema, src, &mut issues, &options);
968 check_no_errors(name, src, issues.get(), &mut errors);
969 if let StatementType::Select { arguments, columns } = q {
970 check_arguments(name, &arguments, "any", &mut errors);
971 check_columns(name, src, &columns, "c:str", &mut errors);
972 } else {
973 println!("{name} should be selsect");
974 errors += 1;
975 }
976 }
977
978 {
979 let name = "q18";
980 let src = "SELECT CAST(\"::0\" AS INET6) AS `id`";
981 let mut issues: Issues<'_> = Issues::new(src);
982 let q = type_statement(&schema, src, &mut issues, &options);
983 check_no_errors(name, src, issues.get(), &mut errors);
984 if let StatementType::Select { arguments, columns } = q {
985 check_arguments(name, &arguments, "", &mut errors);
986 check_columns(name, src, &columns, "id:str!", &mut errors);
987 } else {
988 println!("{name} should be select");
989 errors += 1;
990 }
991 }
992
993 {
994 let name: &str = "q18";
995 let src = "SELECT SUBSTRING(`cbytes`, 1, 5) AS `k` FROM `t1`";
996 let mut issues: Issues<'_> = Issues::new(src);
997 let q = type_statement(&schema, src, &mut issues, &options);
998 check_no_errors(name, src, issues.get(), &mut errors);
999 if let StatementType::Select { arguments, columns } = q {
1000 check_arguments(name, &arguments, "", &mut errors);
1001 check_columns(name, src, &columns, "k:bytes", &mut errors);
1002 } else {
1003 println!("{name} should be select");
1004 errors += 1;
1005 }
1006 }
1007
1008 {
1009 let name = "q19";
1010 let src = "SELECT SUBSTRING(`ctext`, 1, 5) AS `k` FROM `t1`";
1011 let mut issues: Issues<'_> = Issues::new(src);
1012 let q = type_statement(&schema, src, &mut issues, &options);
1013 check_no_errors(name, src, issues.get(), &mut errors);
1014 if let StatementType::Select { arguments, columns } = q {
1015 check_arguments(name, &arguments, "", &mut errors);
1016 check_columns(name, src, &columns, "k:str!", &mut errors);
1017 } else {
1018 println!("{name} should be select");
1019 errors += 1;
1020 }
1021 }
1022
1023 {
1024 let name = "q19";
1025 let src = "SELECT SUBSTRING(`ctext`, 1, 5) AS `k` FROM `t1`";
1026 let mut issues: Issues<'_> = Issues::new(src);
1027 let q = type_statement(&schema, src, &mut issues, &options);
1028 check_no_errors(name, src, issues.get(), &mut errors);
1029 if let StatementType::Select { arguments, columns } = q {
1030 check_arguments(name, &arguments, "", &mut errors);
1031 check_columns(name, src, &columns, "k:str!", &mut errors);
1032 } else {
1033 println!("{name} should be select");
1034 errors += 1;
1035 }
1036 }
1037
1038 {
1039 let name = "q20";
1040 let src = "SELECT JSON_QUERY('{ \"A\": 1, \"B\": [2, 3]}', '$.B[1]') AS `k` FROM `t3`";
1041 let mut issues: Issues<'_> = Issues::new(src);
1042 let q = type_statement(&schema, src, &mut issues, &options);
1043 check_no_errors(name, src, issues.get(), &mut errors);
1044 if let StatementType::Select { arguments, columns } = q {
1045 check_arguments(name, &arguments, "", &mut errors);
1046 check_columns(name, src, &columns, "k:json", &mut errors);
1047 } else {
1048 println!("{name} should be select");
1049 errors += 1;
1050 }
1051 }
1052
1053 {
1054 let name = "q21";
1055 let src = "SELECT JSON_REMOVE('{ \"A\": 1, \"B\": [2, 3]}', '$.B[1]', '$.C[3]') AS `k` FROM `t3`";
1056 let mut issues: Issues<'_> = Issues::new(src);
1057 let q = type_statement(&schema, src, &mut issues, &options);
1058 check_no_errors(name, src, issues.get(), &mut errors);
1059 if let StatementType::Select { arguments, columns } = q {
1060 check_arguments(name, &arguments, "", &mut errors);
1061 check_columns(name, src, &columns, "k:json", &mut errors);
1062 } else {
1063 println!("{name} should be select");
1064 errors += 1;
1065 }
1066 }
1067
1068 {
1069 let name = "q22";
1070 let src = "SELECT JSON_OVERLAPS('false', 'false') AS `k` FROM `t3`";
1071 let mut issues: Issues<'_> = Issues::new(src);
1072 let q = type_statement(&schema, src, &mut issues, &options);
1073 check_no_errors(name, src, issues.get(), &mut errors);
1074 if let StatementType::Select { arguments, columns } = q {
1075 check_arguments(name, &arguments, "", &mut errors);
1076 check_columns(name, src, &columns, "k:b!", &mut errors);
1077 } else {
1078 println!("{name} should be select");
1079 errors += 1;
1080 }
1081 }
1082
1083 {
1084 let name = "q23";
1085 let src = "SELECT JSON_OVERLAPS('false', NULL) AS `k` FROM `t3`";
1086 let mut issues: Issues<'_> = Issues::new(src);
1087 let q = type_statement(&schema, src, &mut issues, &options);
1088 check_no_errors(name, src, issues.get(), &mut errors);
1089 if let StatementType::Select { arguments, columns } = q {
1090 check_arguments(name, &arguments, "", &mut errors);
1091 check_columns(name, src, &columns, "k:b", &mut errors);
1092 } else {
1093 println!("{name} should be select");
1094 errors += 1;
1095 }
1096 }
1097
1098 {
1099 let name = "q24";
1100 let src = "SELECT JSON_CONTAINS('{\"A\": 0, \"B\": [\"x\", \"y\"]}', '\"x\"', '$.B') AS `k` FROM `t3`";
1101 let mut issues: Issues<'_> = Issues::new(src);
1102 let q = type_statement(&schema, src, &mut issues, &options);
1103 check_no_errors(name, src, issues.get(), &mut errors);
1104 if let StatementType::Select { arguments, columns } = q {
1105 check_arguments(name, &arguments, "", &mut errors);
1106 check_columns(name, src, &columns, "k:b!", &mut errors);
1107 } else {
1108 println!("{name} should be select");
1109 errors += 1;
1110 }
1111 }
1112
1113 {
1114 let name = "q25";
1115 let src = "SELECT JSON_CONTAINS('{\"A\": 0, \"B\": [\"x\", \"y\"]}', NULL, '$.A') AS `k` FROM `t3`";
1116 let mut issues: Issues<'_> = Issues::new(src);
1117 let q = type_statement(&schema, src, &mut issues, &options);
1118 check_no_errors(name, src, issues.get(), &mut errors);
1119 if let StatementType::Select { arguments, columns } = q {
1120 check_arguments(name, &arguments, "", &mut errors);
1121 check_columns(name, src, &columns, "k:b", &mut errors);
1122 } else {
1123 println!("{name} should be select");
1124 errors += 1;
1125 }
1126 }
1127
1128 {
1129 let name = "q26";
1130 let src = "SELECT `id` FROM `t1` FORCE INDEX (`hat`)";
1131 let mut issues: Issues<'_> = Issues::new(src);
1132 type_statement(&schema, src, &mut issues, &options);
1133 if issues.is_ok() {
1134 println!("{name} should fail");
1135 errors += 1;
1136 }
1137 }
1138
1139 {
1140 let name = "q27";
1141 let src = "SELECT `id` FROM `t1` USE INDEX (`hat2`)";
1142 let mut issues: Issues<'_> = Issues::new(src);
1143 let q = type_statement(&schema, src, &mut issues, &options);
1144 check_no_errors(name, src, issues.get(), &mut errors);
1145 if let StatementType::Select { arguments, columns } = q {
1146 check_arguments(name, &arguments, "", &mut errors);
1147 check_columns(name, src, &columns, "id:i32!", &mut errors);
1148 } else {
1149 println!("{name} should be select");
1150 errors += 1;
1151 }
1152 }
1153
1154 {
1155 let name = "q28";
1156 let src = "INSERT INTO t5 (`a`) VALUES (44)";
1157 check_no_errors(name, src, issues.get(), &mut errors);
1158 }
1159
1160 {
1161 let name = "q29";
1162 let src = "INSERT INTO t5 (`a`, `b`, `c`) VALUES (?, ?)";
1163 let mut issues: Issues<'_> = Issues::new(src);
1164 type_statement(&schema, src, &mut issues, &options);
1165 if issues.is_ok() {
1166 println!("{name} should fail");
1167 errors += 1;
1168 }
1169 }
1170
1171 {
1172 let name = "q30";
1173 let src = "INSERT INTO t5 (`a`, `b`, `c`) VALUES (?, ?, ?)";
1174 check_no_errors(name, src, issues.get(), &mut errors);
1175 }
1176
1177 {
1178 let name = "q31";
1179 let src = "INSERT INTO t5 (`a`, `b`, `c`) VALUES (?, ?, ?, ?)";
1180 let mut issues: Issues<'_> = Issues::new(src);
1181 type_statement(&schema, src, &mut issues, &options);
1182 if issues.is_ok() {
1183 println!("{name} should fail");
1184 errors += 1;
1185 }
1186 }
1187
1188 {
1189 let name = "q32";
1190 let src = "INSERT INTO t5 (`b`, `c`) VALUES (44, 45)";
1191 let mut issues: Issues<'_> = Issues::new(src);
1192 type_statement(&schema, src, &mut issues, &options);
1193 if issues.is_ok() {
1194 println!("{name} should fail");
1195 errors += 1;
1196 }
1197 }
1198
1199 {
1200 let mut t = |expr: &str, t: &str| {
1201 let name = format!("q33 {expr}");
1202 let src = format!("SELECT {expr} AS q FROM t6");
1203 let mut issues: Issues<'_> = Issues::new(&src);
1204 let q = type_statement(&schema, &src, &mut issues, &options);
1205 check_no_errors(&name, &src, issues.get(), &mut errors);
1206 if let StatementType::Select { columns, .. } = q {
1207 check_columns(&name, &src, &columns, &format!("q:{t}"), &mut errors);
1208 } else {
1209 println!("{name} should be select");
1210 errors += 1;
1211 }
1212 };
1213 t("ADD_MONTHS(`d`, 2)", "date!");
1214 t("DATE_ADD(`d`, INTERVAL 31 DAY)", "date");
1215 t("ADDDATE(`d`, 31)", "date");
1216 t("CONVERT_TZ(`dt`, '+00:00','+10:00')", "dt!");
1219 t("CURDATE()", "date!");
1221 t("CURDATE() - INTERVAL 5 DAY", "date!");
1222 t("CURTIME()", "time!");
1224 t("CURTIME()", "time!");
1225 t("DATE('2013-07-18 12:21:32')", "date!");
1226 t("`dt` + INTERVAL 1 SECOND", "dt!");
1227 t("INTERVAL 1 DAY + `d`", "date!");
1228 t("DATE_ADD(`dt`, INTERVAL 1 SECOND)", "dt");
1229 t("DATE_ADD(`dt`, INTERVAL '1:1' MINUTE_SECOND)", "dt");
1230 t("DATE_FORMAT(`dt`, '%D %y %a %d %m %b %j')", "str!");
1231 t("DATE_SUB(`d`, INTERVAL 31 DAY)", "date");
1232 t("DATE_SUB(`dt`, INTERVAL '1 1:1:1' DAY_SECOND)", "dt");
1233 t("DATEDIFF(`dt`, `d`)", "i!");
1234 t("DAYNAME(`d`)", "str!");
1235 t("DAYOFMONTH(`d`)", "i!");
1236 t("DAYOFWEEK(`d`)", "i!");
1237 t("DAYOFYEAR(`d`)", "i!");
1238 t("EXTRACT(DAY_MINUTE FROM `dt`)", "i!");
1239 t("FROM_DAYS(730669)", "date!");
1240 t("FROM_UNIXTIME(1196440219.0)", "dt!");
1241 t(
1243 "FROM_UNIXTIME(1196440219.0, '%Y %D %M %h:%i:%s %x')",
1244 "str!",
1245 );
1246 t("HOUR(`t`)", "i!");
1247 t("LAST_DAY('2004-01-01 01:01:01')", "date!");
1248 t("MAKEDATE(2011,31)", "date");
1249 t("MAKETIME(13,57,33)", "time");
1250 t("MICROSECOND(`dt`)", "i!");
1251 t("MINUTE(`dt`)", "i!");
1252 t("MONTH(`d`)", "i!");
1253 t("MONTHNAME(`dt`)", "str!");
1254 t("NOW()", "dt!");
1255 t("PERIOD_ADD(200801,2)", "i!");
1257 t("PERIOD_DIFF(200802,200703)", "i!");
1258 t("QUARTER(`dt`)", "i!");
1259 t("SEC_TO_TIME(12414)", "time!");
1261 t("SECOND(`dt`)", "i!");
1262 t(
1263 "STR_TO_DATE('Wednesday23423, June 2, 2014', '%W, %M %e, %Y')",
1264 "dt!",
1265 );
1266 t("SYSDATE()", "dt!");
1269 t("TIME('2013-07-18 12:21:32')", "time!");
1270 t("TIME_FORMAT(`t`, '%H %k %h %I %l')", "str!");
1271 t("TIME_TO_SEC(`t`)", "f!");
1272 t(
1273 "TIMEDIFF('2000:01:01 00:00:00', '2000:01:01 00:00:00.000001')",
1274 "time!",
1275 );
1276 t("TIMESTAMP('2003-12-31')", "dt!");
1277 t("TIMESTAMP('2003-12-31 12:00:00','6:30:00')", "dt!");
1278 t("TIMESTAMPADD(MINUTE,1,`d`)", "dt!");
1279 t("TIMESTAMPDIFF(MONTH,'2003-02-01','2003-05-01')", "i!");
1280 t("TO_DAYS(`d`)", "i!");
1281 t("TO_SECONDS(`dt`)", "i!");
1282 t("UNIX_TIMESTAMP(`dt`)", "i64!");
1283 t("UNIX_TIMESTAMP()", "i64!");
1284 t("UTC_DATE()", "date!");
1286 t("UTC_TIME()", "time!");
1288 t("UTC_TIMESTAMP()", "dt!");
1290 t("WEEK(`d`)", "i!");
1291 t("WEEK(`d`, 3)", "i!");
1292 t("WEEKDAY(`d`)", "i!");
1293 t("YEAR(`d`)", "i!");
1294 t("YEARWEEK(`d`)", "i!");
1295 t("YEARWEEK(`d`, 3)", "i!");
1296 }
1297 {
1298 let name = "q34";
1299 let src = "TRUNCATE TABLE `t1`";
1300 let mut issues: Issues<'_> = Issues::new(src);
1301 let q = type_statement(&schema, src, &mut issues, &options);
1302 check_no_errors(name, src, issues.get(), &mut errors);
1303 if !matches!(q, StatementType::Truncate) {
1304 println!("{name} should be truncate");
1305 errors += 1;
1306 }
1307 }
1308
1309 {
1310 let name = "q35";
1311 let src = "TRUNCATE TABLE `unknown_table`";
1312 let mut issues: Issues<'_> = Issues::new(src);
1313 type_statement(&schema, src, &mut issues, &options);
1314 if issues.is_ok() {
1315 println!("{name} should fail");
1316 errors += 1;
1317 }
1318 }
1319
1320 {
1321 let name = "q36";
1322 let src = "CALL p1(42, 'hello')";
1323 let mut issues: Issues<'_> = Issues::new(src);
1324 let q = type_statement(&schema, src, &mut issues, &options);
1325 check_no_errors(name, src, issues.get(), &mut errors);
1326 if !matches!(q, StatementType::Call { .. }) {
1327 println!("{name} should be call");
1328 errors += 1;
1329 }
1330 }
1331
1332 {
1333 let name = "q37";
1334 let src = "CALL p1(42)";
1335 let mut issues: Issues<'_> = Issues::new(src);
1336 type_statement(&schema, src, &mut issues, &options);
1337 if issues.is_ok() {
1338 println!("{name} should fail (wrong arg count)");
1339 errors += 1;
1340 }
1341 }
1342
1343 {
1344 let name = "q38";
1345 let src = "CALL unknown_proc(1)";
1346 let mut issues: Issues<'_> = Issues::new(src);
1347 type_statement(&schema, src, &mut issues, &options);
1348 if issues.is_ok() {
1349 println!("{name} should fail (unknown procedure)");
1350 errors += 1;
1351 }
1352 }
1353
1354 {
1355 let name = "q39";
1356 let src = "BEGIN";
1357 let mut issues: Issues<'_> = Issues::new(src);
1358 let q = type_statement(&schema, src, &mut issues, &options);
1359 check_no_errors(name, src, issues.get(), &mut errors);
1360 if !matches!(q, StatementType::Transaction) {
1361 println!("{name} should be transaction");
1362 errors += 1;
1363 }
1364 }
1365
1366 {
1367 let name = "q40";
1368 let src = "COMMIT";
1369 let mut issues: Issues<'_> = Issues::new(src);
1370 let q = type_statement(&schema, src, &mut issues, &options);
1371 check_no_errors(name, src, issues.get(), &mut errors);
1372 if !matches!(q, StatementType::Transaction) {
1373 println!("{name} should be transaction");
1374 errors += 1;
1375 }
1376 }
1377
1378 {
1379 let name = "q41";
1380 let src = "SET @var = 42";
1381 let mut issues: Issues<'_> = Issues::new(src);
1382 let q = type_statement(&schema, src, &mut issues, &options);
1383 check_no_errors(name, src, issues.get(), &mut errors);
1384 if !matches!(q, StatementType::Set) {
1385 println!("{name} should be set");
1386 errors += 1;
1387 }
1388 }
1389
1390 {
1391 let name = "q42";
1392 let src = "SET @@session.time_zone = 'UTC'";
1393 let mut issues: Issues<'_> = Issues::new(src);
1394 let q = type_statement(&schema, src, &mut issues, &options);
1395 check_no_errors(name, src, issues.get(), &mut errors);
1396 if !matches!(q, StatementType::Set) {
1397 println!("{name} should be set");
1398 errors += 1;
1399 }
1400 }
1401
1402 {
1403 let name = "q42b";
1404 let src = "SET @@time_zone = '+00:00'";
1405 let mut issues: Issues<'_> = Issues::new(src);
1406 let q = type_statement(&schema, src, &mut issues, &options);
1407 check_no_errors(name, src, issues.get(), &mut errors);
1408 if !matches!(q, StatementType::Set) {
1409 println!("{name} should be set");
1410 errors += 1;
1411 }
1412 }
1413
1414 {
1415 let name = "q43";
1416 let src = "LOCK TABLES `t1` READ";
1417 let mut issues: Issues<'_> = Issues::new(src);
1418 let q = type_statement(&schema, src, &mut issues, &options);
1419 check_no_errors(name, src, issues.get(), &mut errors);
1420 if !matches!(q, StatementType::Lock) {
1421 println!("{name} should be lock");
1422 errors += 1;
1423 }
1424 }
1425
1426 {
1427 let name = "q44";
1428 let src = "LOCK TABLES `unknown_table` WRITE";
1429 let mut issues: Issues<'_> = Issues::new(src);
1430 type_statement(&schema, src, &mut issues, &options);
1431 if issues.is_ok() {
1432 println!("{name} should fail (unknown table)");
1433 errors += 1;
1434 }
1435 }
1436
1437 {
1438 let name = "q45";
1439 let src = "UNLOCK TABLES";
1440 let mut issues: Issues<'_> = Issues::new(src);
1441 let q = type_statement(&schema, src, &mut issues, &options);
1442 check_no_errors(name, src, issues.get(), &mut errors);
1443 if !matches!(q, StatementType::Lock) {
1444 println!("{name} should be lock");
1445 errors += 1;
1446 }
1447 }
1448
1449 if errors != 0 {
1450 panic!("{errors} errors in test");
1451 }
1452 }
1453
1454 #[test]
1455 fn postgresql() {
1456 let schema_src = "
1457 BEGIN;
1458
1459 DO $$ BEGIN
1460 CREATE TYPE my_enum AS ENUM (
1461 'V1',
1462 'V2',
1463 'V3'
1464 );
1465 EXCEPTION
1466 WHEN duplicate_object THEN null;
1467 END $$;
1468
1469 CREATE TABLE IF NOT EXISTS t1 (
1470 id bigint NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
1471 path text NOT NULL UNIQUE,
1472 v my_enum NOT NULL,
1473 time timestamptz NOT NULL DEFAULT now(),
1474 old_id bigint,
1475 CONSTRAINT t1__old
1476 FOREIGN KEY(old_id)
1477 REFERENCES t1(id)
1478 ON DELETE SET NULL
1479 );
1480
1481 CREATE TABLE IF NOT EXISTS t2 (
1482 id bigint NOT NULL PRIMARY KEY
1483 );
1484
1485 DROP INDEX IF EXISTS t2_index;
1486
1487 CREATE INDEX t2_index2 ON t2 (id);
1488
1489 CREATE TABLE IF NOT EXISTS t3 (
1490 a bigint NOT NULL,
1491 b bigint NOT NULL
1492 );
1493
1494 CREATE UNIQUE INDEX t3u ON t3(a,b);
1495
1496 COMMIT;
1497 ";
1498
1499 let options = TypeOptions::new().dialect(SQLDialect::PostgreSQL);
1500 let mut issues = Issues::new(schema_src);
1501 let schema = parse_schemas(schema_src, &mut issues, &options);
1502 let mut errors = 0;
1503 check_no_errors("schema", schema_src, issues.get(), &mut errors);
1504
1505 let options = TypeOptions::new()
1506 .dialect(SQLDialect::PostgreSQL)
1507 .arguments(SQLArguments::Dollar);
1508
1509 {
1510 let name = "q1";
1511 let src = "INSERT INTO t2 (id) SELECT id FROM t1 WHERE path=$1 ON CONFLICT (id) DO NOTHING RETURNING id";
1512 let mut issues = Issues::new(src);
1513 let q = type_statement(&schema, src, &mut issues, &options);
1514 check_no_errors(name, src, issues.get(), &mut errors);
1515 if let StatementType::Insert {
1516 arguments,
1517 returning,
1518 ..
1519 } = q
1520 {
1521 check_arguments(name, &arguments, "str", &mut errors);
1522 check_columns(
1523 name,
1524 src,
1525 &returning.expect("Returning"),
1526 "id:i64!",
1527 &mut errors,
1528 );
1529 } else {
1530 println!("{name} should be select");
1531 errors += 1;
1532 }
1533 }
1534
1535 {
1536 let name = "q2";
1537 let src = "WITH hat AS (DELETE FROM t1 WHERE old_id=42 RETURNING id) INSERT INTO t2 (id) SELECT id FROM hat";
1538 let mut issues = Issues::new(src);
1539 let q = type_statement(&schema, src, &mut issues, &options);
1540 check_no_errors(name, src, issues.get(), &mut errors);
1541
1542 if let StatementType::Insert { arguments, .. } = q {
1543 check_arguments(name, &arguments, "", &mut errors);
1544 } else {
1545 println!("{name} should be select {q:?}");
1546 errors += 1;
1547 }
1548 }
1549
1550 {
1551 let name = "q3";
1552 let src = "INSERT INTO t1 (path) VALUES ('HI')";
1553 let mut issues: Issues<'_> = Issues::new(src);
1554 type_statement(&schema, src, &mut issues, &options);
1555 if issues.is_ok() {
1556 println!("{name} should fail");
1557 errors += 1;
1558 }
1559 }
1560
1561 {
1562 let name = "q4";
1563 let src = "INSERT INTO t1 (path, v) VALUES ('HI', 'V1')";
1564 let mut issues: Issues<'_> = Issues::new(src);
1565 let q = type_statement(&schema, src, &mut issues, &options);
1566 check_no_errors(name, src, issues.get(), &mut errors);
1567
1568 if let StatementType::Insert { arguments, .. } = q {
1569 check_arguments(name, &arguments, "", &mut errors);
1570 } else {
1571 println!("{name} should be insert {q:?}");
1572 errors += 1;
1573 }
1574 }
1575
1576 {
1577 let name = "q5";
1578 let src = "UPDATE t1 SET path='HI' RETURNING id";
1579 let mut issues: Issues<'_> = Issues::new(src);
1580 let q = type_statement(&schema, src, &mut issues, &options);
1581 if let StatementType::Update {
1582 arguments,
1583 returning,
1584 ..
1585 } = q
1586 {
1587 check_arguments(name, &arguments, "", &mut errors);
1588 if returning.is_none() {
1589 println!("{name} should have returning");
1590 errors += 1;
1591 }
1592 } else {
1593 println!("{name} should be update {q:?}");
1594 errors += 1;
1595 }
1596 }
1597
1598 {
1599 let name = "q6";
1600 let src = "INSERT INTO t3 (a,b) VALUES (1, 1) ON CONFLICT (a,b) DO UPDATE SET a=t3.a, b=EXCLUDED.b WHERE t3.a != EXCLUDED.a";
1601 let mut issues = Issues::new(src);
1602 let q = type_statement(&schema, src, &mut issues, &options);
1603 check_no_errors(name, src, issues.get(), &mut errors);
1604
1605 if let StatementType::Insert { arguments, .. } = q {
1606 check_arguments(name, &arguments, "", &mut errors);
1607 } else {
1608 println!("{name} should be insert {q:?}");
1609 errors += 1;
1610 }
1611 }
1612
1613 {
1614 let name = "q7";
1615 let src = "INSERT INTO t3 (a,b) VALUES (1, 1) ON CONFLICT (a,c) cake DO UPDATE SET a=2";
1616 let mut issues = Issues::new(src);
1617 type_statement(&schema, src, &mut issues, &options);
1618 if issues.is_ok() {
1619 println!("{name} should fail");
1620 errors += 1;
1621 }
1622 }
1623
1624 {
1625 let name = "q8";
1626 let src = "INSERT INTO t3 (a,b) VALUES (1, 1) ON CONFLICT (a,b) t3u DO UPDATE SET a=2 WHERE b=2";
1627 let mut issues = Issues::new(src);
1628 type_statement(&schema, src, &mut issues, &options);
1629 if issues.is_ok() {
1630 println!("{name} should fail");
1631 errors += 1;
1632 }
1633 }
1634
1635 {
1636 let name = "q9";
1637 let src = "SELECT left(path, -4) AS k FROM t1";
1638 let mut issues = Issues::new(src);
1639 let q = type_statement(&schema, src, &mut issues, &options);
1640
1641 check_no_errors(name, src, issues.get(), &mut errors);
1642 if let StatementType::Select { arguments, columns } = q {
1643 check_arguments(name, &arguments, "", &mut errors);
1644 check_columns(name, src, &columns, "k:str!", &mut errors);
1645 } else {
1646 println!("{name} should be select");
1647 errors += 1;
1648 }
1649 }
1650
1651 {
1652 let name = "pg_set1";
1653 let src = "SET search_path = 'myschema'";
1654 let mut issues = Issues::new(src);
1655 let q = type_statement(&schema, src, &mut issues, &options);
1656 check_no_errors(name, src, issues.get(), &mut errors);
1657 if !matches!(q, StatementType::Set) {
1658 println!("{name} should be set");
1659 errors += 1;
1660 }
1661 }
1662
1663 {
1664 let name = "pg_set2";
1665 let src = "SET @user_var = 42";
1666 let mut issues = Issues::new(src);
1667 type_statement(&schema, src, &mut issues, &options);
1668 if issues.is_ok() {
1669 println!("{name} should fail: @var not valid in PostgreSQL");
1670 errors += 1;
1671 }
1672 }
1673
1674 {
1675 let name = "pg_set3";
1676 let src = "SET @@session.time_zone = 'UTC'";
1677 let mut issues = Issues::new(src);
1678 type_statement(&schema, src, &mut issues, &options);
1679 if issues.is_ok() {
1680 println!("{name} should fail: @@var not valid in PostgreSQL");
1681 errors += 1;
1682 }
1683 }
1684
1685 if errors != 0 {
1686 panic!("{errors} errors in test");
1687 }
1688 }
1689
1690 #[test]
1691 fn sqlite() {
1692 let schema_src = "
1693 CREATE TABLE IF NOT EXISTS `t1` (
1694 `id` INTEGER NOT NULL PRIMARY KEY,
1695 `sid` TEXT NOT NULL) STRICT;
1696 CREATE UNIQUE INDEX IF NOT EXISTS `t1_sid` ON `t1` (`sid`);
1697 ";
1698
1699 let options = TypeOptions::new().dialect(SQLDialect::Sqlite);
1700 let mut issues = Issues::new(schema_src);
1701 let schema = parse_schemas(schema_src, &mut issues, &options);
1702 let mut errors = 0;
1703 check_no_errors("schema", schema_src, issues.get(), &mut errors);
1704
1705 let options = TypeOptions::new()
1706 .dialect(SQLDialect::Sqlite)
1707 .arguments(SQLArguments::QuestionMark);
1708
1709 {
1710 let name = "q1";
1711 let src = "INSERT INTO `t1` (`sid`) VALUES (?)";
1712 let mut issues = Issues::new(src);
1713 let q = type_statement(&schema, src, &mut issues, &options);
1714 check_no_errors(name, src, issues.get(), &mut errors);
1715 if !matches!(q, StatementType::Insert { .. }) {
1716 println!("{name} should be select");
1717 errors += 1;
1718 }
1719 }
1720
1721 if errors != 0 {
1722 panic!("{errors} errors in test");
1723 }
1724 }
1725}